Skip to content

feat(spec): mechanical standard-synonym admission gate on the error-code ledger, with recorded waivers - #8450

Merged
qq9340100 merged 5 commits into
mainfrom
claude/issue-8211-ledger-synonym-gate
Aug 13, 2026
Merged

feat(spec): mechanical standard-synonym admission gate on the error-code ledger, with recorded waivers#8450
qq9340100 merged 5 commits into
mainfrom
claude/issue-8211-ledger-synonym-gate

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Fixes#8211

Implements the adjudicated option C (2026-08-12): the ledger header's "if the condition is generic, use the standard catalog instead of registering a synonym" rule was prose only — the admission gate rejected a code only when it was literally a StandardErrorCode member, which is why four semantic synonyms accumulated without anyone deciding to allow them. This PR makes the rule mechanical, with recorded waivers. No wire value changes; nothing unregisters — consolidation (option B) stays deferred, undecided, until a specific code has a measured victim.

The mechanism (closed, mechanical — no NLP fuzz)

standardSynonymOf(code) in packages/spec/src/api/error-code-ledger.zod.ts flags a registered code as a semantic synonym of a standard-catalog member on two prongs, each reproducible from the file alone:

  1. Reason-phrase alias — the code is the SCREAMING_SNAKE spelling of an HTTP reason phrase whose status HttpStatusErrorCodeMap maps to a standard member (FORBIDDEN → 403 → PERMISSION_DENIED). Judged against the explicit map only, never the bucket fallback — so PAYLOAD_TOO_LARGE (413, no covering member) is deliberately NOT flagged.
  2. Token subset — every _-token of the code appears in one standard member's name (CONFLICTRESOURCE_CONFLICT, INTERNALINTERNAL_ERROR): the code says nothing the member's own name does not already say. Domain-prefixed codes (FORM_NOT_FOUND) carry a token no member has and pass — exactly the shape the registration instructions endorse.

standardSynonymViolations(ledger, waivers) reports every unwaived synonym registration; the admission suite in error-code-ledger.test.ts asserts it is empty on the real ledger. A waiver (STANDARD_SYNONYM_WAIVERS, schema StandardSynonymWaiverSchema) admits exactly the (code, shadows) pair it records and must carry a non-empty reason — admission becomes a decision on the record, never drift. Stale waivers (code unregistered, or no longer detected as a synonym of the named member) fail the suite.

The hard pin the cli seat demanded

A detector that only passes on today's tree is the exact failure mode this card closes — the prose rule also "passed" on every tree. The suite pins that the same function that gates the real ledger goes red when a newly-introduced synonym lands, once per prong (TOO_MANY_REQUESTSRATE_LIMIT_EXCEEDED; RATE_LIMITRATE_LIMIT_EXCEEDED), plus the waiver-admits / wrong-member-does-not-admit pair, plus the reverse direction (dropping the FORBIDDEN waiver reddens all three registering packages).

Grandfathered: the ruled four, plus one the detector surfaced

Measured over the full ledger, the detector flags five existing codes and zero false positives: the four the card named (CONFLICT, NOT_FOUND, FORBIDDEN, INTERNAL) plus UNAUTHORIZED (401 reason-phrase spelling of UNAUTHENTICATED's condition, registered by @objectstack/rest). Narrowing the criterion to exclude it would have been tuning the detector to the ruling's list — the failure mode above wearing a different hat. It is grandfathered by a fifth waiver with the same rationale (wire value kept; consolidation deferred); flagged for the veto window in the round report. #8211's option-B question for it, as for the other four, remains open.

One rule, two doors (#8087 — remains open, not addressed here)

The ledger header now states the shared rule once: this gate is the admission door; the dispatcher door is ruled separately (#8087, option B-as-a-gate) and its gate is not yet built — until it lands, resolveThrownHttpError (@objectstack/types, PR #8088) carries code narrowed / declaredCode verbatim across the gap. Wording in the header cross-references both.

Fork-clause check

No wire-visible change anywhere: the ledger rows, REGISTERED_ERROR_CODES, and ErrorCode are byte-identical in meaning; all additions are new exports (standardSynonymOf, standardSynonymViolations, StandardSynonymWaiverSchema, StandardSynonymWaiver, STANDARD_SYNONYM_WAIVERS) — hence a minor changeset on @objectstack/spec. The row shape is untouched (waivers are a separate table), so no ledger consumer can be affected; out-of-package references to the ledger in source are comments only.

ADR-0122: StandardSynonymWaiver is z.input; isomorphism pinned as Iso838 in type-alias-convention.pin.test.ts (824 → 825, measured: no defaults/transforms anywhere in the tree).

Verification

Full @objectstack/spec suite + typecheck + check:generated run serialized behind the shared verification lock; gate results recorded in the dev report on #8211. Light gates green locally: check:adr-anchors, check:changeset-gate-self-tests, check:cross-package-test-inputs, check:docs-audit-scope, check:error-code-casing, check:merge-driver, check:spec-parsed-alias, check:type-source-resolution, check:nul-bytes, check:query-options-erasure, check:type-check-coverage, check:release-body, check:objectui-changeset, changeset-no-major.


Generated by Claude Code

…ode ledger (#8211)
Option C per the #8211 adjudication (2026-08-12): the ledger header's
'use the standard catalog instead of registering a synonym' rule gets
teeth. standardSynonymOf() is a closed two-prong detector (HTTP
reason-phrase alias via HttpStatusErrorCodeMap; token-subset of a
standard member's name); standardSynonymViolations() is the admission
gate's engine; STANDARD_SYNONYM_WAIVERS grandfathers the pre-gate
synonyms (CONFLICT, NOT_FOUND, FORBIDDEN, INTERNAL, and UNAUTHORIZED
which the detector surfaced) with recorded reasons naming the member
each shadows. No wire change; consolidation (option B) stays deferred.
The suite pins that the gate rejects a newly-introduced synonym on
both prongs — a detector that only passes on today's tree is the
failure mode this closes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012MNV7ZSCjNfA38eDCjsXQL
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 13, 2026 4:46pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via @objectstack/spec)
  • content/docs/automation/connectors.mdx(via @objectstack/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/cli.mdx(via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via @objectstack/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/kernel/services.mdx(via @objectstack/spec)
  • content/docs/permissions/authorization.mdx(via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/rls.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/permissions/system-context.mdx(via packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/apps.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)
  • content/docs/ui/translations.mdx(via @objectstack/spec)
  • content/docs/ui/views.mdx(via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx(via @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v16.mdx(via @objectstack/spec)
  • content/docs/releases/v17.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 13, 2026
…nd strictness-ledger counts for the synonym-gate exports (#8211)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012MNV7ZSCjNfA38eDCjsXQL
…relay after #8457)
Discharges the os-regen deferral from the origin/main merge: api-surface,
export-origins, reference docs, strictness-ledger counts re-derived on the
merged base so the union carries both #8457's entries and the #8211
synonym-gate exports. gen:openapi re-run after the chain per the script's
warning. Sibling assertions green (datasource-config-placeholder-refused
registered; placeholderFree/containsUnresolvedPlaceholder body present).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012MNV7ZSCjNfA38eDCjsXQL
@qq9340100
qq9340100 marked this pull request as ready for review August 13, 2026 17:28
@qq9340100
qq9340100 added this pull request to the merge queueAug 13, 2026
Merged via the queue into main with commit 36d90fcAug 13, 2026
27 checks passed
@qq9340100
qq9340100 deleted the claude/issue-8211-ledger-synonym-gate branch August 13, 2026 17:44
qq9340100 pushed a commit that referenced this pull request Aug 13, 2026
…ins to Iso839-Iso844 (#7751)
Main's #8211 (PR #8450) took Iso838; the six object-* pins renumber to the
next free ids on the merged tree (ids are claims about pins, not positions —
renumbering unmerged pins is legal). Pin count 825 + 6 = 831, asserted by the
count case, not arithmetic.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012MNV7ZSCjNfA38eDCjsXQL
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

2 participants

@qq9340100@claude