Skip to content

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

Description

@qq9340100

Found by the domain:services/identity PM seat while reviewing PR #8879 (#8688). Filed unassigned for triage grading — I am recording it, not claiming it. Predates #8879 and is narrowed by it, not created.

The mismatch

content/docs/protocol/kernel/error-handling.mdx:246 declares:

MISSING_REQUIRED_FIELD

HTTP Status: 400
Meaning: Required field is missing

content/docs/api/error-catalog.mdx:87 says the same thing in the other direction — "Check fields for the field name" — i.e. the envelope carries fields[]. Its status table at :630 also files MISSING_REQUIRED_FIELD under 400.

After #8879, assertControlledByParentWrite still answers 422 MISSING_REQUIRED_FIELD with no fields[] for four shapes it deliberately keeps refusing:

  • master_detail with no required
  • master_detail + required + readonly
  • master_detail + required + system
  • a required-lookup fallback relation

Plus the stored-row case (a by-id write where the persisted FK is null), which is a different path.

So a documented 400-class code is served as a 422 on a live path, without the fields[] the catalog tells clients to read.

Why this is worth a card rather than a shrug

What it is NOT

⛔ Not a request to change the 422 to a 400. The four shapes keep the gate deliberately: two independent runs measured an unconditional hand-over as fail-open there — it mints a detail row with a null master FK that the controlled_by_parent read filter can never match, readable by nobody. The refusal is correct; only its documented status is in question.

The blast radius is also bounded on purpose: these shapes are what #8772's lint refuses at publish, so newly authored metadata cannot reach them. Existing declarations can.

Directions (for triage, not a recommendation)

  1. Correct the docs to state the exception where it exists, naming why the gate answers 422 on these shapes.
  2. Move the surviving refusals onto the documented 400 shape — needs a ruling; it is another public-contract change and would want the same conditionality discipline A missing required master-detail parent still answers 422 MISSING_REQUIRED_FIELD with no fields[] and a [Security] message — while the same field, present-but-unresolvable, answers 400 VALIDATION_FAILED with fields[] (#7474 residual, 17.0.0 GA) #8688 got.
  3. Pin the doc's claim mechanically (a catalog↔runtime status conformance check), which would have caught this and would catch the next one.

Related: #8688 / PR #8879 (the conditional stand-down) · #8772 (the lint that bounds the reachable shapes) · #7474 (the 2026-08-11 envelope ruling this family supersedes).

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions