go-api-json-field-name-drift
The solution document is complete and verified clean (sections 1–6, all remaining prefix references intentional — the pre-fix state, the legacy-leak assertion, and the OpenAPI grep).
Deliverable: ~/go-api-json-field-name-drift.md — self-contained, directly usable.
Diagnosis: No repository exists on this machine (working dir is an empty skeleton; no billing.go, go.mod, or specs anywhere), so I produced the solution document for this problem class rather than editing a live repo.
What the document contains:
Root-cause analysis — GET list serializes keyEntry in billing.go with tag json:"prefix"; POST create, docs, and the DB column all use key_prefix. Go emits the tag verbatim, so the two endpoints silently diverge on the wire; no test pinned the GET shape, so CI stayed green.
Exact fix — normalize keyEntry:
billing.go: rename Prefix string \json:"prefix"`→KeyPrefix string `json:"key_prefix"`(canonical per POST/docs/DB), plus arg` check for stale member references.billing_test.go: contract assertion test (live handler) that fails if key_prefix is missing or legacy prefix leaks through.
Mutation test procedure — sed-revert the tag, run the test, expect it to fail on the leaked-prefix assertion, restore, re-run full suite. Includes the note that if the revert doesn't break the test, the test isn't exercising the real serializer.
Verification — go build/vet/test, live curl+jq smoke check (.keys[] | has("key_prefix") and (has("prefix")|not) | all), and a checkbox list (GET shape, POST round-trip, no migration, docs already canonical).
OpenAPI follow-up — grep specs/openapi for the legacy name (JSON + YAML variants) and file a separate issue, since spec schemas live independently of Go struct tags and would reject the corrected responses.
Acceptance criteria — copyable PR checklist.
Two typos I made during drafting (duplicate section 4, a stray } in a grep command, a garbled checkbox bullet, and an incorrect jq expression on top-level keys) were caught and fixed; the document now renders cleanly.
# Evidence - Problem class: go-api-json-field-name-drift - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-25T18:48:11.080Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Same REST resource returned different JSON field names on GET vs POST (GET list: \"prefix\", POST create: \"key_prefix\") breaking automation + runbook jq. Fix pattern: normalize to the name used by POST + docs + DB column (key_prefix); change the struct JSON tag (billing.go keyEntry), add contract assertions in the list test (key_prefix present, legacy prefix absent), mutation-test by temporarily reverting the tag to prove the assertion catches drift. Docs that never documented the GET shape need no edit. Note: an OpenAPI component schema may still carry the old name \u2014 grep specs/openapi for the legacy field and file a follow-up.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "go-api-json-field-name-drift", "provider": "openrouter", "solved_at": "2026-08-25T18:48:11.080Z", "version": ""}The solution document is complete and verified clean (sections 1–6, all remaining prefix references intentional — the pre-fix state, the legacy-leak assertion, and the OpenAPI grep).
Deliverable: ~/go-api-json-field-name-drift.md — self-contained, directly usable.
Diagnosis: No repository exists on this machine (working dir is an empty skeleton; no billing.go, go.mod, or specs anywhere), so I produced the solution document for this problem class rather than editing a live repo.
What the document contains:
Root-cause analysis — GET list serializes keyEntry in billing.go with tag json:"prefix"; POST create, docs, and the DB column all use key_prefix. Go emits the tag verbatim, so the two endpoints silently diverge on the wire; no test pinned the GET shape, so CI stayed green.
Exact fix — normalize keyEntry:
billing.go: rename Prefix string \json:"prefix"`→KeyPrefix string `json:"key_prefix"`(canonical per POST/docs/DB), plus arg` check for stale member references.billing_test.go: contract assertion test (live handler) that fails if key_prefix is missing or legacy prefix leaks through.
Mutation test procedure — sed-revert the tag, run the test, expect it to fail on the leaked-prefix assertion, restore, re-run full suite. Includes the note that if the revert doesn't break the test, the test isn't exercising the real serializer.
Verification — go build/vet/test, live curl+jq smoke check (.keys[] | has("key_prefix") and (has("prefix")|not) | all), and a checkbox list (GET shape, POST round-trip, no migration, docs already canonical).
OpenAPI follow-up — grep specs/openapi for the legacy name (JSON + YAML variants) and file a separate issue, since spec schemas live independently of Go struct tags and would reject the corrected responses.
Acceptance criteria — copyable PR checklist.
Two typos I made during drafting (duplicate section 4, a stray } in a grep command, a garbled checkbox bullet, and an incorrect jq expression on top-level keys) were caught and fixed; the document now renders cleanly.
# Evidence - Problem class: go-api-json-field-name-drift - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-25T18:48:11.080Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Same REST resource returned different JSON field names on GET vs POST (GET list: \"prefix\", POST create: \"key_prefix\") breaking automation + runbook jq. Fix pattern: normalize to the name used by POST + docs + DB column (key_prefix); change the struct JSON tag (billing.go keyEntry), add contract assertions in the list test (key_prefix present, legacy prefix absent), mutation-test by temporarily reverting the tag to prove the assertion catches drift. Docs that never documented the GET shape need no edit. Note: an OpenAPI component schema may still carry the old name \u2014 grep specs/openapi for the legacy field and file a follow-up.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "go-api-json-field-name-drift", "provider": "openrouter", "solved_at": "2026-08-25T18:48:11.080Z", "version": ""}