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 required lookup 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-manager os-project-manager added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 16, 2026 — with Claude
@vercel

vercel Bot commented Aug 16, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 16, 2026 3:07am

Request Review

@os-project-manager os-project-manager added documentation Improvements or additions to documentation and removed size/s labels Aug 16, 2026 — with Claude
@os-project-manager
os-project-manager marked this pull request as ready for review August 16, 2026 03:10
@os-project-manager
os-project-manager added this pull request to the merge queue Aug 16, 2026
Merged via the queue into main with commit 02042be Aug 16, 2026
33 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-8880-missing-required-field-status branch August 16, 2026 03:24
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…he status the runtime emits (objectstack-ai#8967) (objectstack-ai#9023)

`content/docs/api/error-catalog.mdx` and
`content/docs/protocol/kernel/error-handling.mdx` publish an HTTP status per
error code; the doors decide it. Nothing reconciled the two, so
`MISSING_REQUIRED_FIELD` was documented 400 in both pages while five
`controlled_by_parent` refusal paths answered 422, with CI green throughout.

`scripts/check-error-status-conformance.mjs` closes that. The runtime side is
DERIVED, never listed: error classes' own `readonly status` / `readonly
statusCode`, both `sendError` doors' literal arguments, the REST mapper's
`{ status, body }` terminals, and `HttpStatusErrorCodeMap`'s explicit entries
(never the bucket fallback). Identifiers resolve through a repo-wide constant
index including computed-key status tables; an unresolvable declaration is
reported, not dropped; comments are stripped first, because this repo narrates
envelopes in prose and two of the gate's first findings were docblocks
describing a bug that had been fixed.

The assertion is a set comparison in BOTH directions, so a code may carry more
than one documented status: every status the runtime can emit is documented,
and every status the docs claim is reachable. Doc statements are graded —
per-code lines and quick-reference rows CLAIM (both directions), `## … (NNN)`
section headings only COVER (direction A), because a category heading is not a
per-code assertion.

Verified red on PR objectstack-ai#8963's merge parent (90197e1), where the real pre-fix
pages filed the code under 400 with no exception:

    x MISSING_REQUIRED_FIELD: the runtime can emit HTTP 422, and no doc
      publishes that status for it (documented: 400). Emitted at
      packages/plugins/plugin-security/src/errors.ts: class
      MasterReferenceMissingError.

Swapping in ONLY objectstack-ai#8963's two doc files on that same tree clears it. The real
pre-fix text is embedded as a `--self-test` red case so the property is pinned,
not demonstrated once.

Two more of the same defect class, found by the new instrument on `main` and
corrected here in `error-catalog.mdx`'s quick reference (docs only, no runtime
status moved): `NOT_IMPLEMENTED` is served 501 by four storage routes while the
page filed it under 500, and `TIMEOUT` is the code a 504 carries while the 500
row claimed it and nothing emits it at 500.

Refs objectstack-ai#8967

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation skip-changeset PR 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