Skip to content

feat(spec): the error-code ledger states its federation contract; makeApiErrorSchema(extraCodes) (#4805) - #7110

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4805-federated-error-ledger
Aug 9, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-4805-federated-error-ledger

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Fixes #4805

The ruling this implements

Maintainer ruling on #4805, 2026-08-03, re-confirmed 2026-08-09 — quoted verbatim, untranslated:

✅ 维护者裁定:同意联邦式账本 —— cloud 条目在 cloud 仓维护

裁定原文:「4805 同意」(对上一条分析的方案确认)。据此:

本 issue 的剩余范围收敛为 objectstack 侧三件小事(供主 backlog PM 派发)

  1. packages/spec/src/api/error-code-ledger.zod.ts 文件头:把「本账本只登记 framework 包;下游产品仓维护自己的账本并自行组合校验」写成显式约束
  2. packages/spec/src/api/contract.zod.ts:19 的描述句修订为「StandardErrorCode ∪ 服务方登记的账本」;
  3. (可选,不阻塞)导出 makeApiErrorSchema(extraCodes) 工厂,让下游单次 parse 替代两步断言。

三件都是文档/附加性质,无破坏性。

The cloud-side ledger and the 36-code migration are the cloud shard's work under cloud#944 — not in this PR. No ERROR_CODE_LEDGER entry is added, removed or changed, and no new error code is introduced.

The three items

# 落点 (landing site) before after
1 packages/spec/src/api/error-code-ledger.zod.ts — file header Every owner key happened to be a framework package. The constraint existed only as a property of the list: a reader had to scan 23 package names and infer it. #4805 was filed by someone who did not, and could not have been expected to. A stated rule under "Scope: THIS ledger registers framework packages only (#4805)": a downstream product repo does not register here — it keeps its own ledger and composes the validation (envelopeViolations for shape + code ∈ StandardErrorCode ∪ (its own ledger) for vocabulary, or one makeApiErrorSchema parse). Plus why the ruling went this way (an Apache-2.0 spec should not enumerate a closed-source product's billing/plan states under package names absent from this distribution; per-code cross-repo friction pushes authors toward reusing a semantically wrong code, which is less visible than inventing one), and the corollary for this file: an owner key for a package not published from this repo is out of scope by construction.
2 packages/spec/src/api/contract.zod.tsApiErrorSchema.code description (was :19) 'Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ ERROR_CODE_LEDGER)', with a JSDoc saying "a code registered in ERROR_CODE_LEDGER". 'Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)', with the JSDoc naming the federation and pointing at makeApiErrorSchema. Description only — the parsed vocabulary is untouched.
3 packages/spec/src/api/contract.zod.ts — new export A downstream repo checked a body with two separate assertions: shape, then a hand-written membership test over its own ledger. makeApiErrorSchema(extraCodes) — the same envelope with StandardErrorCode ∪ extraCodes as the code vocabulary, so the suite gets one verdict with a Zod issue path. The shape is ApiErrorSchema's, reused via .extend rather than restated, so a field added to the base envelope reaches every downstream ledger with it. ERROR_CODE_LEDGER's own entries are deliberately not folded in: a service that also relays framework-produced errors says so explicitly (makeApiErrorSchema([...REGISTERED_ERROR_CODES, ...MY_CODES])).
const CloudApiError = makeApiErrorSchema(CLOUD_ERROR_CODES);
CloudApiError.safeParse(body);   // shape + vocabulary, one parse

Additive throughout. Federating the ledger does not open the vocabulary; it moves where the other half of it is declared.

Verification

Premise re-located on current main before editing. Both anchors exist as the ruling describes: the ledger header's two-tier prose ran straight into the "Registering a new code" section with no scope statement anywhere, and ApiErrorSchema.code sat at contract.zod.ts:19 composing its vocabulary from ErrorCode (error-code-ledger.zod.tsz.enum([...StandardErrorCode.options, ...REGISTERED_ERROR_CODES])). The factory reuses that same composition rule with the caller's list in place of the framework ledger.

Reverse verification — direction predicted before measuring. Revert the factory's union to the standard catalog alone ([...StandardErrorCode.options], extraCodes ignored) and the two pins that exercise an extra code must flip red, while the standard-catalog pin and the rejection pin stay green — those two do not depend on the union:

pin predicted measured
accepts the standard catalog both ways pass pass ✅
accepts an extra code ONLY through the factory FAIL FAIL
rejects an unregistered code both ways pass pass ✅
reuses the base envelope shape rather than restating it (parses an extra code) FAIL FAIL

Tests 2 failed | 48 passed (50) under the reverted union; 50 passed restored. The rejection pins assert the Zod issue path (['code']) and code (invalid_value), so they cannot pass on an unrelated failure.

ApiErrorSchema behaviour byte-identical. Its own tests were not touched, and the four API error suites run unchanged and green (contract, error-code-ledger, errors, envelope-violations — 99 passed). Only its code description changed, which is item 2 and shows up exactly where a description should: one line of generated reference docs.

Gates

Run one by one from .github/workflows/lint.yml, both jobs:

gate result
pnpm lint (ESLint, --no-inline-config) ✅ pass
pnpm check:nul-bytes (incl. self-test) ✅ pass
pnpm check:error-code-casing (incl. self-test) ✅ 0 lowercase codes in 3348 files
pnpm --filter @objectstack/spec exec tsc --noEmit ✅ pass
pnpm --filter @objectstack/spec typecheck (src + scripts + tests) ✅ pass
pnpm --filter @objectstack/spec check:generated --reconcile-only ✅ 20 check: + 14 gen: all classified
check:export-origins (self-test + check) ✅ 4992 exports / 16 entry points resolve as recorded
check:api-surface ✅ pass
check:authorable-surface ✅ pass
check:docs ✅ pass
check:skill-docs, check:spec-changes, check:upgrade-guide ✅ pass
check:exported-any, check:dual-source-exports ✅ pass
pnpm check:spec-parsed-alias (ADR-0122) ✅ pass (no new z.infer alias)
node scripts/check-adr-0087-registration.mjs --base origin/main ✅ nothing owed — no declared-breaking changeset (analog: #7050's additive-export changeset)
pnpm --filter @objectstack/spec test ✅ 355 files / 9275 tests passed

Regenerated snapshots committed — item 3 adds a public export, and check:export-origins is new in the Type Check job today (a missed snapshot is what red-lit PR #7103 on exactly this gate):

  • packages/spec/api-surface/api.jsongen:api-surface (needs the built dist/*.d.ts; ran after pnpm --filter @objectstack/spec build)
  • packages/spec/export-origins/api.jsongen:export-origins (named by check:generated --reconcile-only)
  • content/docs/references/api/{contract,error-code-ledger}.mdxgen:schema && gen:docs

One changeset, @objectstack/spec minor (the factory ships). No content/docs/releases/ edit.

Findings

  • The factory's vocabulary is StandardErrorCode ∪ extraCodes, not ErrorCode ∪ extraCodes, matching the ruling's formula (code ∈ StandardErrorCode ∪ CLOUD_ERROR_CODE_LEDGER) rather than quietly widening it. A downstream that also relays framework-package codes has to say so — makeApiErrorSchema([...REGISTERED_ERROR_CODES, ...MY_CODES]) — and that spelling is documented on the factory. Worth a reviewer's eye: it is the one design decision in this PR that could reasonably have gone the other way, and it is the direction that keeps a downstream ledger from silently inheriting 200-odd framework codes it never audited.
  • Prose in packages/types/src/response-envelope.ts and packages/metadata-protocol/src/protocol.ts still says StandardErrorCode ∪ ERROR_CODE_LEDGER. Left alone deliberately — both describe the framework side, where that union is still exactly right; ErrorCode is unchanged.

…eApiErrorSchema(extraCodes) (#4805)

The three objectstack-side items of the federated ERROR_CODE_LEDGER ruling
(#4805, 2026-08-03, re-confirmed 2026-08-09).

1. `error-code-ledger.zod.ts` header: "this ledger registers framework
   packages only" becomes a stated RULE — with what a downstream product repo
   does instead (its own ledger, composed as `envelopeViolations` for shape +
   `code ∈ StandardErrorCode ∪ <its own ledger>` for vocabulary), and why the
   ruling went this way (a commercial vocabulary does not belong in an
   Apache-2.0 spec; per-code cross-repo friction breeds semantic reuse).
   Previously inferable only by scanning the package names.

2. `ApiErrorSchema.code`'s description: `StandardErrorCode ∪ ERROR_CODE_LEDGER`
   -> `StandardErrorCode` ∪ the ledger the serving side registers, naming
   `ERROR_CODE_LEDGER` as the framework packages' one. Description only.

3. New export `makeApiErrorSchema(extraCodes)`: the same envelope with
   `StandardErrorCode ∪ extraCodes` as the code vocabulary, so a downstream
   conformance suite gets one parse with a Zod issue path instead of a shape
   assertion plus a hand-written membership test. The envelope shape is
   `ApiErrorSchema`'s, reused rather than restated. Additive: `ApiErrorSchema`
   parses exactly what it parsed before.

Regenerated: api-surface/api.json, export-origins/api.json (the new export),
content/docs/references/api/{contract,error-code-ledger}.mdx (gen:schema &&
gen:docs).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GmC5HAjupdHqdB9bzsYzPg
@vercel

vercel Bot commented Aug 9, 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 9, 2026 4:51pm

Request Review

@github-actions github-actions Bot added the size/m label Aug 9, 2026
@github-actions

github-actions Bot commented Aug 9, 2026

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.

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 size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ERROR_CODE_LEDGER 没有任何 cloud 包条目 —— cloud 服务想要 service-specific error code 只能违反 ApiErrorSchema

1 participant