Skip to content

docs: MISSING_REQUIRED_FIELD answers 422 without fields on the surviving controlled_by_parent refusals - #8963

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-8880-missing-required-field-status
Aug 16, 2026
Merged

docs: MISSING_REQUIRED_FIELD answers 422 without fields on the surviving controlled_by_parent refusals#8963
os-project-manager merged 1 commit into
mainfrom
claude/issue-8880-missing-required-field-status

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#8880

Docs-only. Direction 1 of the card, per the maintainer ruling of 2026-08-15: the docs state the exception where it exists and name why the gate answers 422 there. No runtime edits — direction 2 (moving the wire to 400) was declined, and direction 3 (a catalog vs. runtime status conformance gate) stays its own card.

The mismatch, re-measured on this branch's base

MISSING_REQUIRED_FIELD is documented as HTTP 400 carrying fields, while assertControlledByParentWrite answers 422 MISSING_REQUIRED_FIELD with no fields on four declarable shapes. I verified each half from source rather than taking the card's summary:

The four shapes come from resolveCbpRelation (packages/plugins/plugin-security/src/security-plugin.ts), which sets the one bit the stand-down turns on:

omissionRefusedByValidation:
def?.type==='master_detail'&&!!def?.required&&!def?.readonly&&!def?.system,

So the hand-over to 400 VALIDATION_FAILED happens only for a master_detail that is required and neither readonly nor system. The 422 survives for: a master_detail with no required; required + readonly; required + system; and a requiredlookup resolved through the third fallback. All four are pinned in controlled-by-parent-sharing.test.ts on err.status / err.statusCode 422. The stored-row case (an update or delete by id whose persisted FK is null) travels the non-insert branch and keeps the same envelope.

The 422 and the absent fields are confirmed at the door, not assumed.MasterReferenceMissingError declares code = 'MISSING_REQUIRED_FIELD', status = 422, statusCode = 422 (errors.ts); declaredHttpStatus in packages/rest/src/error-response.ts reads status then statusCode, and its 4xx passthrough arm emits { error, code, object } — no fields. fields is emitted only for the VALIDATION_FAILED duck-type, which this error is not. Both halves of the card check out.

One claim from the card that I did NOT repeat

The card, the landed #8879 changeset, and two source comments all state that #8772's publish-time lint refuses these shapes, "so newly authored metadata cannot reach them". That is not true today.#8772 is still open, relationship/master-detail-required is severity: 'warning' (packages/lint/src/data-model-rules.ts:491-499), and no rule covers the readonly, system, or fallback-lookup shapes at all. Writing that bound into the docs would have documented a guarantee that does not exist, so the pages say instead that these shapes are authorable today. Filed separately as #8959 (sub-issue of #8772) — the stale claim is in landed source comments and a release-note-bound changeset, which is a runtime-adjacent fix and out of scope here.

Related to the same honesty point: nothing mechanically pins either side of this branch, so both pages tell clients to branch on code and read the status off the response rather than deriving it from a table.

Changes

  • content/docs/protocol/kernel/error-handling.mdx — the MISSING_REQUIRED_FIELD entry carries the exception, the full five-row matrix (which shape is refused by which subsystem, with status, code and whether fields rides along), why the gate refuses rather than handing over (the measured fail-open alternative), and the authorable-today note. The 422 row of the HTTP status table now points at it.
  • content/docs/api/error-catalog.mdx — the entry's "Check fields for the field name" line is qualified, a Callout carries the same exception in list form, and the status quick reference gains a 422 row marked as an exception to the 400 row rather than a second home for the code. The matrix itself lives in one place only (the protocol page, linked) — duplicating it across two files is the drift this card is about.

Verification

Gate union run after the final commit, at 8ead907cd:

pnpm check:docs-audit-scope ✓ scope in sync with content/docs/: 177 hand-written doc(s)
pnpm check:role-word ✓ OK (43 baselined file(s), no new occurrences)
pnpm check:nul-bytes ✓ OK (scanned 5935 text file(s); no raw ASCII control bytes)
pnpm check:error-code-casing ✓ no lowercase error codes in 4119 scanned file(s)

Re-derived against the actual changed paths with node scripts/pm/dispatch-gates.mjs — it returns exactly check:docs-audit-scope and check:role-word for content/docs, adding nothing; check:nul-bytes and check:error-code-casing were run as the convention-scoped pair. No package builds or tests are implicated: no source file changed.

Anchor and cross-doc link forms were checked against real precedents in the tree (#letting-a-delegate-invite-delegated_admin confirms underscores survive slugification; /docs/protocol/kernel/... confirms the cross-page path). The exception is rendered as a list rather than a table inside the Callout, since no existing Callout in content/docs contains a markdown table.

skip-changeset: docs-only, nothing releasable.


Generated by Claude Code

…iving controlled_by_parent refusals (#8880)
`MISSING_REQUIRED_FIELD` is documented as HTTP 400 carrying `fields[]`
(`error-handling.mdx`, `error-catalog.mdx`), but `assertControlledByParentWrite`
answers `422 MISSING_REQUIRED_FIELD` with no `fields[]` on four declarable
shapes, plus a stored-row case on a different path. Documents the exception
where it exists rather than moving the wire.
Measured on this branch's base rather than taken from the card:
- `resolveCbpRelation` sets `omissionRefusedByValidation` to
`type === 'master_detail' && required && !readonly && !system`, so only
that shape hands over to validation's `400 VALIDATION_FAILED`.
- The four surviving shapes are pinned by
`controlled-by-parent-sharing.test.ts` (`err.status`/`statusCode` 422).
- `MasterReferenceMissingError` declares `code`/`status`/`statusCode`;
`mapDataError`'s 4xx passthrough emits `{ error, code, object }`, and
`fields[]` is emitted only for the `VALIDATION_FAILED` duck-type — so the
absent `fields[]` is confirmed at the door, not assumed.
One claim from the card and the #8879 changeset is deliberately NOT repeated:
that a publish-time lint bounds these shapes. #8772 is still open and
`relationship/master-detail-required` is `severity: 'warning'`, with no rule
covering the `readonly`/`system`/fallback-`lookup` shapes — so the docs say
these shapes are authorable today. Filed as #8959.
No runtime change: the refusal is correct (an unconditional hand-over was
measured fail-open) and direction 2 was declined by the maintainer ruling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RB4waLuNbdruCo6X9oobm
@os-project-manageros-project-manager added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 16, 2026 — with Claude
@vercel

vercelBot commented Aug 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 16, 2026 3:07am

Request Review

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] MISSING_REQUIRED_FIELD is documented as HTTP 400, but the surviving controlled_by_parent insert refusals answer 422

2 participants

@os-project-manager@claude