Skip to content

fix(runtime,i18n)!: /i18n/locales answers in one shape, plus the success-envelope conformance gate that found it - #3870

Merged
os-zhuang merged 1 commit into
mainfrom
claude/gettranslationsrequest-namespace-keys-rv08r7
Jul 28, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/gettranslationsrequest-namespace-keys-rv08r7

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Follow-up to #3676 / #3833 / #3847 — the generalizable part of that trilogy, plus the fourth gap it immediately caught.

Why a gate, not another one-off fix

Those three defects were each a body that did not match the schema declaring it. All three survived a green suite, for one shared reason:

Every test asserted the emitted body against a hand-written literal. Comparing output to a literal proves the code does what the test author believed. It cannot prove the code does what the contract declares. Nothing had ever put the emitted value and the declared schema in the same assertion.

That is not three bugs, it is one missing check.

The gate

packages/runtime/src/i18n-success-envelope.conformance.test.ts — the missing success-path twin of service-i18n's error-envelope.conformance.test.ts, and the same pairing storage got in #3689 (success-envelope.conformance.test.ts, whose header notes i18n went through the error half in #3675 and never the success half).

Every /i18n success body is parsed against BaseResponseSchema and against the schema plugin-rest-api names for that route — responseSchema: 'GetLocalesResponseSchema', etc. — imported rather than restated. A body that parses is a body the SDK's published return type does not lie about.

It found a fourth gap on its first run

GET /i18n/locales passed getLocales()'s raw string[] straight through the dispatcher, while GetLocalesResponseSchema declares { code, label, isDefault }[] — and service-i18n, the other provider of this identical route, already emitted descriptors.

One endpoint, two shapes, decided by which plugin mounted it, with the dispatcher's form contradicting the SDK's own GetLocalesResponse type.

That is the same split #3833 found in the field-labels derivation, one route over, and for the same reason: two surfaces, one mapping, kept twice. So the mapping is now shared as toLocaleDescriptors in packages/spec/src/system/i18n-resolver.ts, next to resolveObjectFieldLabels, and both surfaces call it.

label is the locale code. No display-name source exists in the tree and the schema requires the field; inventing an ICU display-name table here would be a product decision, not an implementation detail.

Verified by reverting, not by passing

Same discipline as #3846. The fix was reverted and the suite confirmed to fail on it:

locales body does not match its declared schema:
  [{"expected":"object","code":"invalid_type","path":["locales",0],
    "message":"Invalid input: expected object, received string"}, …]
 Tests  3 failed | 3 passed (6)

A regression test never observed failing is just another assertion.

Five existing tests pinned the bare string[]. They now assert on .map(l => l.code) — the codes stay pinned, the shape is owned by the schema. That is the intended division: literals for values, schemas for shapes.

Verification

  • @objectstack/spec — 262 files / 6822 tests pass
  • @objectstack/runtime — 48 files / 702 tests pass
  • @objectstack/service-i18n — 5 files / 63 tests pass
  • Conformance suite confirmed failing against pre-fix code, passing after
  • check:api-surface — regenerated (0 breaking, toLocaleDescriptors + LocaleDescriptor added), re-verified clean
  • check:docs — 250 generated files in sync
  • @objectstack/client builds clean

Breaking

GET /i18n/locales served by the dispatcher now returns [{ code, label, isDefault }] instead of ['en', …]. Callers on the service-i18n mount already received this shape, and the SDK's published GetLocalesResponse type has always described it — this ends a divergence rather than starting one.

Worth generalizing

This suite covers one route family. The same check is mechanically available repo-wide today: plugin-rest-api.zod.ts already carries a responseSchema name on essentially every route (29 declarations across 28 handlers), so the route → declaring-schema mapping needed to drive it exists and is currently unused by any check.

Note the existing check:liveness gate does not cover this: it is registry-rooted over authorable metadata types, not API request/response schemas. That is exactly the blind spot all four gaps lived in.

Also swept while here, per #3833's follow-up: every remaining reader of a translation bundle (rest-server.ts:1681, the two i18n surfaces, the two adapters) is accounted for — no third consumer is still on the retired o. dialect.


Generated by Claude Code

…ccess-envelope conformance gate that found it

Follow-up to #3676 / #3833 / #3847. Those three were each a body that did not
match the schema declaring it, and each survived a green suite because every
test asserted the emitted body against a hand-written literal. Comparing output
to a literal proves the code does what the test author believed; it cannot prove
the code does what the contract declares. Nothing had ever put the emitted value
and the declared schema in the same assertion.

This adds that assertion as a suite — `i18n-success-envelope.conformance.test.ts`
in runtime, the missing success-path twin of service-i18n's
`error-envelope.conformance.test.ts` and the same pairing storage got in #3689.
Every `/i18n` success body is parsed against `BaseResponseSchema` and against the
schema `plugin-rest-api` names for that route, imported rather than restated.

It found a fourth gap on its first run. `GET /i18n/locales` passed
`getLocales()`'s raw `string[]` straight through the dispatcher, while
`GetLocalesResponseSchema` declares `{ code, label, isDefault }[]` — and
service-i18n, the OTHER provider of this identical route, already emitted
descriptors. One endpoint, two shapes, decided by which plugin mounted it, with
the dispatcher's form contradicting the SDK's own `GetLocalesResponse` type.

That is the same split #3833 found in the field-labels derivation, one route
over, and for the same reason: two surfaces, one mapping, kept twice. The mapping
is now shared as `toLocaleDescriptors` next to `resolveObjectFieldLabels`, and
both surfaces call it. `label` is the locale code — no display-name source exists
in the tree and the schema requires the field; inventing an ICU display-name
table here would be a product decision, not an implementation detail.

The gate was verified the way #3833's was: the fix was reverted and the suite
confirmed to FAIL on it ("expected object, received string" at locales[0]),
rather than merely passing once written. Five existing tests pinned the bare
`string[]`; they now assert on `.map(l => l.code)`, so the codes stay pinned
while the shape is owned by the schema.

BREAKING: `GET /i18n/locales` served by the dispatcher now returns
`[{ code, label, isDefault }]` instead of `['en', ...]`. Callers on the
service-i18n mount already received this shape, and the SDK's published
`GetLocalesResponse` type has always described it, so this ends a divergence
rather than starting one.

Worth generalizing beyond `/i18n`: `plugin-rest-api.zod.ts` already carries a
`responseSchema` name on essentially every route (29 declarations across 28
handlers), so the route -> declaring-schema mapping needed to run this check
repo-wide exists today and is unused.

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

vercel Bot commented Jul 28, 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 28, 2026 1:14pm

Request Review

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:system tests tooling labels Jul 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

113 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 packages/runtime, @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/runtime, @objectstack/spec)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime, 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/runtime, packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime, @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/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • 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/runtime, @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/authentication.mdx (via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime, @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/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/runtime, 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/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/services, @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime, @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/runtime, @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/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.

@os-zhuang
os-zhuang marked this pull request as ready for review July 28, 2026 13:31
@os-zhuang
os-zhuang merged commit 41642b0 into main Jul 28, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/gettranslationsrequest-namespace-keys-rv08r7 branch July 28, 2026 13:32
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…mmits that decided them (objectstack-ai#20708)

Part of objectstack-ai#20596
Clause-②: no

## What changed

This is the sixth stage of the `domain:services` lane of the
dead-citation sweep. It covers
`packages/services/service-storage/src/**` and nothing else. By the
seat's census at the claim (`5896394242`), it is the largest package in
the lane that no in-flight work holds. Later stages cover the other
packages, so this PR says `Part of` and the card stays open.

Every comment or docblock site in scope that cited a tracker number
answering 404 has been rewritten in ruling C+D's form C (comment
5749154545 on objectstack-ai#19123), by the method of stages 1 to 5 (PR objectstack-ai#20609 as
`422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR
objectstack-ai#20658 as `9a4b2bb38`, PR objectstack-ai#20693 as `0e9ad74fb`). That is **42 sites on
41 lines in 15 files, covering 8 numbers**:

- 27 census sites (every census site this package has);
- 15 sites in test comments, which the census defers.

Each rewritten line now cites the commit in `origin/main` history that
decided what the line describes, and says in its own words what was
decided: **7 distinct shas**. No number in this package has an ADR or
ruling record of its own in the repository (a grep of `docs/adr/` for
all 8 finds none, and the repository keeps no other ruling-record file
for them), so every anchor is a commit, per ruling C's order. No number
was dropped.

Only comments changed. Every touched source file keeps its line count
(43 lines out, 43 in, over 15 files), so no line citation into these
files moves. 2 of those 43 lines hold no dead citation; they are reflow,
listed under Wordings below. No code token moves (see the guard below).

**No citation number is added.** Every tracker number on an added line
was already on the line it replaces: `objectstack-ai#12069`
(`translations/index.ts:29`), `objectstack-ai#10246` (`storage-service-plugin.ts:392`)
and the cross-repo `cloud#1395`
(`backfill-sys-file-organizations.ts:86`). Over the whole diff, added
minus removed is 0 or negative for every number, and no number is new to
the diff. No PR number stands on an added line.

Eleven dead sites are left on purpose, all of them test titles (see the
list below).

One more file: a `patch` changeset for `@objectstack/service-storage`,
because the rewritten docblocks and inline comments ship (see Changeset
below).

## Census: `service-storage`, before and after

**Instrument (A1).** The gate's own `node
scripts/check-issue-citations.mjs --census --json`, read-only and
unchanged. The count below is its `allocated-but-absent` findings under
`packages/services/service-storage/`. Each run counts as a reading only
because its board frontier equals the newest issue number, read by a
separate request just before and just after the run.

| reading | tree | board | whole-repo `allocated-but-absent` |
service-storage sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `31ed06763`, run 2026-09-29T18:42:47Z to 18:46:12Z |
enumerated, 186 pages, frontier objectstack-ai#20702 (newest objectstack-ai#20702 before and after),
18,529 numbers | 1,254 | **27** | 27 | 8 | 8 |
| after | head `5db5155a2`, run 18:55:34Z to 18:58:50Z | enumerated, 186
pages, frontier objectstack-ai#20702 (newest objectstack-ai#20702 before and after), 18,529 numbers
| 1,227 | **0** | 0 | 0 | 0 |

The before count matches the seat's census at the claim (27 sites in 8
files, at `6bff748b`). The whole-repo drop is 27, exactly this diff's
census sites. The `resolves` tally is 32,967 in both runs, and
`resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (994) did
not move either. The after run was taken on `5db5155a2`; the head
`09d2ecc96` adds only the changeset. No run was truncated or discarded:
both enumerations read 186 pages at the newest frontier.

**Supplementary instrument, the whole scope.** The census does not read
test files or strings, and this stage's scope includes test comments. So
a second reading runs the gate's own exported `extractCitations`
(whole-file and comment-prose projections) and `namesThisRepository`
over every `.ts` file under `service-storage/src` (71 files). It takes
its verdicts from the before census's own board reading rather than from
a second enumeration: a number is dead when that census reported it
`allocated-but-absent`, and alive when that census judged it on this
board anywhere (its `--list` extraction, 4,943 numbers) and did not
report it. The three numbers the census never saw, because they stand
only in test files (`objectstack-ai#13996`, `objectstack-ai#15607`, `objectstack-ai#17571`), were read one by one
on the issues endpoint, and each answers 200.

| reading | citations | dead | src comment | test comment | src string |
test string |
|---|---|---|---|---|---|---|
| before, `31ed06763` | 608 | **53** | 27 | 15 | 0 | 11 |
| after, `5db5155a2` | 566 | **11** | 0 | 0 | 0 | 11 |

Its src-comment column equals the census's 27, which is the control on
the second instrument. The 554 live citations and the 1 cross-repo
citation are the same in both readings, and the drop of 42 citations is
exactly the rewritten sites. A third, raw reading (every `#` followed by
2 to 6 digits, whatever surrounds it) finds 53 dead occurrences before
and 11 after, and its residue equals the gate's residue site for site.

## Per-number table

Sites and files count every dead occurrence in scope at the base
(comments and strings, tests included). `rewritten / left` counts the
sites rewritten and the sites left. Each anchor was read in its message
and diff, not only its subject, and `git blame` at the base puts each
rewritten line in that commit or in a later one that applied it.

| number | sites / files | rewritten / left | anchor: what it decided |
|---|---|---|---|
| `objectstack-ai#13178` | 19/5 | 14/5 | `f087c376f`: the `sys_file` /
`sys_upload_session` update and delete doors take the acting
organization and scope the statement to it (they stamp nothing), and the
upload routes bind the session they had resolved and discarded. New to
the sweep |
| `objectstack-ai#13279` | 11/4 | 11/0 | `6a180e42d`: a failed permission-store read
raises `AuthzStoreUnavailableError` (503) instead of reading as zero
grants, and the transports' fail-closed nets, this package's file-read
authorizer among them, re-raise it. The anchor of stages 2 and 5 and of
the rest, runtime and types stages |
| `objectstack-ai#10091` | 9/3 | 5/4 | `da891e0ef`: `sys_attachment` `beforeUpdate`
gated by the uploader-or-parent-editor rule, the attach rule on a
re-point, and the update-verb refusal of an unscoped multi-update. New
to the sweep |
| `objectstack-ai#11427` | 6/3 | 4/2 | `c3c72a4bc`: record file-field hydration asks
the reap guard's held-file question, through the batched `findHeldFiles`
this package adds, so hydration and the download path agree about a
tombstoned `sys_file`. Its message ends with a reference to `objectstack-ai#11427`.
New to the sweep |
| `objectstack-ai#6206` | 3/2 | 3/0 | `aa4b90d9a`: the full-envelope ruling applied to
the sharing contract; `ISharingService` takes the whole
`ExecutionContext`, and its docblock says callers "MUST NOT rebuild a
subset of it". Stage 2's anchor, named there as the full-envelope ruling
|
| `objectstack-ai#6523` | 3/2 | 3/0 | `aa4b90d9a`: the same commit, which was
`objectstack-ai#6523`'s change (its subject names it). Stage 2's and the spec stage's
anchor |
| `objectstack-ai#8778` | 1/1 | 1/0 | `7901b2dd2`: stamp-only
`tenancy.organizationField`, with its consumers scope-pinned by the
maintainer's ruling (the pin text is in its diff). The spec and
`plugin-security` stages' anchor |
| `objectstack-ai#11671` | 1/1 | 1/0 | `09b4f4e4e`: the source-hashes provenance
companion. The identical `translations/index.ts` line in
`service-messaging`, `plugin-sharing` and `plugin-security` already
cites it |

Every cited sha matches exactly one commit (`git rev-parse
--disambiguate`, count 1 for each of the 7), and every one is an
ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 7; the
history is complete, `--is-shallow-repository` false, 15,120 commits).
Each of the 8 numbers answers 404 on the issues endpoint, read one by
one before the rewrite.

## Wordings to check

- **The full-envelope ruling, `attachment-access-hooks.ts:127` and
`:129`.** 「what the objectstack-ai#6206 ruling requires … (objectstack-ai#6523)」 became 「what the
full-envelope ruling requires … (commit aa4b90d)」. The quoted words
「MUST NOT rebuild a subset of it」 are the `ISharingService` docblock
that `aa4b90d9a` wrote, so the commit sits beside the quotation. The
same form at `attachment-access-hooks.test.ts:766`.
- **`attachment-access-hooks.test.ts:914-916`.** 「the objectstack-ai#6523 contract's
unit is the envelope, and objectstack-ai#6206 forbids rebuilding a subset of it」
became 「the contract's unit is the envelope (commit aa4b90d), and the
full-envelope ruling forbids rebuilding a subset of it」 (1 reflow line,
`:916`).
- **A heading that named its card,
`attachment-access-hooks.test.ts:621`.** 「objectstack-ai#10091 through the WIRED
engine」 became 「Commit da891e0's gate through the WIRED engine」.
- **The confusion the loud outage prevents,** `storage-routes.ts:201`,
`storage-service-plugin.ts:1057`,
`file-read-tenancy-posture-admission.test.ts:582` and
`storage-routes.authz-outage-relay.test.ts:16`. 「the confusion objectstack-ai#13279
exists to prevent」 became 「the confusion commit 6a180e4 was made to
prevent」: an outage answered as a capability denial is what that
commit's message says it removes.
- **The relay, `storage-service-plugin.ts:1048` and `:1149`.** 「the
objectstack-ai#13279 relay that block already runs」 became 「the relay that block has
run since commit 6a180e4」, and 「takes the objectstack-ai#13279 relay in」 became
「takes the relay (commit 6a180e4) in」. The re-raise in that `catch`
(`:1229`) is in `6a180e42d`'s diff.
- **A referent, `storage-service-plugin.ts:1058-1059`.** 「it had
swallowed the objectstack-ai#13279 permission-store outage at this door since that
card landed」 became 「it had swallowed the branded permission-store
outage at this door since commit 6a180e4 landed」: 「that card」 lost its
referent with the number (1 reflow line, `:1059`).
- **The update/delete halves, `file-reference-lifecycle.ts:111`.** 「the
update/delete halves objectstack-ai#13178)」 became 「the update/delete halves in commit
f087c37)」, beside the live `objectstack-ai#12745` and `objectstack-ai#12928`.
- **Present tense made past,
`tombstone-hydration-download-agreement.test.ts:320`.** 「the divergence
objectstack-ai#11427 fixes」 became 「the divergence commit c3c72a4 fixed」.
- **The scope pin, `backfill-sys-file-organizations.ts:86`.**
「scope-pinned by the objectstack-ai#8778 ruling (widened by name on cloud#1395)」
became 「scope-pinned by its ruling (commit 7901b2d; widened by name on
cloud#1395)」. 「its」 is the key's own ruling, which `7901b2dd2` carried
out and recorded as the pin; the widening is the cross-repo reference
that was already there.
- **Reflow, 2 lines with no dead site** (every file keeps its line
count): `attachment-access-hooks.test.ts:916`,
`storage-service-plugin.ts:1059`.

## The 11 sites left

- **Test titles, 11 sites.** `describe` / `it` titles, which are string
tokens, left as stages 1 to 5 left theirs:
`attachment-access-hooks.test.ts:232`, `:314`, `:640`, `:932`
(`objectstack-ai#10091`); `tenant-audit-update-delete-half-repairs.test.ts:151`,
`:224`, `:345`, `:552`, `:664` (`objectstack-ai#13178`);
`tombstone-hydration-download-agreement.test.ts:148`, `:326` (`objectstack-ai#11427`).
- There is no operator string, assertion message, generated header or
quoted ruling carrying a dead number in this package. The generated
`*.source-hashes.generated.ts` headers are untouched and carry none. The
verbatim maintainer quotations in scope (5 lines: 「同意」 three times,
「12745 A回,其他同意。」 and 「批 objectstack-ai#7 同意」) carry no dead number and are untouched.

## Mechanical guard: no code token moves

The guard compares the TypeScript parser's leaf nodes, with comments as
trivia and JSDoc nodes never visited, base `31ed06763` against head.
Template literals are therefore read in context. It ran over all 15
touched `.ts` files.

- Real run: 20,143 base leaf tokens, **0 files with a token change**
(exit 0).
- Comment control in `storage-routes.ts` (`Bound, not discarded` to
`Bound and not discarded`): 0 files changed, as expected (exit 0).
- Positive control, a code token added in `storage-routes.ts` (`const {
fileId, eTag } = req.body ?? {};` given a trailing `?? undefined`):
DIFFER (exit 1).
- Positive control, one digit changed inside a kept test title
(`tombstone-hydration-download-agreement.test.ts:148`): DIFFER (exit 1).

Every mutation went through `scripts/ablation-replace.mjs`, and each
landed (anchor 1 to 0, blob changed). Each restore was proven
byte-identical to the HEAD blob (`44ecc8e64ae0`, `ee84cf718a6f`), with
`git diff HEAD` empty and a clean tree afterwards.

## Changeset

This change ships bytes, so a `patch` changeset for
`@objectstack/service-storage`
(`.changeset/20596-service-storage-provenance-anchors.md`) is included.
It says only that the provenance comments were re-anchored, in stage 5's
words.

Measured on the built package (A3): `files[]` is `dist`, `README.md` and
`CHANGELOG.md`. After the build, the rewritten comments reach `dist`:
`f087c376f` 6 times and `da891e0ef` once in each of `dist/index.d.ts`
and `index.d.cts`; `f087c376f` 4 times and `da891e0ef` once in each of
`index.js` and `index.cjs`. Positive controls: the unchanged line 「the
parent record — the delete rule, applied to the verb that could」 beside
the shipped rewrite at `attachment-access-hooks.ts:28` is found once in
each declaration file, and the unchanged line 「standard catalog code —
the same both-verbs pairing the derived」 beside the shipped rewrite at
`:470` once in each JS file. A never-written negative phrase appears
nowhere in `dist`. None of the 8 dead numbers is left anywhere in
`dist`.

## Gates (head `09d2ecc96`)

- **Citation judging, as CI runs it:** `pnpm check:issue-citations`
(self-test) exits 0. `node scripts/check-issue-citations.mjs` exits 0:
the diff-scoped run judged 3 citations (`objectstack-ai#12069` and `objectstack-ai#10246` resolve;
`cloud#1395` is cross-repo), each already on the line it replaces.
- **Doc authoring:** `pnpm check:doc-authoring` exits 0.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at `09d2ecc96` derived 65 commands:
all 56 derived at dispatch, plus `check:duration-unit-keys`,
`check:dispatcher-error-vocabulary`, `check:engine-double-contract`,
`check:logger-receiver-detach`, `check:objectql-double-limit`,
`check:query-options-erasure`, `check:type-check-coverage`,
`check:type-check-debt` and `check:where-matcher`. Each ran with its
exit code captured before any pipe, and all 65 exit 0. `--ran`, fed each
command with its exit code, reports 65 run, 0 NOT MEASURED (a derived
zero), 0 unrun, and exits 0. A full `turbo run build` of `./packages/*`
and `./packages/*/*` ran first under the shared verify lock (71 of 71
tasks, exit 0), so no gate hit an unbuilt workspace.
- **Roster families the derivation lists outside its commands** (their
rosters sit in directories this diff touches): `node
scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm
check:error-code-casing` and `pnpm check:filter-alias-parity`, each exit
0.
- **Tests and typecheck, under the verify lock:**
- `pnpm --filter @objectstack/service-storage test`: 40 files pass and
627 tests pass. That is every test file in the package, the 7 touched
ones included.
- `pnpm --filter @objectstack/service-storage typecheck` exits 0 (`tsc`
on `tsconfig.json`, the scripts program, and the test layer on
`tsconfig.test.json`). `--listFiles` on both `tsconfig.json` and
`tsconfig.test.json` shows all 71 files under `src/`, the 40 test files
included, and all 15 touched files in the program.
- **Lint, as a proven narrowing:** `eslint --no-inline-config --format
json` over the 15 touched `.ts` files gives 15 files, 0 errors and 0
warnings. All 15 are in eslint's own population (`isPathIgnored` is
false for each; a `dist` file, as the control, is ignored).
`eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`, as its own lines 327-328 state), so a comment
edit here cannot move the verdict on any untouched file. The repo-wide
`pnpm lint` is CI's run.
- **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of
the 16 changed files for control bytes finds none.

## Acceptance notes

- **The gate-invisible spellings, grepped as the claim asked.**
`CITATION_RE` refuses a hyphen after the digits and a `/` before the `#`
(objectstack-ai#20636). In this package there is no `#N-word` spelling at all. There
are 11 `#A/#B` lines carrying 13 second numbers
(`attachment-access-hooks.ts:215`, `:217`, `:434`;
`attachment-access-hooks.test.ts:217`; `attachment-lifecycle.ts:177`;
`file-reference-lifecycle.test.ts:226`;
`local-storage-adapter.test.ts:35`; `metadata-store.test.ts:41`;
`storage-route-ledger.ts:74`, which chains four;
`storage-routes.metadata-outage.test.ts:67`;
`tombstone-download-live-reference.test.ts:50`), and every second number
on them is live: `objectstack-ai#5574`, `objectstack-ai#9974`, `objectstack-ai#5541`, `objectstack-ai#5480`, `objectstack-ai#3833` and `objectstack-ai#3847`
by the census's own board, and `objectstack-ai#5197` and `objectstack-ai#3870` read one by one
(200). So nothing there needed rewriting. The claim counted 12 such
spellings on `main`; this reading is 11 lines and 13 second numbers,
with nothing dead among them either way. The raw scan above, which sees
both spellings, agrees.
- **The census instrument did not truncate in this stage.** Both
enumerations read 186 pages at the newest frontier.
- **Anchors the next stages can reuse**, each checked here: `objectstack-ai#13178` →
`f087c376f`; `objectstack-ai#10091` → `da891e0ef`; `objectstack-ai#11427` → `c3c72a4bc`; `objectstack-ai#13279` →
`6a180e42d`; `objectstack-ai#6206` / `objectstack-ai#6523` → `aa4b90d9a`; `objectstack-ai#8778` → `7901b2dd2`;
`objectstack-ai#11671` → `09b4f4e4e`.
- **Base.** The branch is 4 commits behind `main` (`defc7f7b5`, read at
19:31Z). None touches `service-storage`,
`scripts/check-issue-citations.mjs` or `.changeset/config.json`, so
there was no merge.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

2 participants