Skip to content

fix(spec): serve a field's translated help on description, never on an undeclared help - #21956

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-21948-translated-field-help-declared-key
Oct 6, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-21948-translated-field-help-declared-key

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21948

Clause-②: no

What changed

translateObject used to put a bundle's objects.OBJECT.fields.FIELD.help entry on a help key that FieldSchema does not declare. The served field then failed FieldSchema with unrecognized_keys. A consumer that reads only declared keys rendered the English description, and the console logged one ingestion warning per such field.

  • Target key, measured. The extractor writes that bundle entry from field.help ?? field.description (packages/cli/src/utils/i18n-extract.ts:1223). FieldSchema refuses help, so on a spec-valid field the only source is description. The nine bundle source trees author 0 inlineHelpText and 0 field help. The nine en bundles carry 523 help entries.
  • translateField (packages/spec/src/system/i18n-resolver.ts:3054) now serves the translated help on description and writes no help key. inlineHelpText is not touched.
  • Precedence: ADR-0029 D9.2a, the resolver's own rule, applied through the existing valueOverridesPackagedBase predicate. There is no second comparison. The catalog applies only while the served field's description equals the packaged field's.
  • ObjectFieldLike (:2453) drops its help?: string member. This is safe:
    • its [key: string]: any index signature still accepts and types a help key, so a caller that writes or reads one still compiles (probe below);
    • packages/spec/api-surface/system.json records only ObjectFieldLike (interface), not its members, and check:api-surface stays green;
    • the one in-repo importer (service-analytics, which reads options only) still typechecks.
  • The translateObject docblock no longer says it translates a field's help. A new section states the target key and the D9.2a precedence.
  • .changeset/21948-spec-field-help-served-on-description.md: @objectstack/spec patch.

Clause-② — measured no

  • No Zod schema, parse or export changed. These all stay green: check:authorable-surface, check:api-surface, check:export-origins, and check:generated (15 artifacts up to date).
  • Type probe (scratch, not committed), compiled against the rebuilt dist/system/index.d.ts, exit 0:
    • const legacy: ObjectFieldLike = { name: 'x', help: 'legacy' } compiles, and so does reading legacy.help;
    • a declared member still refuses a wrong value (an @ts-expect-error on label: 42 is consumed);
    • const proof: number = ({} as ObjectFieldLike).help compiles, which shows the probe read the rebuilt declaration.
  • Control leg: the same probe against a copy of that .d.ts with BASE's help?: string put back gives exit 2, with exactly one error: TS2322 on the proof line. Writing and reading help compile under both declarations.
  • So no value the type or the schemas used to accept is refused now. The one type-level delta is that ObjectFieldLike['help'] is now read through the index signature (any) instead of string | undefined. That is stated here for the at-tier review.

Measured

All at HEAD 3ad2f22bed unless stated.

Corpus at the resolver seam (scratch script, not committed). Every object in packages/platform-objects/scripts/i18n-extract.config.ts (48 objects, 617 fields), with that package's real bundles, using each object as its own packaged base:

served help keys help refused by FieldSchema sys_user fields with help zh-CN description equal to the bundle entry
BASE behaviour (ablation leg 1 below) 332 332 18 of 26 0
HEAD 0 0 0 of 26 332
  • At HEAD in zh-CN, sys_user.two_factor_enabled.description = 该用户是否已启用双因素认证。由 better-auth 的 \twoFactor` 插件维护。`.
  • In en, 0 descriptions change from source, because the en entries repeat the source.
  • NOT MEASURED: the HTTP door (GET /api/v1/meta/object/sys_user). The spec change invalidates the showcase build closure: 62 of 63 turbo tasks miss the cache. The pins and the corpus run read the same translateMetadataDocument dispatch the REST read calls.

Pins (packages/spec/src/system/i18n-resolver.test.ts:3552, 9 cases):

  • a zh-CN served field carries no help, has the translation on description, and parses with no unrecognized_keys;
  • the untranslated control is unchanged;
  • a field with both description and inlineHelpText gets the translation on description, and inlineHelpText is untouched;
  • a diverged description is kept in zh-CN and en, through the type dispatch, while the undiverged sibling is translated;
  • a field the base does not declare counts as diverged;
  • with no packaged base (undefined, null, or omitted) the catalog applies;
  • an absent description is filled by the catalog;
  • array-shaped fields are judged against an array-shaped base;
  • the input is not mutated.

No existing pin asserted a served field help. I searched every test outside the bundle suites, so none had to move.

Ablation, run on the committed head. Each leg used scripts/ablation-replace.mjs in WRAP mode plus a trap. Predictions were written down before the run. Both legs were restored, with the restored blob equal to HEAD (a8eade91) and git diff HEAD empty:

leg mutation (anchor 1 → 0 on disk) predicted observed
1 restore BASE's two lines (write next.help, never description) 7 failed / 2 passed (control and no-mutation stay green) Tests 7 failed | 2 passed
2 flat overlay (the D9.2a comparison replaced by true) 3 failed: the diverged, undeclared-field and array cases Tests 3 failed | 6 passed
  • Leg 1 sample: expected 'Whether two-factor authentication is …' to be '该用户是否已启用双因素认证。…'.
  • Leg 2 sample: expected '该用户是否已启用双因素认证。…' to be 'Edited by the tenant.'.

Tests

  • @objectstack/spec, full local project: Test Files 619 passed, Tests 18480 passed | 1 todo.
  • pnpm --filter @objectstack/spec typecheck: exit 0. Its test layer compiles under tsconfig.test.json with the identity-pinned debt held.
  • @objectstack/service-analytics: typecheck exit 0. The three dimension/label suites that call translateObject: Tests 53 passed.
  • Targeted eslint over the two changed .ts files: 0 errors and 0 warnings. That run is not the repo-wide lint, which is CI's.

Gates. node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran derived 85 families: 84 ran with exit 0 and 1 is NOT MEASURED.

  • The NOT MEASURED one is pnpm check:dual-build-cjs-loads, which exits 3 with PREREQUISITE NOT MET because 78 packages have no dist/.
    • Narrowed in its place: all 19 require entries of @objectstack/spec's exports load from the rebuilt dist, and system.translateObject is a function.
  • pnpm check:lean-entry-closure first answered exit 3 (objectql/core unbuilt). After turbo run build --filter=@objectstack/objectql... it answered exit 0, and that is the recorded code.

Ships: npm pack --dry-run of @objectstack/spec lists dist/system/index.js, .mjs and .d.ts. Both runtime files carry packagedObjectField and no next.help = translatedHelp.

Base: origin/main moved to a3bd157730 (#21947, test titles under packages/spec/src/ui/ only). None of this PR's files is touched, so it is not merged.

Known readers in objectui

objectui at .objectui-sha 0abd4f9f, read-only. No objectui file is edited here.

  • packages/plugin-form/src/ObjectForm.tsx:1033 and packages/plugin-form/src/sectionFields.ts:311 (field.help || field.description), and packages/app-shell/src/utils/resolveActionParams.ts:714 (param.helpText ?? field.help ?? field.description).
    • All three fall back to description, so they render the same translated text, and their help arm is now dead. Retiring those arms is objectui's job.
  • The ingestion warning is packages/core/src/utils/reference-keys.ts:362, reached through the no-declared-twin arm (:415) of canonicalizeRetiredFieldKeys. It fires for any undeclared key on a served field def, so it goes quiet once no help is served.

Acceptance notes

None of these is addressed in this PR.

  1. Studio saveFields write-back. NOT MEASURED; this is a read-only inference.
    • objectui packages/app-shell/src/services/MetadataService.ts:898 to :939 carries per-field server keys from the translated read back into the object PUT.
    • help is not in objectui's RETIRED_FIELD_KEYS (packages/types/src/internal/retired-field-keys.ts). So a field save on a platform object with bundle help entries should have sent a key FieldSchema refuses.
    • This PR removes the source.
    • Carrier: this PR. Reach would need one measured PUT.
  2. The extractor's field.help ?? arm (packages/cli/src/utils/i18n-extract.ts:1223) reads a key FieldSchema refuses, so it is dead on every spec-valid field. Carrier: domain:cli; none in flight.
  3. Field placeholder is never overlaid. FieldTranslationSchema declares placeholder, and the extractor emits objects.OBJECT.fields.FIELD.placeholder (:1224), but translateField never overlays it. The nine shipped bundles carry 0 field-level placeholder entries (the 4 per locale are action params), so it is dormant. Carrier: none.
  4. inlineHelpText has no translation path. The extractor never reads it, so an authored one is never offered for translation. The nine bundle sources author 0 of them. Carrier: none.
  5. Docs line. content/docs/protocol/kernel/i18n-standard.mdx:166 lists a field's label / help / placeholder as display labels, but FieldSchema declares no help. Carrier: none.

Generated by Claude Code

claude added 2 commits October 6, 2026 05:19
… an undeclared `help`

translateObject overlaid the bundle's `objects.OBJECT.fields.FIELD.help`
entry onto a `help` key FieldSchema does not declare. The entry is the
translation of the field's `description` (the i18n extractor writes it from
that key), so it is now served there, under ADR-0029 D9.2a: the catalog
applies only while the served description equals the packaged field's
(valueOverridesPackagedBase), and with no packaged base it applies.
ObjectFieldLike drops its `help` member.

Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:system tests tooling labels Oct 6, 2026
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 138 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 01e0f71ad8518ed208dc95eb7bb6bf2b2fc2fa05 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 1562b97cc7f3ebadae0712bb31964613a5fa3a83 — the merge of head 3ad2f22bed534424e1af81d7d96c38eab84afd0d into base 01e0f71ad8518ed208dc95eb7bb6bf2b2fc2fa05, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1562b97cc7f3ebadae0712bb31964613a5fa3a83 && git checkout 1562b97cc7f3ebadae0712bb31964613a5fa3a83
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 01e0f71ad8518ed208dc95eb7bb6bf2b2fc2fa05 3ad2f22bed534424e1af81d7d96c38eab84afd0d && git checkout -B drift-repro 01e0f71ad8518ed208dc95eb7bb6bf2b2fc2fa05 && git merge --no-ff 3ad2f22bed534424e1af81d7d96c38eab84afd0d

node scripts/docs-audit/affected-docs.mjs --json 01e0f71ad8518ed208dc95eb7bb6bf2b2fc2fa05

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 6, 2026 07:01
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 6, 2026 07:01
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 6, 2026
Merged via the queue into main with commit bab7685 Oct 6, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21948-translated-field-help-declared-key branch October 6, 2026 07:40
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…ce row no longer displaces a code-defined datasource at boot, and the metadata door refuses edits to the host default (objectstack-ai#21965)

Part of objectstack-ai#21922
Fixes objectstack-ai#21944
Clause-②: no (narrowing)

## What changes

A code-defined datasource (a `*.datasource.ts` the installed artifact
declares, or the host's own `default`) is read-only by published
contract: `DatasourceSchema.origin` says "code — authored as
`*.datasource.ts`, GitOps-owned, read-only in the UI", the datasource
registry entry in `metadata-plugin.zod.ts` says code-defined datasources
"win on name collision", and `datasource-admin-service.ts` says "A
runtime datasource never shadows a code one (code wins on collision)".
The datasource-admin plugin's boot restore broke all three, and the
metadata door could not see `default` at all.

The fix is the one host-owned set of code datasources the two cards'
triage asked for ("One set serves both, so do not build two"):

- **The set** (`packages/runtime/src/code-datasource-names.ts`, new).
One in-memory `Set` of the datasource names the host registers from
code, on the kernel service `code-datasource-names`.
`contributeCodeDatasourceNames` registers it on first use and adds to it
after that, the shape `seed-summary` uses.
- **Its producers, both in `init()`.** `AppPlugin.init()` adds every
datasource the artifact declares: the same list its `start()` registers
in the MetadataService, now memoized so the two phases read one answer.
`DefaultDatasourcePlugin.init()` adds `default`. Phase 1 completes
before any `start()`, so the set is whole before the restore runs,
whatever order the plugins were composed in.
- **The restore** (`restoreRuntimeDatasources`,
`packages/services/service-datasource/src/datasource-admin-plugin.ts`).
A stored row under a name in the set is not registered over the code
definition. It is kept, and one boot warning names it with the repair.
The warning goes to the host's `options.logger`, or to the kernel logger
when the host passes none (`os serve` passes none).
- **The resolver** (`isDeclaredCodeDatasource`,
`packages/metadata-protocol/src/protocol.ts`, nothing else in that
file). It reads the same set beside the installed packages, so the
metadata door answers `default` the way it answers every code-defined
datasource since PR objectstack-ai#21942.

⛔ "Code" is never read from a stored row's `origin`, the MetadataService
slot's `origin`, the connection service's `ConnectResult`, or a request
body's `origin`.

## Measured on a booted showcase

The harness is the `@objectstack/verify` `bootStack` with the
datasource-admin routes mounted the way `serve.ts` mounts them, in a
temp cwd. The stored rows assert `origin: 'runtime'` and their own
`config.filename` (the cards' case (b)). They were written through the
metadata door's repository on the runtime-only intent, then the stack
restarted. BEFORE is `76fec88b16`; AFTER is this branch at `1d840709ae`.
The readings come from a throwaway probe that was never committed; the
committed pins below assert the AFTER column.

| Reading after the restart | BEFORE | AFTER |
|---|---|---|
| admin list, `showcase_external` | `origin: runtime`, label "Shadow
21922" | `origin: code`, "External Analytics (SQLite)" |
| `PATCH /api/v1/datasources/showcase_external` | 200 | 400
`DATASOURCE_ADMIN_ERROR` "… is code-defined and cannot be edited at
runtime." |
| live pool named `default` | a second pool opened on the stored row's
file; verdict `already-registered` became `connected` | none; verdict
stays `already-registered` |
| `showcase_ext_customer` read | 3 rows (code fixture) | 3 rows (code
fixture) |
| boot warning naming each stored row | none | one per row |
| `PUT /api/v1/meta/datasource/default` | 200 "Saved datasource
'default'" | 403 `NOT_OVERRIDABLE` |
| `DELETE /api/v1/meta/datasource/default`, no stored row | 200 | 403
`NOT_OVERRIDABLE` |
| `DELETE /api/v1/meta/datasource/showcase_external` (repair), then meta
`GET` in the same boot | 200, but the meta `GET` kept serving the stored
edit until the next restart | 200, and the meta `GET` serves the code
definition |

## The dispatch's mechanism hypotheses

- **H1, start order.** In all three compositions that load
`service-datasource` (`serve.ts`, `standalone-stack.ts`, the verify
harness), `DefaultDatasourcePlugin` and `AppPlugin` are `use()`d before
`DatasourceAdminServicePlugin`. None of the three declares an ordering
edge to another, so their `start()`s run in insertion order: the code
registrations did land before the restore, but by list position alone,
which ADR-0116 says proves nothing. The answer is the first branch: the
set is filled by a phase that precedes the restore (`init()`). It is
pinned by a boot whose reader plugin is composed first, ahead of every
producer. The restore pin also covers a code registration that lands
after it.
- **H2, the seam.** It is a kernel service read through the services
registry the protocol already resolves (`getServicesRegistry()`), with
no `packages/spec` change. The ObjectQL registry was rejected: the
engine's datasource definitions mix both origins, and a host package
record would be a fabricated provenance.
- **H3, the admin refusal.** Measured, not assumed. The pins' stored
rows carry `origin: 'runtime'`, and the slot the refusal reads holds
AppPlugin's explicit `origin: 'code'`. Under ablation A the admin door
served the stored row as `origin: runtime`, so the refusal cannot come
from the admin read's `origin ?? 'code'` default.
- **H4, the live pool.** For `default`: yes. The restored row reached
`rehydratePools`, which opened a second pool named `default` on the
row's file. Routing did not move, because the engine never routes to a
driver named `default`; the default driver keeps its natural name. It is
the same defect and the same decision fixes it, pinned by
`getDriverByName('default')` and the connect verdict. For
`showcase_external`, nothing was re-pointed at boot in this composition:
AppPlugin's connect ran first, so the rehydrate answered
`already-registered`. The admin `PATCH` 200 was the open door to a
re-point (an update that changes connectivity rebuilds the pool), and it
is now refused.

## Seam and the Clause-② limb (for the seat)

- **Published exports added: none.** `code-datasource-names.ts` is not
re-exported from `packages/runtime/src/index.ts`. `service-datasource`
and `metadata-protocol` spell the service name privately and read the
value structurally as `has(name)`, the way `'datasource-connection'` is
read today.
- **What the seam does add is one kernel service entry**,
`code-datasource-names`, which two packages read by name. Whether that
is the claim's "service contract" limb is the seat's call. The line
above stays as the claim wrote it, and the changeset grades all three
packages `minor`, which holds under either reading.

## Named gap: the metadata door's read while a stored row exists

`GET /api/v1/meta/datasource/:name` still serves a stored row under a
code-defined name for as long as the row exists. The door reads its
stored overlay first (ADR-0005's read order), whatever the
MetadataService holds. The AFTER boot measured it: the admin door served
the code definition while the meta `GET` served the stored row, for
`showcase_external` and for `default`. So triage's pins "both doors
serve the code definition" and "removes it with no change to what is
served" hold for the admin door. For the metadata door they hold once
the repair `DELETE` has run, in the same boot. That read lives in
`getMetaItem`'s overlay step, outside `isDeclaredCodeDatasource`, and
`protocol.ts` is held by objectstack-ai#21934 in other regions, so it is left to the
seat. `meta-door-code-datasource.dogfood.test.ts` already pins that read
as it is.

objectstack-ai#21922 stays open for that read: this PR is `Part of` it, and the seat
routes the remaining half through triage when it merges.

## Landing beyond the claim's named files

`packages/runtime/src/app-plugin.ts` is the producer of the packages'
half of the set, in the declared `runtime` package. The memo also makes
its residual-owner warning print once instead of once per phase.
`packages/runtime/src/code-datasource-names.ts` is new in the same
package.

## Patch round 1 (head `d77e150701`)

Review `6011282321` on objectstack-ai#21922. The Tests, Ablations and Gates sections
below are round 0's, at `80fbcfdea6`; this section carries the readings
on the current head.

- **The plugin-dev pin (CI red on round 0).** `AppPlugin.init()` now
contributes its datasource names as a function that the host's
code-datasource set resolves at its first read. The set is still
contributed to only in Phase 1, before any `start()`. The resolution is
deferred because the names come from the artifact's `collections`, which
walk `packages[]`. `AppPlugin.init()`'s manifest registration is the one
thing in `init()` allowed to touch `packages[]`, pinned by
`plugin-dev`'s malformed-stack falsifier, which this PR's first head
turned red. The kernel service now holds a `CodeDatasourceNames`, a set
with pending contributions; readers still use `has(name)` only. A
contribution that throws stays pending and rethrows to every reader.
- **The `default` refusal names what defines it**: the host's database
configuration (the database URL the server starts with). It names no
`*.datasource.ts`, because none declares `default`. Every
package-declared datasource's sentence is byte-identical, and `code`,
`status` and the refused set are unchanged (`packaged-base-regime.ts`,
the datasource row's `hostOwned`).
- `const listOf = (` spacing restored in `app-plugin.ts`. Merged
`origin/main` `80f9f7e6ba` as `49421a8fe6`.
- **Ablation D:** the old sentence put back for `default` turned 6 unit
cases and 1 dogfood case red, and was restored by blob.
- **Tests at `d77e150701`** (each `VERDICT command-exit 0`):
  - `service-datasource`: 748 / 748;
  - `metadata-protocol`: 27978 passed, 19 skipped;
  - `runtime` local: 4668 passed, 19 skipped;
  - `plugin-dev`: 86 / 86;
  - the two dogfood files: 11 / 11;
- downstream consumers of `runtime` (cli, client, verify,
http-conformance, cloud-connection): all passed;
  - typechecks for the five packages: green.
- **Gates at `d77e150701`:** `dispatch-gates --commands` derived 72,
reconciled with `--ran` as 72 run and 0 not measured, plus
`check:init-service-contract` and `check:startup-registry-verdict`. All
exit 0.
- **Docs:** the 18 hand-written pages the Docs Drift Check lists were
read page by page. None states anything this PR makes false, so no docs
are edited.

## Tests (head `80fbcfdea6`)

- `pnpm --filter @objectstack/service-datasource exec vitest run
--maxWorkers=2`: 41 files, 748 passed.
- `pnpm --filter @objectstack/metadata-protocol exec vitest run
--maxWorkers=2`: 218 files passed, 3 skipped; 27976 tests passed, 19
skipped.
- `pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2
--project local`: 331 files, 4658 passed, 19 skipped.
- Dogfood, `--project isolated`:
`datasource-restore-code-wins.dogfood.test.ts` (new) and
`meta-door-code-datasource.dogfood.test.ts`, 2 files, 11 passed.
- `typecheck` green for `service-datasource`, `metadata-protocol`,
`runtime` (including its `check:test-typecheck` ledger, held) and
`dogfood`. `tsc --listFiles` counts each touched test file once in its
program.
- Each package's run includes its new pins: 5 in
`datasource-admin-plugin.test.ts` (the restore), 4 in
`code-datasource-names.test.ts` (the set and its phase), and 2 resolver
plus 8 door cases in `protocol.code-defined-datasource-door.test.ts`
(`default`, on both kernel shapes).

## Ablations

Each one was committed first and mutated with
`scripts/ablation-replace.mjs` in wrap mode, under a shell trap. For
subjects resolved through `dist/`, the package was rebuilt and
`ablation-dist-preflight.mjs` proved the marker was present. The restore
leg was rebuilt and proven `--absent`, the blob equalled HEAD, `git diff
HEAD` was empty, and the tree was clean.

- **A, the restore registers over a code name**
(`datasource-admin-plugin.ts`; marker in 2 files of
`service-datasource/dist`). Unit: 3 failed, 16 passed. The slot served
the stored row, the warning was not called, and the order-independent
case registered the row. Dogfood: 2 failed, 3 passed. The admin list
served `showcase_external` as the stored row, and the repair case read
the same.
- **B, the resolver does not know the set** (`protocol.ts`; marker in 2
files of `metadata-protocol/dist`). Unit: 5 failed, 30 passed (the
resolver case, and `PUT` plus no-row `DELETE` of `default` on both
kernels). Dogfood: `PUT /meta/datasource/default` answered 200. The next
case then failed as a cascade, because the row that `PUT` stored made
the seed conflict. The repair `DELETE` and runtime controls stayed
green, as expected.
- **C, AppPlugin's `init()` contribution deleted** (`app-plugin.ts`; the
subject resolves from source). The reader-first boot saw `['default']`,
not `['app_wh', 'default']`. So the set comes from `init()`; the
`start()` registration never fills it.

## Gates (head `80fbcfdea6`)

`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 72 commands on the actual change, a
superset of the 52 at dispatch. All 72 ran with exit codes captured
before any pipe. `--ran` printed "72 derived famil(ies) accounted for —
72 run, 0 NOT-MEASURED".

- `check:dual-build-cjs-loads` first answered `PREREQUISITE NOT MET`
(exit 3) because eight packages outside this diff had no `dist/`. After
building them it measured green.
- Two families the derivation does not name were also run, both green:
`check:init-service-contract` ("34 declared / 1 self-provided / 3
without a workspace provider") and `check:startup-registry-verdict`
("none recording a verdict the boot can contradict").
- `check-changeset-no-major`'s level axis needs a PR payload, so CI
reads it.
- Lint is a proven narrowing, not the repo-wide run. `eslint
--no-inline-config --format json` on the 9 touched source and test files
reported 9 files, 0 errors and 0 warnings. `eslint.config.mjs` never
enables type-aware linting (no `parserOptions.project`, no typed rules,
as its own comment states), so this diff cannot move any untouched
file's verdict.

## Acceptance notes

- **The `default` refusal's remedy** names the host's database
configuration (patch round 1).
- **Cluster convergence.** `convergePool` reads a stored row directly
and is unchanged. Its signals come from peer admin writes, and the admin
door now refuses those for code names.
- **The restore's other warnings** (a failed read, a failed register)
still go only to `options.logger`, which `os serve` does not pass. They
are unchanged here.
- **objectstack-ai#21923 remains open.** This diff does not touch
`listDatasourceRecords`, `getDatasourceRecord` or
`persistDatasourceRow`. One interaction: a metadata-door-created
datasource with no `origin` still restores, and is still read as `code`
by the admin door's default.
- **Main drift.** `origin/main` gained objectstack-ai#21956, objectstack-ai#21964 and objectstack-ai#21961 (spec
and docs-qa only) after the round-1 merge. None touches a file here, and
the queue's merged generation is the check.

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

---------

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec(i18n): translateObject writes translated field help onto an undeclared help key — the console warns ~520× per page load and drops the value

2 participants