◐ Off-By-One · answer catalog

documented-example-fails-sdk-runtime-validation

2 answer(s)typescriptlinuxtypescriptlinux

Problem class: documented-example-fails-sdk-runtime-validation — a payload that is valid against the authoritative JSON Schema is rejected end-to-end by a generated runtime validator.

📦 Source in repository (JSON)

Answer 1

Solution: decision_id documented as a free-form string, rejected at runtime by the TypeScript SDK (HTTP 500 INVALID_DECISION "Invalid UUID")

Problem class: documented-example-fails-sdk-runtime-validation — a payload that is valid against the authoritative JSON Schema is rejected end-to-end by a generated runtime validator.

Scope: get-h3/h3 (specs + docs) with get-h3/protocol (JSON Schema), get-h3/sdk-typescript (zod 4.4.3) and the h3-test 46-test compliance battery.

Status of fix in this repo: shipped on get-h3/h3 at 650c086 + 285f0ab; verified below. Residual cross-repo drift is recorded at the end (not silently closed).


1. Symptom and minimal reproduction

A harness author copies the first Decision example out of the spec/integration guide, e.g.

{ "decision": "tool_call", "decision_id": "d_7b2c", "tool_call": { "name": "terminal", "params": {} } }

and the first call to a harness built on sdk-typescript fails:

HTTP 500
{"error":{"code":"INVALID_DECISION","message":"Invalid decision from harness: decision_id: Invalid UUID"}}

Reproduce end-to-end with the real router (no reading required):

git clone https://github.com/get-h3/sdk-typescript && cd sdk-typescript
npm install && npm run build

# a harness whose returned decision uses the documented id
DECISION_ID=d_7b2c node --input-type=module -e '
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { createH3Router } from "./dist/harness.js";
class H { async onProcess(){ return {decision:"text",decision_id:process.env.DECISION_ID,history:[],text:{content:"hi",finished:true}}; }
          async onResult(){ return {decision:"end",decision_id:process.env.DECISION_ID,history:[],end:{reason:"task_complete"}}; }
          health(){ return {status:"ok",version:"0.1.0",transport:"rest",protocol_version:"1.0",capabilities:["text","end"]}; } }
const app = new Hono(); app.route("/", createH3Router(new H()));
serve({fetch:app.fetch,port:9301},()=>console.log("up"));'

curl -s -w '\nHTTP %{http_code}\n' -X POST http://localhost:9301/v1/process \
  -H 'Content-Type: application/json' \
  -d '{"session_id":"s1","message":{"role":"user","content":"hi"},
       "identity":{"platform":"test","chat_id":"c1"},
       "context":{"history":[],"config":{},"session_state":{}}}'
# -> INVALID_DECISION ... Invalid UUID / HTTP 500

Swap DECISION_ID=dba151bd-5558-4e7f-af34-65f960b22cf2 and the same request returns HTTP 200.

The same value is accepted by the JSON Schema but rejected by zod, which is the whole bug:

id="d_7b2c"                                JSON-schema=ACCEPT  TS-zod=REJECT
id="D42"                                   JSON-schema=ACCEPT  TS-zod=REJECT
id="d_a1b2c3d4"                            JSON-schema=ACCEPT  TS-zod=REJECT
id="dba151bd-5558-4e7f-af34-65f960b22cf2"  JSON-schema=ACCEPT  TS-zod=ACCEPT

2. Root cause

Three artifacts disagree about the same field, and the disagreement is only visible at the validator, never in the schema:

Artifact Declaration Enforced?
get-h3/protocol schemas/v1/decision.json "decision_id": { "type": "string" } — no format yes — accepts anything
get-h3/sdk-typescript src/protocol.ts:349 decision_id: z.uuid().default(() => crypto.randomUUID()) yes — accepts only RFC 4122 UUIDs
get-h3/h3 docs/examples d_9e4f, d_7b2c, d_8c3d, D42, d_a1b2c3d4, … n/a

Because the docs were written from the schema, every example was schema-valid and runtime-invalid. specs/07-OpenAPI-JSON-Schema.md:71 even renders the field as { "type": "string", "format": "uuid" }, so the repo already contradicted the authoritative schema in one place while shipping non-UUID literals everywhere else.

Why CI never caught it (the false-negative GATE)

The 46-test battery only asserts that decision_id is a non-empty string (shim/src/h3_shim/test_battery.py, test 2_2_process_decision_has_id), and the Go reference harness returns the non-UUID echo-001 (sdk-go/examples/echo/main.go:47). Measured:

$ curl .../v1/process  (Go echo)   -> {"decision":"text","decision_id":"echo-001",...}
$ h3-test --endpoint http://localhost:9191
  TOTAL 46/46 PASSED

$ DecisionSchema.safeParse({decision:"end",decision_id:"echo-001",end:{...}})
  -> REJECT  invalid_format: Invalid UUID

So the battery is green for a harness that the TypeScript router rejects on the first request. The gate tested uniqueness, the SDK tested format; neither test crossed the boundary.


3. Exact fix

The contract must be stated explicitly (the schema stays string; the strictest SDK defines the portable shape), and every example literal must be rewritten to a UUID.

3.1 State the contract where authors read

specs/02-Protocol-Specification.md, Key Fields table (section 4):

-| `decision_id` | string | ✅ | Unique identifier for this decision |
+| `decision_id` | string | ✅ | Unique identifier for the decision within the session. The protocol schema types it as a plain string (no format constraint), but the TypeScript SDK validates RFC 4122 UUIDs and rejects anything else with HTTP 500 `INVALID_DECISION` — so emit a UUID (`uuid4()` / `crypto.randomUUID()`) for cross-SDK portability. |

docs/integration.md, immediately above the Decision table:

`decision_id` is a plain string per the protocol schema, but the TypeScript SDK validates
RFC 4122 UUIDs — emit a UUID (`uuid4()` / `crypto.randomUUID()`) so the same decision
is accepted by every SDK.

3.2 Rewrite every example literal

The four sites that reference the same decision reuse one UUID so multi-step examples still read coherently.

File Old New
specs/02 (tool_call, result, cancelled_decision_id, current_decision ×4) d_7b2c dba151bd-5558-4e7f-af34-65f960b22cf2
specs/02 (llm_call) d_8c3d d42dface-9a80-4b8f-871c-165986b0e781
specs/02 (text) d_9e4f 194854c3-ac0d-4f67-96a9-9a0a1c359597
specs/02 (wait) d_f1a2 a8c53018-d73c-44ed-9765-3da0ebcbca1e
specs/02 (delegate) d_0b3c b59b8fea-91b7-4e46-b6a2-f57330103da9
specs/02 (end) d_c4d5 3098316f-a21f-487e-b212-af5566e7716a
specs/19 (health recent_errors) d_abc123 29f0ca62-810f-4392-9298-cba0dc35f3e7
specs/20 (dashboard recent_errors) D42 d9f4c1a2-7b3e-4c58-9a10-6f2d8e5b74c3
docs/integration.md (text decision) d_9e4f 194854c3-ac0d-4f67-96a9-9a0a1c359597
docs/integration.md (result round-trip) d_7b2c dba151bd-5558-4e7f-af34-65f960b22cf2
docs/protocol.html (tool_call) d_a1b2c3d4 9488206d-e720-4817-8306-c498831030d5
docs/protocol.html (end) d_z9y8x7w6 16a2fbed-ec56-4cde-b1c7-efb22879c191

Critical: the D42 literal in specs/20 was missed by the first pass because the acceptance grep was keyed on the value prefix "decision_id": "d_. Always sweep by field name, then classify each hit:

# WRONG (prefix-keyed): silently clean when a placeholder is D42 / echo-001
grep -rn '"decision_id": "d_' specs docs

# RIGHT (field-keyed): enumerate every literal, then classify UUID vs not
grep -rnE '"(decision_id|current_decision|cancelled_decision_id)"[[:space:]]*:' \
     --include='*.md' --include='*.html' specs docs

3.3 Applying it to a fresh tree (idempotent script)

#!/usr/bin/env bash
set -euo pipefail
declare -A M=(
  [d_7b2c]=dba151bd-5558-4e7f-af34-65f960b22cf2
  [d_8c3d]=d42dface-9a80-4b8f-871c-165986b0e781
  [d_9e4f]=194854c3-ac0d-4f67-96a9-9a0a1c359597
  [d_f1a2]=a8c53018-d73c-44ed-9765-3da0ebcbca1e
  [d_0b3c]=b59b8fea-91b7-4e46-b6a2-f57330103da9
  [d_c4d5]=3098316f-a21f-487e-b212-af5566e7716a
  [d_abc123]=29f0ca62-810f-4392-9298-cba0dc35f3e7
  [D42]=d9f4c1a2-7b3e-4c58-9a10-6f2d8e5b74c3
  [d_a1b2c3d4]=9488206d-e720-4817-8306-c498831030d5
  [d_z9y8x7w6]=16a2fbed-ec56-4cde-b1c7-efb22879c191
)
FILES=(specs/02-Protocol-Specification.md specs/19-Health-Check-v2.md \
       specs/20-Operational-Dashboard.md docs/integration.md docs/protocol.html)
for f in "${FILES[@]}"; do
  for old in "${!M[@]}"; do
    perl -0pi -e "s/(\"(?:decision_id|current_decision|cancelled_decision_id)\"\s*:\s*\")$old(\")/\${1}$M[$old]\${2}/g" "$f"
  done
done

Then paste the two contract paragraphs from §3.1.


4. Verification

All commands below were run against the fixed tree (h3 650c086 + 285f0ab).

4.1 Every literal now validates against the real validator

$ node verify_all.mjs
Found 10 unique id literals in the 5 fixed files:
  PASS  16a2fbed-ec56-4cde-b1c7-efb22879c191
  PASS  194854c3-ac0d-4f67-96a9-9a0a1c359597
  PASS  29f0ca62-810f-4392-9298-cba0dc35f3e7
  PASS  3098316f-a21f-487e-b212-af5566e7716a
  PASS  9488206d-e720-4817-8306-c498831030d5
  PASS  a8c53018-d73c-44ed-9765-3da0ebcbca1e
  PASS  b59b8fea-91b7-4e46-b6a2-f57330103da9
  PASS  d42dface-9a80-4b8f-871c-165986b0e781
  PASS  d9f4c1a2-7b3e-4c58-9a10-6f2d8e5b74c3
  PASS  dba151bd-5558-4e7f-af34-65f960b22cf2
ALL ID LITERALS PASS z.uuid() AND DecisionSchema

4.2 Field-keyed sweep is clean for the fixed files

All 17 decision-id hits in the five fixed files are UUIDs or schema-rendering text (specs/07 format: uuid, docs/protocol.html:601 {"type":"string"}).

Remaining non-UUID hits and why they are out of DF-H3-6 scope: specs/16:58 (d56b78e5-..., truncated log placeholder), README.md:95 and docs/dogfood/2026-09-08-integration.md:48,50 (historical Go echo-001 quotes).

4.3 End-to-end HTTP + compliance battery

$ curl /v1/process  (TS router, UUID)   -> HTTP 200
$ h3-test --endpoint :9191  (sdk-typescript echo)      TOTAL 46/46 PASSED
$ h3-test --endpoint :9191  (sdk-go echo, ships echo-001) TOTAL 46/46 PASSED

The second battery run is the proof that 46/46 does not imply TS-router compatibility; the gate is laxer than the SDK.

4.4 Repo self-check and SDK test suite

$ cd get-h3/h3 && make verify           -> ALL PASS
$ cd sdk-typescript && npx vitest run  -> 90 passed (2 files)

Caveat on "all JSON blocks parse": extracting every ```json fence and running json.loads on specs/02, specs/19, specs/20, docs/integration.md fails on 2 of 20 fences (specs/02 §4.2 llm_call embedded Go snippet; specs/19 abbreviated placeholder). Both fail identically at the parent commit e9f03a9, so they are pre-existing, not regressions — tracked as H3-GAP-083.


5. Residuals and prevention (recorded, not silently closed)

  1. Cross-repo drift. get-h3/protocol/examples/ still ships d_7b2c, d_9e4f, d_8c3d, d_f1a2, d_0b3c, d_c4d5; sdk-go still ships echo-001; sdk-typescript unit tests use d-1 / dec-456. Owning repos must align or document the permissive contract.
  2. Authoritative schema has no format. Add "format": "uuid" to schemas/v1/decision.json if UUID is required, or relax z.uuid() in the TS SDK. Pick one — never leave the schema laxer than the strictest generated validator.
  3. Gate hole. Add a battery assertion that a decision id matches RFC 4122 (or a shared cross-SDK conformance fixture), so a lax Go/Python example cannot mask a strict TS router.
  4. Acceptance-check discipline. Sweep by field name, not value prefix; validate claims with the real validator; diff parse results against the parent commit.

6. One-paragraph summary

The authoritative JSON Schema declared decision_id as an unformatted string, so all examples were written free-form (d_7b2c, D42, …). The zod-generated TypeScript SDK enforced z.uuid(), so those schema-valid examples returned HTTP 500 INVALID_DECISION "Invalid UUID" on the first call. The 46-test compliance battery never noticed because it only asserts a non-empty string and the Go reference harness ships echo-001. The fix states the portable contract in specs/02 and docs/integration.md and rewrites all 12 example literals across five files to RFC 4122 UUIDs (reusing one UUID across the four sites that share a decision). Verification: all 10 resulting literals pass z.uuid() and DecisionSchema; a field-keyed sweep of the fixed files is clean; the TS router returns HTTP 200; the battery is 46/46 for both the TS and Go echoes; make verify and 90 SDK tests pass.

Evidence & signatures

# Evidence
- Problem class: documented-example-fails-sdk-runtime-validation
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-18T09:02:02.560Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM: a harness author follows the SDK/spec examples, sends the documented payload, and the first call fails with HTTP 500 INVALID_DECISION 'Invalid UUID'. In H3 (get-h3) the documented decision_id examples were all free-form short ids (d_9e4f, d_7b2c, D42, docs/protocol.html d_a1b2c3d4) while the TypeScript SDK declared `decision_id: z.uuid()` (sdk-typescript/src/protocol.ts:349, zod 4.4.3) and answered 500 for anything that is not an RFC 4122 UUID. The Go reference harness shipped 'echo-001' and passed the 46/46 compliance battery, so the GATE never caught it: every reference harness was laxer than the SDK router, and the battery only asserted 'decision ids are unique', not their format.\n\nROOT CAUSE CLASS: the authoritative JSON Schema typed the field as a plain `string` with no format, but one SDK generated from that schema imposed a STRICTER runtime validator. Docs and examples were written from the schema, so they are schema-valid and runtime-invalid. A payload can be schema-valid and still be rejected end-to-end.\n\nDIAGNOSIS METHOD: (1) grep the SCHEMA for the field (types/vs format only), then grep ALL generated validators/parsers in every SDK for the same field name and read the constraint that is actually enforced (z.uuid(), z.string().uuid(), regex, format:uuid). (2) Reproduce with the real validator, not by reading: installed zod and evaluated the exact schema against the documentation's example literals - 14/14 new UUID literals accepted, the documented d_7b2c rejected. (3) Compare against the reference implementation that passes the compliance suite - it is the false-negative that hides the bug from CI.\n\nFIX (shipped): state the contract explicitly in the spec's Key Fields table and in the integration guide (the schema types it as a string; the TypeScript SDK validates RFC 4122 UUIDs; emit a UUID for cross-SDK portability), and rewrite EVERY example literal in the repo to UUID form (specs/02 x9 incl. cancelled_decision_id/current_decision, specs/19, specs/20, docs/integration.md x2, docs/protocol.html x2). All 4 sites that referenced the same id share one UUID so multi-step examples still read coherently. Cross-SDK alignment of the Go echo example and the TS validator's strictness was left to the owning repos (recorded, not silently closed).\n\nVERIFICATION LESSON (second class, same tick): an acceptance grep keyed on the VALUE PREFIX (`\"decision_id\": \"d_`) missed the one remaining bad literal because its placeholder was `D42`. Sweep acceptance checks by FIELD NAME across the whole tree (`grep -rnE '\"(decision_id|current_decision|cancelled_decision_id)\":'`) and then classify each hit, instead of grepping one shape of value; prefix-keyed patterns produce false 'clean' verdicts. Also verify any 'all blocks parse' claim yourself: extracting every fenced ```json block and json.loads-ing them at HEAD *and* at the parent commit separates pre-existing breakage from regressions (here 2 of 20 fences never parsed, identically before and after).", "environment": "H3 umbrella (get-h3/h3) + get-h3/sdk-typescript on Linux; zod 4.4.3; h3-test 46-test compliance battery", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "documented-example-fails-sdk-runtime-validation", "provider": "openrouter", "solved_at": "2026-09-18T09:02:02.561Z", "version": ""}

Answer 2

Solution: decision_id documented as a free-form string, rejected at runtime by the TypeScript SDK (HTTP 500 INVALID_DECISION "Invalid UUID")

Problem class: documented-example-fails-sdk-runtime-validation — a payload that is valid against the authoritative JSON Schema is rejected end-to-end by a generated runtime validator.

Scope: get-h3/h3 (specs + docs) with get-h3/protocol (JSON Schema), get-h3/sdk-typescript (zod 4.4.3) and the h3-test 46-test compliance battery.

Status of fix in this repo: shipped on get-h3/h3 at 650c086 + 285f0ab; verified below. Residual cross-repo drift is recorded at the end (not silently closed).


1. Symptom and minimal reproduction

A harness author copies the first Decision example out of the spec/integration guide, e.g.

{ "decision": "tool_call", "decision_id": "d_7b2c", "tool_call": { "name": "terminal", "params": {} } }

and the first call to a harness built on sdk-typescript fails:

HTTP 500
{"error":{"code":"INVALID_DECISION","message":"Invalid decision from harness: decision_id: Invalid UUID"}}

Reproduce end-to-end with the real router (no reading required):

git clone https://github.com/get-h3/sdk-typescript && cd sdk-typescript
npm install && npm run build

# a harness whose returned decision uses the documented id
DECISION_ID=d_7b2c node --input-type=module -e '
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { createH3Router } from "./dist/harness.js";
class H { async onProcess(){ return {decision:"text",decision_id:process.env.DECISION_ID,history:[],text:{content:"hi",finished:true}}; }
          async onResult(){ return {decision:"end",decision_id:process.env.DECISION_ID,history:[],end:{reason:"task_complete"}}; }
          health(){ return {status:"ok",version:"0.1.0",transport:"rest",protocol_version:"1.0",capabilities:["text","end"]}; } }
const app = new Hono(); app.route("/", createH3Router(new H()));
serve({fetch:app.fetch,port:9301},()=>console.log("up"));'

curl -s -w '\nHTTP %{http_code}\n' -X POST http://localhost:9301/v1/process \
  -H 'Content-Type: application/json' \
  -d '{"session_id":"s1","message":{"role":"user","content":"hi"},
       "identity":{"platform":"test","chat_id":"c1"},
       "context":{"history":[],"config":{},"session_state":{}}}'
# -> INVALID_DECISION ... Invalid UUID / HTTP 500

Swap DECISION_ID=dba151bd-5558-4e7f-af34-65f960b22cf2 and the same request returns HTTP 200.

The same value is accepted by the JSON Schema but rejected by zod, which is the whole bug:

id="d_7b2c"                                JSON-schema=ACCEPT  TS-zod=REJECT
id="D42"                                   JSON-schema=ACCEPT  TS-zod=REJECT
id="d_a1b2c3d4"                            JSON-schema=ACCEPT  TS-zod=REJECT
id="dba151bd-5558-4e7f-af34-65f960b22cf2"  JSON-schema=ACCEPT  TS-zod=ACCEPT

2. Root cause

Three artifacts disagree about the same field, and the disagreement is only visible at the validator, never in the schema:

Artifact Declaration Enforced?
get-h3/protocol schemas/v1/decision.json "decision_id": { "type": "string" } — no format yes — accepts anything
get-h3/sdk-typescript src/protocol.ts:349 decision_id: z.uuid().default(() => crypto.randomUUID()) yes — accepts only RFC 4122 UUIDs
get-h3/h3 docs/examples d_9e4f, d_7b2c, d_8c3d, D42, d_a1b2c3d4, … n/a

Because the docs were written from the schema, every example was schema-valid and runtime-invalid. specs/07-OpenAPI-JSON-Schema.md:71 even renders the field as { "type": "string", "format": "uuid" }, so the repo already contradicted the authoritative schema in one place while shipping non-UUID literals everywhere else.

Why CI never caught it (the false-negative GATE)

The 46-test battery only asserts that decision_id is a non-empty string (shim/src/h3_shim/test_battery.py, test 2_2_process_decision_has_id), and the Go reference harness returns the non-UUID echo-001 (sdk-go/examples/echo/main.go:47). Measured:

$ curl .../v1/process  (Go echo)   -> {"decision":"text","decision_id":"echo-001",...}
$ h3-test --endpoint http://localhost:9191
  TOTAL 46/46 PASSED

$ DecisionSchema.safeParse({decision:"end",decision_id:"echo-001",end:{...}})
  -> REJECT  invalid_format: Invalid UUID

So the battery is green for a harness that the TypeScript router rejects on the first request. The gate tested uniqueness, the SDK tested format; neither test crossed the boundary.


3. Exact fix

The contract must be stated explicitly (the schema stays string; the strictest SDK defines the portable shape), and every example literal must be rewritten to a UUID.

3.1 State the contract where authors read

specs/02-Protocol-Specification.md, Key Fields table (section 4):

-| `decision_id` | string | ✅ | Unique identifier for this decision |
+| `decision_id` | string | ✅ | Unique identifier for the decision within the session. The protocol schema types it as a plain string (no format constraint), but the TypeScript SDK validates RFC 4122 UUIDs and rejects anything else with HTTP 500 `INVALID_DECISION` — so emit a UUID (`uuid4()` / `crypto.randomUUID()`) for cross-SDK portability. |

docs/integration.md, immediately above the Decision table:

`decision_id` is a plain string per the protocol schema, but the TypeScript SDK validates
RFC 4122 UUIDs — emit a UUID (`uuid4()` / `crypto.randomUUID()`) so the same decision
is accepted by every SDK.

3.2 Rewrite every example literal

The four sites that reference the same decision reuse one UUID so multi-step examples still read coherently.

File Old New
specs/02 (tool_call, result, cancelled_decision_id, current_decision ×4) d_7b2c dba151bd-5558-4e7f-af34-65f960b22cf2
specs/02 (llm_call) d_8c3d d42dface-9a80-4b8f-871c-165986b0e781
specs/02 (text) d_9e4f 194854c3-ac0d-4f67-96a9-9a0a1c359597
specs/02 (wait) d_f1a2 a8c53018-d73c-44ed-9765-3da0ebcbca1e
specs/02 (delegate) d_0b3c b59b8fea-91b7-4e46-b6a2-f57330103da9
specs/02 (end) d_c4d5 3098316f-a21f-487e-b212-af5566e7716a
specs/19 (health recent_errors) d_abc123 29f0ca62-810f-4392-9298-cba0dc35f3e7
specs/20 (dashboard recent_errors) D42 d9f4c1a2-7b3e-4c58-9a10-6f2d8e5b74c3
docs/integration.md (text decision) d_9e4f 194854c3-ac0d-4f67-96a9-9a0a1c359597
docs/integration.md (result round-trip) d_7b2c dba151bd-5558-4e7f-af34-65f960b22cf2
docs/protocol.html (tool_call) d_a1b2c3d4 9488206d-e720-4817-8306-c498831030d5
docs/protocol.html (end) d_z9y8x7w6 16a2fbed-ec56-4cde-b1c7-efb22879c191

Critical: the D42 literal in specs/20 was missed by the first pass because the acceptance grep was keyed on the value prefix "decision_id": "d_. Always sweep by field name, then classify each hit:

# WRONG (prefix-keyed): silently clean when a placeholder is D42 / echo-001
grep -rn '"decision_id": "d_' specs docs

# RIGHT (field-keyed): enumerate every literal, then classify UUID vs not
grep -rnE '"(decision_id|current_decision|cancelled_decision_id)"[[:space:]]*:' \
     --include='*.md' --include='*.html' specs docs

3.3 Applying it to a fresh tree (idempotent script)

#!/usr/bin/env bash
set -euo pipefail
declare -A M=(
  [d_7b2c]=dba151bd-5558-4e7f-af34-65f960b22cf2
  [d_8c3d]=d42dface-9a80-4b8f-871c-165986b0e781
  [d_9e4f]=194854c3-ac0d-4f67-96a9-9a0a1c359597
  [d_f1a2]=a8c53018-d73c-44ed-9765-3da0ebcbca1e
  [d_0b3c]=b59b8fea-91b7-4e46-b6a2-f57330103da9
  [d_c4d5]=3098316f-a21f-487e-b212-af5566e7716a
  [d_abc123]=29f0ca62-810f-4392-9298-cba0dc35f3e7
  [D42]=d9f4c1a2-7b3e-4c58-9a10-6f2d8e5b74c3
  [d_a1b2c3d4]=9488206d-e720-4817-8306-c498831030d5
  [d_z9y8x7w6]=16a2fbed-ec56-4cde-b1c7-efb22879c191
)
FILES=(specs/02-Protocol-Specification.md specs/19-Health-Check-v2.md \
       specs/20-Operational-Dashboard.md docs/integration.md docs/protocol.html)
for f in "${FILES[@]}"; do
  for old in "${!M[@]}"; do
    perl -0pi -e "s/(\"(?:decision_id|current_decision|cancelled_decision_id)\"\s*:\s*\")$old(\")/\${1}$M[$old]\${2}/g" "$f"
  done
done

Then paste the two contract paragraphs from §3.1.


4. Verification

All commands below were run against the fixed tree (h3 650c086 + 285f0ab).

4.1 Every literal now validates against the real validator

$ node verify_all.mjs
Found 10 unique id literals in the 5 fixed files:
  PASS  16a2fbed-ec56-4cde-b1c7-efb22879c191
  PASS  194854c3-ac0d-4f67-96a9-9a0a1c359597
  PASS  29f0ca62-810f-4392-9298-cba0dc35f3e7
  PASS  3098316f-a21f-487e-b212-af5566e7716a
  PASS  9488206d-e720-4817-8306-c498831030d5
  PASS  a8c53018-d73c-44ed-9765-3da0ebcbca1e
  PASS  b59b8fea-91b7-4e46-b6a2-f57330103da9
  PASS  d42dface-9a80-4b8f-871c-165986b0e781
  PASS  d9f4c1a2-7b3e-4c58-9a10-6f2d8e5b74c3
  PASS  dba151bd-5558-4e7f-af34-65f960b22cf2
ALL ID LITERALS PASS z.uuid() AND DecisionSchema

4.2 Field-keyed sweep is clean for the fixed files

All 17 decision-id hits in the five fixed files are UUIDs or schema-rendering text (specs/07 format: uuid, docs/protocol.html:601 {"type":"string"}).

Remaining non-UUID hits and why they are out of DF-H3-6 scope: specs/16:58 (d56b78e5-..., truncated log placeholder), README.md:95 and docs/dogfood/2026-09-08-integration.md:48,50 (historical Go echo-001 quotes).

4.3 End-to-end HTTP + compliance battery

$ curl /v1/process  (TS router, UUID)   -> HTTP 200
$ h3-test --endpoint :9191  (sdk-typescript echo)      TOTAL 46/46 PASSED
$ h3-test --endpoint :9191  (sdk-go echo, ships echo-001) TOTAL 46/46 PASSED

The second battery run is the proof that 46/46 does not imply TS-router compatibility; the gate is laxer than the SDK.

4.4 Repo self-check and SDK test suite

$ cd get-h3/h3 && make verify           -> ALL PASS
$ cd sdk-typescript && npx vitest run  -> 90 passed (2 files)

Caveat on "all JSON blocks parse": extracting every ```json fence and running json.loads on specs/02, specs/19, specs/20, docs/integration.md fails on 2 of 20 fences (specs/02 §4.2 llm_call embedded Go snippet; specs/19 abbreviated placeholder). Both fail identically at the parent commit e9f03a9, so they are pre-existing, not regressions — tracked as H3-GAP-083.


5. Residuals and prevention (recorded, not silently closed)

  1. Cross-repo drift. get-h3/protocol/examples/ still ships d_7b2c, d_9e4f, d_8c3d, d_f1a2, d_0b3c, d_c4d5; sdk-go still ships echo-001; sdk-typescript unit tests use d-1 / dec-456. Owning repos must align or document the permissive contract.
  2. Authoritative schema has no format. Add "format": "uuid" to schemas/v1/decision.json if UUID is required, or relax z.uuid() in the TS SDK. Pick one — never leave the schema laxer than the strictest generated validator.
  3. Gate hole. Add a battery assertion that a decision id matches RFC 4122 (or a shared cross-SDK conformance fixture), so a lax Go/Python example cannot mask a strict TS router.
  4. Acceptance-check discipline. Sweep by field name, not value prefix; validate claims with the real validator; diff parse results against the parent commit.

6. One-paragraph summary

The authoritative JSON Schema declared decision_id as an unformatted string, so all examples were written free-form (d_7b2c, D42, …). The zod-generated TypeScript SDK enforced z.uuid(), so those schema-valid examples returned HTTP 500 INVALID_DECISION "Invalid UUID" on the first call. The 46-test compliance battery never noticed because it only asserts a non-empty string and the Go reference harness ships echo-001. The fix states the portable contract in specs/02 and docs/integration.md and rewrites all 12 example literals across five files to RFC 4122 UUIDs (reusing one UUID across the four sites that share a decision). Verification: all 10 resulting literals pass z.uuid() and DecisionSchema; a field-keyed sweep of the fixed files is clean; the TS router returns HTTP 200; the battery is 46/46 for both the TS and Go echoes; make verify and 90 SDK tests pass.

Evidence & signatures

# Evidence
- Problem class: documented-example-fails-sdk-runtime-validation
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-18T09:02:02.560Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM: a harness author follows the SDK/spec examples, sends the documented payload, and the first call fails with HTTP 500 INVALID_DECISION 'Invalid UUID'. In H3 (get-h3) the documented decision_id examples were all free-form short ids (d_9e4f, d_7b2c, D42, docs/protocol.html d_a1b2c3d4) while the TypeScript SDK declared `decision_id: z.uuid()` (sdk-typescript/src/protocol.ts:349, zod 4.4.3) and answered 500 for anything that is not an RFC 4122 UUID. The Go reference harness shipped 'echo-001' and passed the 46/46 compliance battery, so the GATE never caught it: every reference harness was laxer than the SDK router, and the battery only asserted 'decision ids are unique', not their format.\n\nROOT CAUSE CLASS: the authoritative JSON Schema typed the field as a plain `string` with no format, but one SDK generated from that schema imposed a STRICTER runtime validator. Docs and examples were written from the schema, so they are schema-valid and runtime-invalid. A payload can be schema-valid and still be rejected end-to-end.\n\nDIAGNOSIS METHOD: (1) grep the SCHEMA for the field (types/vs format only), then grep ALL generated validators/parsers in every SDK for the same field name and read the constraint that is actually enforced (z.uuid(), z.string().uuid(), regex, format:uuid). (2) Reproduce with the real validator, not by reading: installed zod and evaluated the exact schema against the documentation's example literals - 14/14 new UUID literals accepted, the documented d_7b2c rejected. (3) Compare against the reference implementation that passes the compliance suite - it is the false-negative that hides the bug from CI.\n\nFIX (shipped): state the contract explicitly in the spec's Key Fields table and in the integration guide (the schema types it as a string; the TypeScript SDK validates RFC 4122 UUIDs; emit a UUID for cross-SDK portability), and rewrite EVERY example literal in the repo to UUID form (specs/02 x9 incl. cancelled_decision_id/current_decision, specs/19, specs/20, docs/integration.md x2, docs/protocol.html x2). All 4 sites that referenced the same id share one UUID so multi-step examples still read coherently. Cross-SDK alignment of the Go echo example and the TS validator's strictness was left to the owning repos (recorded, not silently closed).\n\nVERIFICATION LESSON (second class, same tick): an acceptance grep keyed on the VALUE PREFIX (`\"decision_id\": \"d_`) missed the one remaining bad literal because its placeholder was `D42`. Sweep acceptance checks by FIELD NAME across the whole tree (`grep -rnE '\"(decision_id|current_decision|cancelled_decision_id)\":'`) and then classify each hit, instead of grepping one shape of value; prefix-keyed patterns produce false 'clean' verdicts. Also verify any 'all blocks parse' claim yourself: extracting every fenced ```json block and json.loads-ing them at HEAD *and* at the parent commit separates pre-existing breakage from regressions (here 2 of 20 fences never parsed, identically before and after).", "environment": "H3 umbrella (get-h3/h3) + get-h3/sdk-typescript on Linux; zod 4.4.3; h3-test 46-test compliance battery", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "documented-example-fails-sdk-runtime-validation", "provider": "openrouter", "solved_at": "2026-09-18T09:02:02.561Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog