Skip to content

fix(rest,service-settings,service-datasource)!: four more route modules emit the declared envelope, and the guard is shared (#3843) - #3972

Merged
os-zhuang merged 6 commits into
mainfrom
claude/envelope-drift-route-modules-v2zoky
Jul 30, 2026
Merged

fix(rest,service-settings,service-datasource)!: four more route modules emit the declared envelope, and the guard is shared (#3843)#3972
os-zhuang merged 6 commits into
mainfrom
claude/envelope-drift-route-modules-v2zoky

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Closes #3843. Takes the issue's own recommendation — option 3 then option 1: lift the guard first, then convert what it reports.

Merge order — done

objectstack-ai/objectui#2972 is merged (e651c93), and this branch now carries the .objectui-sha bump a136322 → e651c93, so the bundled Setup console ships with the envelope tolerance this change needs. Nothing further is gated.

The pin only moves two commits because main had already refreshed it to a136322. An earlier bump from this branch's stale base would have swept in eleven unrelated frontend commits including two breaking ones — caught and reverted before pushing.

The four modules

Module before now
service-settings/settings-routes.ts nested error, no success on any of 5 bodies full envelope
service-datasource/admin-routes.ts { error: '<string>' }, message a sibling of error full envelope
rest/external-datasource-routes.ts { error: '<string>' } + a private ok full envelope
rest/package-routes.ts 3 of 16 bodies had success; 2 failures had no error at all full envelope

Every body now goes through a sendOk / sendError pair. Success payloads keep their keys and move under data; error bodies become { success: false, error: { code, message } }, so body.error.message finally reads the service's message instead of undefined — the asymmetry #3675 opened on, still live two issues later.

Two failures that never said why now do: DELETE /api/v1/packages/:id answered a bare { success: false } and a bare { success: false, failed, cleanups }. They are now PACKAGE_DELETE_FAILED / PACKAGE_DELETE_PARTIAL, with the per-item arrays under error.details.

Codes: ADR-0112, not carried over

Revised. The first two pushes deliberately carried the nine lowercase codes over untouched, on the stated grounds that #3841 had not picked a vocabulary yet. It has — ADR-0112 batch 1 (#3988) landed while this was in review, along with #3842. ApiErrorSchema.code is now the closed ErrorCode union rather than z.string(), so those codes failed schema parse and took 7 of this PR's own conformance assertions with them. Re-spelled accordingly.

Generic conditions reuse the standard catalog rather than becoming registered synonyms of it, which is what the ledger asks for:

datasource_admin_unavailable  → SERVICE_UNAVAILABLE      (standard)
external_service_unavailable  → SERVICE_UNAVAILABLE      (standard)
not_found / PACKAGE_NOT_FOUND → RESOURCE_NOT_FOUND       (standard)
PUBLISH_FIELDS_MISSING        → MISSING_REQUIRED_FIELD   (standard)
INTERNAL                      → INTERNAL_ERROR           (standard)
datasource_admin_error        → DATASOURCE_ADMIN_ERROR   (registered)
external_import_error         → EXTERNAL_IMPORT_ERROR    (registered)
PUBLISH_MANIFEST_INVALID      → PACKAGE_MANIFEST_INVALID (registered)
PUBLISH_FAILED                → PACKAGE_PUBLISH_FAILED   (registered)
PACKAGE_DELETE_PARTIAL / PACKAGE_DELETE_FAILED / SETTINGS_ACTION_FAILED (registered)

Which service is unavailable is carried by message. The seven registered codes are added to ERROR_CODE_LEDGER under their owning packages, including a new @objectstack/service-datasource entry.

One deliberate non-change

POST /external/validate keeps its ok. Unlike the { ok: true, key } #3689 retired from storage — a private second word for success — this ok is a computed verdict over the federated objects (results.every(r => r.ok)). The request can succeed while the verdict is false, so they are not the same field; it moves inside data rather than being dropped. The guard's private-success-word rule is scoped to ok: literals for exactly this reason, and a test pins both flags disagreeing.

The guard — repo-wide, not per-package

scripts/check-route-envelope.mjs + pnpm check:route-envelope, wired into lint.yml beside the sibling check:* guards, which is what this repo's guard convention actually is.

Revised after review. The first push made this a workspace package, @objectstack/route-envelope-conformance. That was over-engineering — a whole package, build node, tsconfig and five devDependencies for one ~150-line pure function — and its name broke the repo's one naming rule: it was the only package in 40+ whose directory name did not match its package name. An ESLint rule was the other candidate but fits worse than it looks: this repo has zero custom ESLint plugins (eslint-rules/ is an objectui pattern) and no root vitest to host a rule's own tests, so it would have meant inventing two pieces of infrastructure instead of using the nine-precedent one.

The load-bearing assertion is structural rather than per-route: it counts the response write sites per module. When every body goes through the builder pair that count is fixed at two and does not grow with the route list — so a future route that hand-rolls a body fails the guard. That is the coverage a driven-body test can never give, since it can only drive the routes that existed the day it was written.

Going repo-wide is the substantive part, not the tidying. A per-package scan — which is what all three predecessors were — structurally cannot notice a module nobody thought to convert. Running it across the repo found two immediately, neither in #3843's hand survey:

Three states are declared deliberately — conformant / ratcheted / exempt — the honest classification ADR-0049 asks for. A route module the scan finds but the table does not declare is an error, never a default: applying 2 / 1 / 1 to an unknown module would let a new one pass by coincidence.

It parses the TS AST instead of matching regexes, which fixed two real bugs the copies had:

  1. They stripped comments with String.replace, whose line-comment pattern also ate // inside string literals and truncated the rest of that line — response writes included. A guard that under-counts call sites passes while drift ships, which is precisely the failure mode this issue is about.
  2. .json( does not mean "write a response". hmr-routes.ts calls c.req.json() twice to READ a request body; a textual count reports that as two unenveloped responses.

Comments and literals are not AST tokens, and request-vs-response is a property of the callee, so both disappear and the 110-line tokenizer is gone. The script carries a --self-test pinning each case — the sibling guards have none, but both of these bugs survived a review of the regex version.

The i18n ratchet, stated rather than hidden. i18n-service-plugin.ts is declared at responses: 5, ok: 4, err: 1 pointing at #3973. Its error half is consolidated (#3675), but each of its four read routes builds { success: true, data } inline. Those bodies are correct — not envelope drift — but an unconsolidated builder is a weaker guard. The numbers pin today's structure and drop to 2 / 1 / 1 when #3973 lands.

Consumers

objectui's packages readers were already tolerant (payload?.data ?? payload); its datasource page and the generic type: 'api' action runner now unwrap the envelope and read error.message — the latter previously toasted [object Object] for any nested error. Merged as objectstack-ai/objectui#2972 and pinned here.

SDK methods go through unwrapResponse, which already returns body.data when the flag is present, so packages.list() and datasources.external.* resolve to exactly the object they always did.

Verification (after merging main + full rebuild)

suite result
pnpm check:route-envelope 8 modules — 5 conformant, 2 ratcheted, 1 exempt; self-test green
pnpm check:console-sha pin consistent
pnpm lint clean
@objectstack/spec 6886 passed (265 files) — includes the ADR-0112 ledger guards
@objectstack/rest 472 passed (33 files)
@objectstack/service-settings 189 passed (13 files)
@objectstack/service-datasource 151 passed (10 files)
@objectstack/service-storage 220 passed (18 files)
@objectstack/service-i18n 62 passed (5 files)

One note on method, since it nearly produced a wrong bug report: a stale @objectstack/core dist made rest-meta-auth.test.ts read 'unauthenticated' where ADR-0112 had already changed the source to 'UNAUTHENTICATED'. That looked like a pre-existing break on main until a full pnpm build cleared it. The numbers above are post-rebuild.

Follow-ups filed

…es emit the declared envelope, and the guard is shared (#3843)

#3675 and #3689 moved service-storage and service-i18n onto BaseResponseSchema.
Each scoped itself to one service, and neither asked whether the same drift
existed elsewhere. It did — in four more modules, two of them carrying the
*older* shape #3675 had already declared wrong:

  settings-routes.ts              nested error, no `success` on any of 5 bodies
  admin-routes.ts                 { error: '<string>' }, `message` a SIBLING
  external-datasource-routes.ts   { error: '<string>' } + a private `ok`
  package-routes.ts               3 of 16 bodies had the flag; 2 failures had
                                  no `error` at all

All four now build every body through a sendOk / sendError pair. Success
payloads keep their keys and move under `data`; error bodies become
{ success: false, error: { code, message } }, so `body.error.message` finally
reads the service's message instead of `undefined` — the asymmetry #3675
opened on.

Codes are CARRIED OVER, not renamed: admin-routes and
external-datasource-routes keep their lowercase snake codes so #3841 can pick
one vocabulary for all ~240 at once. Only package-routes needed minted codes,
because its `error` strings were human messages with no code to carry.

POST /external/validate keeps its `ok`: unlike the `{ ok: true, key }` #3689
retired, that one is a computed verdict over the federated objects, and the
request can succeed while the verdict is false.

The guard is shared now, not copied. New private
@objectstack/route-envelope-conformance exports one pure
checkRouteEnvelope(source) => finding[]. Its load-bearing assertion is
structural rather than per-route — it COUNTS the `.json(` call sites, which
stays fixed at the number of builders no matter how many routes exist, so a
future route that hand-rolls a body fails the guard. That is coverage a driven
test cannot give. The scan existed three times as an open-coded regex block;
all three now delegate to it, as do the four new per-module suites.

The shared version also fixes a real bug in those copies: they stripped
comments with String.replace, which also ate `//` inside string literals and
truncated the rest of that line, `.json(` calls included. A guard that
under-counts call sites passes while drift ships. The shared scanner tokenizes
and pins that case in its own suite.

One ratchet, stated rather than hidden: i18n-service-plugin.ts is declared at
jsonCallSites: 5, successBuilders: 4. Its error half is consolidated, but its
four read routes build correct envelopes inline. Those numbers pin today's
structure (a new inline body fails) and drop to 2/1 on consolidation.

Consumers were taught both shapes first so the repos are not coupled by merge
order — objectui's packages readers were already tolerant; its datasource page
and the generic `type: 'api'` action runner now unwrap the envelope and read
error.message (the latter previously toasted "[object Object]").

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

vercel Bot commented Jul 30, 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 Jul 30, 2026 2:21am

Request Review

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/rest, packages/services, @objectstack/spec.

107 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/rest, @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/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 packages/services, @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 packages/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/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 packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/audit-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/services, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/settings-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.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/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/rest, packages/services, @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/i18n-standard.mdx (via packages/rest, packages/services, @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/kernel/runtime-capabilities.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/releases/implementation-status.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/rest, @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/ui/actions.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/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)

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.

…eck script (#3843 review)

Review feedback: a whole workspace package for one ~150-line pure function is
over-engineering, and its name broke the repo's one naming rule.

Both points stand. `@objectstack/route-envelope-conformance` was also the only
package in 40+ whose directory name did not match its package name (the sole
prior exception, objectstack-vscode, is a VS Code extension requirement).

Replaced by `scripts/check-route-envelope.mjs` + `pnpm check:route-envelope`,
wired into lint.yml beside the nine sibling `check:*` guards — which is what
this repo's guard convention actually is. framework has zero custom ESLint
plugins (objectui has the eslint-rules/ precedent, framework does not) and no
root vitest for a rule's own tests, so the check-script form fits without
inventing new infrastructure. Net: one workspace package, one build node, five
devDependencies and one tsconfig removed; one script added.

Going repo-wide is not just tidier — it is a stronger guard, and it proved that
immediately by finding two modules #3843's hand survey missed:

  * plugin-sharing/share-link-routes.ts — the FIFTH drifting module. No body
    carries `success`, and one answers `{ ok: true }`, the private second word
    #3689 retired from storage. Filed #3983, pinned by the guard's ratchet.
    (#3675's own changeset had cited this file as an example of the good shape;
    that was true of its `error` object and nothing else.)
  * metadata/routes/hmr-routes.ts — declared EXEMPT with a reason (dev-only SSE
    endpoint, not on the SDK surface), not skipped.

A per-package scan structurally cannot notice a module nobody thought to
convert. Three states are declared deliberately — conformant / ratcheted /
exempt — the honest classification ADR-0049 asks for; a module the scan finds
but the table does not declare is an ERROR, never a default, since applying the
2/1/1 default to an unknown module would let a new one pass by coincidence.

The scan now parses the TypeScript AST instead of matching regexes, which fixes
two real bugs the three copied blocks had:

  1. comments were stripped with String.replace, whose line-comment pattern
     also ate `//` inside string literals and truncated the rest of that line —
     response writes included. A guard that under-counts passes while drift
     ships.
  2. `.json(` does not mean "write a response". hmr-routes.ts calls
     `c.req.json()` twice to READ a request body; a textual count reports that
     as two unenveloped responses.

Comments and literals are not AST tokens and request-vs-response is a property
of the callee, so both disappear and the 110-line tokenizer is gone. The script
carries a --self-test pinning each case: the nine sibling guards have none, but
both of these bugs survived a review of the regex version.

The driven-body half stays in each module's suite, where it belongs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CYbS3kS8xzsHNXFTzp4e2z
@github-actions github-actions Bot added the ci/cd label Jul 30, 2026
claude added 2 commits July 30, 2026 01:55
…nd refresh the console pin (#3843)

Merging main brought in ADR-0112 batch 1 (#3841 (#3988)) and #3842, both of
which landed while this PR was in review. Two of this PR's stated decisions were
premised on #3841 being UNDECIDED, so they no longer hold.

`ApiErrorSchema.code` is now the closed `ErrorCode` union, not `z.string()`, so
the nine lowercase codes this PR deliberately carried over failed schema parse —
which failed its own conformance suites (6 in service-datasource, 1 in
service-settings). Re-spelled per ADR-0112, with generic conditions going to the
STANDARD catalog rather than becoming registered synonyms of it, which is what
the ledger asks for:

  datasource_admin_unavailable  → SERVICE_UNAVAILABLE      (standard)
  external_service_unavailable  → SERVICE_UNAVAILABLE      (standard)
  not_found / PACKAGE_NOT_FOUND → RESOURCE_NOT_FOUND       (standard)
  PUBLISH_FIELDS_MISSING        → MISSING_REQUIRED_FIELD   (standard)
  INTERNAL                      → INTERNAL_ERROR           (standard)
  datasource_admin_error        → DATASOURCE_ADMIN_ERROR   (registered)
  external_import_error         → EXTERNAL_IMPORT_ERROR    (registered)
  PUBLISH_MANIFEST_INVALID      → PACKAGE_MANIFEST_INVALID (registered)
  PUBLISH_FAILED                → PACKAGE_PUBLISH_FAILED   (registered)
  PACKAGE_DELETE_PARTIAL / PACKAGE_DELETE_FAILED / SETTINGS_ACTION_FAILED

Which service is unavailable is carried by `message` — the ledger explicitly
asks generic conditions to reuse the catalog instead of registering a per-service
503. The seven registered codes go into ERROR_CODE_LEDGER under their owning
packages, including a new @objectstack/service-datasource entry.

Also bumps .objectui-sha a136322 → e651c93 now that objectui#2972 has merged, so
the bundled Setup console carries the envelope tolerance this change needs. The
pin only moves two commits because main had already refreshed it to a136322 —
an earlier attempt from this branch's stale base would have swept in eleven
unrelated frontend commits including two breaking ones.

The route-envelope guard, the conformance suites and their doc comments are
updated to match. Guard: 8 modules, 5 conformant / 2 ratcheted / 1 exempt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CYbS3kS8xzsHNXFTzp4e2z
@os-zhuang
os-zhuang marked this pull request as ready for review July 30, 2026 02:09
claude added 2 commits July 30, 2026 02:13
…es (#3843)

CI's TypeScript Type Check job runs `check:docs`, which failed: registering seven
codes in ERROR_CODE_LEDGER widens the `ErrorCode` union, and every generated
reference page that renders an `ApiErrorSchema`-shaped field embeds that union
inline. content/docs/references/** is AUTO-GEN (AGENTS.md) — regenerated with
`gen:schema && gen:docs` rather than hand-edited.

The diff is exactly this change: error-code-ledger.mdx gains the seven codes, and
ten pages re-render the widened union in place. 251 generated files back in sync.

I had run lint and the package test suites but not this job locally — the gate is
in the typecheck job rather than the test job, which is easy to miss.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CYbS3kS8xzsHNXFTzp4e2z
@os-zhuang
os-zhuang merged commit d1c8298 into main Jul 30, 2026
19 checks passed
@os-zhuang
os-zhuang deleted the claude/envelope-drift-route-modules-v2zoky branch July 30, 2026 02:35
os-zhuang added a commit that referenced this pull request Jul 30, 2026
…ode vocabulary (#3843 follow-up) (#4009)

Comment-only follow-up to #3972 (#3843). No behaviour change.

Two sentences in package-envelope.conformance.test.ts's header were written while
#3841 was still undecided, and #3843 landed after ADR-0112 settled it:

  - "why #3841 still owns the vocabulary" — it does not; ADR-0112 (#3988) closed
    it and ApiErrorSchema.code is a closed union now.
  - "this module needed MINTED codes" — half true. It had nothing to carry over
    (its `error` strings were human messages), but only four of the codes here are
    registered; the three generic conditions reuse the standard catalog, which is
    what the ledger asks for.

Rewritten to state the actual split and why it makes the assertions below
load-bearing: an unregistered code fails parse, so BaseResponseSchema.safeParse is
what would catch an invented one. package-routes.ts already carried the corrected
note; this was the test file missed beside it.

Carries an empty changeset — the PR gate requires one, and this releases nothing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Envelope drift is not just service-storage: four more route modules emit bare bodies, two of them the pre-#3675 { error: '<string>' }

2 participants