Skip to content

[finding] @objectstack/rest registers four generic synonyms the standard catalog already covers (CONFLICT, NOT_FOUND, FORBIDDEN, INTERNAL) — contract call, not a cleanup #8211

Description

@hotlong

Found while converging the record-sharing family onto the ADR-0112 D5 envelope (#8111, PR pending). Filing unassigned — this needs a maintainer contract ruling, not a sweep.

What was measured

respondSharingError's 409 arm emits code: 'CONFLICT'. That value was registered in neither StandardErrorCode (packages/spec/src/api/errors.zod.ts) nor ERROR_CODE_LEDGER (packages/spec/src/api/error-code-ledger.zod.ts), while ErrorCode — what ApiErrorSchema.code parses against — is exactly their union. So the 409 body could not have parsed as the declared envelope: ADR-0112's "silent fourth state", live on the wire and documented at content/docs/kernel/runtime-services/sharing-service.mdx.

The arm is live, not dormant: packages/plugins/plugin-sharing/src/sharing-service.ts:1042 throws it for revoke on a rule-materialised share.

#8111 registered the existing value under @objectstack/rest, which keeps the wire byte-identical (renaming it would change what clients read, and that card was explicitly not licensed to move a code VALUE).

The finding

Registering it is consistent with what that block already does, and that is the part worth a ruling. @objectstack/rest now registers four codes whose condition the standard catalog already covers generically:

ledger entry standard-catalog member for the same condition
CONFLICT RESOURCE_CONFLICT (409)
NOT_FOUND RESOURCE_NOT_FOUND (404)
FORBIDDEN PERMISSION_DENIED (403)
INTERNAL INTERNAL_ERROR (500)

The ledger's own header rule says the opposite should happen:

If the condition is generic (not found / permission / validation / rate limit), use the standard catalog instead of registering a synonym.

That rule is prose only. The mechanical admission gate (error-code-ledger.test.ts, "no registered code shadows the standard catalog") rejects only a code that is literally a StandardErrorCode member, so every synonym above passes — which is why four accumulated without anyone deciding to allow them.

Why this is a contract call and not a cleanup

Each of the four is on the wire today, so consolidating any of them onto the standard member is a breaking change for clients branching on err.code — the SDK surfaces it verbatim (packages/client/src/index.ts, error.code). The options are not equal-cost:

  • A — leave them registered. Zero client impact. Cost: the closed vocabulary carries two words per condition permanently, and the prose rule is dead letter, so the next author copies the pattern.
  • B — consolidate onto the standard catalog, one card per code, with a deprecation window. Cost: a real wire change per code, needing consumer sweeps across objectui and cloud.
  • C — keep them but make the rule mechanical (extend the admission gate to reject a semantic synonym of a standard member unless explicitly waived with a reason). Cost: one gate change; stops the fifth from landing without deciding the fate of the existing four.

Recommendation: C now, B only if a specific code proves confusing in practice. C is the option that makes the mistake structurally hard to repeat (the AI-authored-metadata axis) without paying a breaking change for vocabulary tidiness that no user is currently hitting — and it converts the header's prose into something enforced, which is what "declared = enforced" asks for. A is the status quo the prose already rejects, and B spends real client breakage on a problem that is presently cosmetic.

Not urgent: after #8111 every one of the four is registered, so nothing emits an unparseable body today. The exposure is future drift, which is why this is filed as a finding rather than queued.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions