Skip to content

fix(platform-objects): decide the six bare metadata types' display en-echoes per leaf, and derive the population from the type registry - #19585

Merged
huangyiirene merged 6 commits into
mainfrom
claude/issue-19403-round7-en-echoes
Sep 21, 2026
Merged

huangyiirene merged 6 commits into
mainfrom
claude/issue-19403-round7-en-echoes

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Part of #19403

Clause-②: no

Round 7 of the en-echo decision series. The six bare metadata types — seed, mapping, api, doc, book, capability — have no form at all: no fields, no sections. Their registry label and description are the only strings an author ever sees for them, and every one of those twelve leaves was still its English source in zh-CN, ja-JP and es-ES. They are decided here, one leaf at a time, with the reason recorded for each.

The three-exit question, answered: EXIT 1 — taken, with a stated substitute

Rounds 5 and 6 both refused this family on one standing reason:

only a real panel keeps the DERIVED-POPULATION property with a working dark control — six unrelated types degenerate the derivation and the control has nothing to exclude.

That refusal was right about what it refused. It was reasoning about a population spelled ['seed','mapping','api','doc','book','capability'] — a hand-list, which derives nothing and excludes nothing. That is not the only population available, and this round names the substitute rather than deferring a third time.

What derives the population, if not one panel's en subtree. DEFAULT_METADATA_TYPE_REGISTRY — the spec-side list of every metadata type the platform declares — crossed with one predicate read off the catalog's shape: a type is bare when its catalog entry has no fields and no sections, so the registry entry is its whole panel. Both halves are derived and neither is the ledger's own opinion: the registry lives in packages/spec, a package this round does not touch, and it is the thing that manufactures these leaves — seed.label in the catalog is the registry entry's own label, and every row is pinned to both. Ten types satisfy the predicate; the six decided here are the six that echoed.

What the dark control can exclude — twice, and the second one is the half a panel walk never has.

  1. Outward — the 17 types that do carry a form are excluded, and their own type-level pairs are already authored. The sharpest single exclusion is dataset: it carries a registry description exactly as these six do, so any walk keyed on "registry entries with a description" would sweep it in. The bare predicate is what leaves it out, and it is already authored, so a walk that wrongly included it would not even go red. Asserted by name.
  2. Inwardfour members of the population (job, datasource, external_catalog, translation) are in the walk and come back non-echoing. A hand-list of six can only ever produce positives; this derivation produces a negative on four of its own members, in the same run, from the same walk. That is the control the refusal said this family could not have, and ablation B below is its proof: refilling job.label — a leaf no row in this ledger decides — reds the file.

And the substitute is strictly stronger than a panel walk in one measurable way. A panel walk is bounded by a type that already exists. This one is bounded by a shape over the registry, so a new metadata type — the actual way this defect class reproduces, since every one of these twelve leaves was born the day its registry entry was added — lands in the population automatically. No single-panel ledger in this series can do that. What is not claimed: that the property is preserved unchanged. It is substituted, and the controls are real but are not the same controls.

The phantom-translation trap — structurally out of reach here

Round 5 measured it on action.fields.ai.label: Ai to AI differs in bytes, so it passes the echo predicate in all three locales and drops the census by a key while telling a zh-CN author nothing. An echo that stops matching is not the same thing as a leaf that got translated.

Here the shortcut is not merely refused, it is unreachable, and that is a property of the family nobody chose: the en side of every row is the registry entry in packages/spec/src/kernel/metadata-plugin.zod.ts, not a string in this package. Touching up the English would be an edit to another package, outside this round's file surface. The only edit reachable from here is the one that actually renders the leaf — and ablation D proves the registry pin is live and independent.

The schema readings — five near-misses, each asserted, none described

Resolved through getMetadataTypeSchema (the registry's own schema map) rather than a hand-picked import list.

leaf the word verdict
doc.description, capability.description package near-miss REAL — it is a legal MetadataProvenanceSchema value and both schemas accept _provenance: "package" while refusing a rendered one. Cleared three ways: the key is an envelope field the loader sets (the item parses without it, and the whole en catalog names no underscore-prefixed key anywhere); these types are bare, so the string labels no input; and where this catalog does render a leaf whose stored value is literally package, it renders the display and keeps the value (sys_metadata.fields.managed_by.options.package is 包 / パッケージ / Paquete).
mapping.description rename not a TransformType member (none/constant/lookup/split/join/javascript/map) — rendered. But map is, and "field mapping" contains it: 'field mapping'.includes('map') is TRUE while the word-boundary predicate says NO. That pair is the token guard's dark control.
seed.description publish looks like a SeedMode, is not one — rendered, with this catalog's own authored 发布 / 公開 / Publicar.
api.description pipeline looks like an ApiEndpointSchema.type, is not one (flow/script/object_operation/proxy) — rendered.
book.description groups a key that takes an array, so no rendered word can land in it — the rule round 6 used to clear timeoutMs.

"Capability" in three positions, decided two ways

  • hook/action.fields.body.capabilities.label — a HookBodyCapability token list. Round 6 moved both to 能力 / ケイパビリティ. Untouched, and asserted so: the cross-panel invariant stays at 30/30.
  • object.sections.capabilities.label — the object's feature toggles, a different concept under the same English stem. Still 功能开关 / 機能 / Capacidades, deliberately, and asserted so.
  • capability.label — the ADR-0066 metadata type, whose instances are named authorization capability keys (export_data, billing.refund, from the schema's own name-regex message). Takes 能力 / ケイパビリティ / Capacidad — agreeing with the landed pair by re-deriving from the same objects-catalog evidence (sys_user.fields.ai_access.help, sys_email.fields.attachments_json.help) rather than borrowing its decision, and differing from it in Spanish number because this leaf names one capability and that one names a list.

A precedent answers only the question it contains; round 6's contained the token list, not this type.

The remainder, in keys AND leaves — two counts, two questions

family keys decidable leaves .label leaves
object.fields.enable.* (+ validations) 9 11 27
report.fields.drilldown + runtimeFilter + sections.dataset_binding 3 6 9
remaining total 12 17 36

Census re-taken on this branch's base 88920d153 and on the merged head 699e2e862:

reading base head
en string leaves / .label leaves 893 / 538 893 / 538 — same population
.label keys echoing in ALL THREE 18 (54 .label leaves) 12 (36)
decidable sibling remainder 29 17
POSITIVE CONTROL zh-CN / ja-JP / es-ES 520 / 504 / 504 526 / 510 / 510

The headline falls by 6 and the decidable remainder by 12, because this round decides six labels and six descriptions and only the labels move the headline. State which count you mean.

No key added, no key removed

Full flattened key sets compared base against head across all four bundles: 3572 entries, 0 added, 0 removed, en untouched. Both directions of the comparator proved on a probe key, each mutation landed on disk and restored:

  • added — injecting seed.__probe__ into zh-CN: base=3572 head=3573 added=1 removed=0.
  • removed — deleting zh-CN seed.description: base=3572 head=3571 added=0 removed=1.

Regenerated with pnpm i18n:extract, which rewrote the three bundles byte-identical to the entered values and dropped exactly the 12 provenance rows per locale that recorded these leaves as unauthored extractor fills, adding none. metadataForms.TYPE.PROP is now empty in all three tables where the base held exactly these twelve.

Ablations — 6 runs, 11 distinct assertions, 5 mutation targets across 4 files

Every one through scripts/ablation-replace.mjs: the anchor had to hit and fall, the blob had to change, and each restore is proved by blob-equals-HEAD plus an empty git diff HEAD.

# mutation reds
A zh-CN seed.label refilled with its en source zh-CN: every decided leaf matches its verdict; no leaf in the bare class reads its en source unless the ledger decided it is an echo
B zh-CN job.label refilled — a leaf no row decides DARK, INWARD — four members of the population come back NON-ECHOING; the same class rule. This is the proof the substitute property is real.
C en catalog Seed Data reworded to Seed data every row is pinned to the live en source; the echo predicate can say "echo". The registry leg stays green — the two legs are independent.
D the registry entry in packages/spec reworded, spec rebuilt exactly one: …and to the REGISTRY entry that manufactures it — the third leg. The catalog pin stays green.
E rename made a legal TransformType in packages/spec, spec rebuilt rename is NOT a TransformType — but map is, and "field mapping" contains it
F the derived population swapped for the hand-list rounds 5/6 assumed the BARE predicate splits that population, and is read off the SHAPE not a name list; DARK, INWARD; this class is now DONE.

Two disclosures, volunteered.

  1. The first key-set negative control was refused by the tool — "the anchor count moved 1 to 1, a drop of 0, not the declared 1" — because the replacement re-contained the anchor. It was re-authored as a true replacement (and the removal direction re-authored again into --delete mode when the replacement count would not rise), not re-run until something landed.
  2. Ablation D's first dist marker was Seed data, which pre-exists in packages/spec in 12 unrelated places ('Seed data loader configuration', 'Seed data records', doc comments). Its --absent restore leg read RED as the tool's own header warns it can — a surviving hit that was never mine — and, worse, its present leg would have passed even had the mutation never reached dist. That reading was void, the marker was re-authored to the discriminating type: "seed", label: "Seed data", and D was re-run from scratch. Both spec-side ablations then carried a real dist preflight (marker present in 4 / in 20 built files) and a real restore leg (marker absent from all 216 / 218 built files, tree clean).

Because this ledger imports @objectstack/spec/kernel — unaliased, so it resolves through spec/dist — the spec-side ablations do need a rebuild, and each carries one. That is a difference from round 6, whose ledger reached only sibling sources; it is stated rather than inherited.

Merge with origin/main, two-legged

origin/main moved from 88920d153 to b3615f1a4 while this round ran, so the check was not vacuous — and the two commits (#19538, #19513) touch nothing in packages/platform-objects/src/apps/translations/.

  • Leg 1 — something really moved: git diff --stat 88920d153 origin/main = 10 files, +262 / -60, including packages/spec/src/data/object.zod.ts.
  • Leg 2 — it survived: after the merge, git diff origin/main HEAD is empty on all 10 of those paths. Nothing was reverted.

The merged head was then rebuilt and every reading below re-taken on it.

Gates — 60 derived / 60 run / 0 NOT MEASURED / 0 UNRUN

Reconciled by scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran, with every family recorded as COMMAND :: exit CODE and the exit code captured before any pipe, so the zero is derived rather than claimed:

60 derived famil(ies) accounted for — 60 run, 0 NOT-MEASURED (a DERIVED zero — all 60 recorded an exit code and none of them is 3).

Plus, on the merged head 699e2e862: dependency-closure build exit 0; pnpm --filter @objectstack/platform-objects test exit 0 — 51 files / 770 passed (+1 file, +37 tests vs round 6); typecheck exit 0; pnpm check:i18n exit 0; repo-wide pnpm lint exit 0 over 6970 files, 0 messages — the full population, so no narrowing is claimed and none is owed.

Three exit 3s, all discharged by building the closure the gate named, then re-run: check:dual-build-cjs-loads and check:i18n both wanted a full build (turbo run build --concurrency=2 --filter='!@objectstack/docs', 73 tasks, exit 0); check:type-check-debt named @objectstack/platform-objects specifically. All three then exit 0, and the final sweep on the merged head produced no exit 3 at all.

The -- forwarding trap was avoided by construction: every build in this round is spelled pnpm exec turbo run build --concurrency=2 --filter=....

Changeset — measured, not assumed

@objectstack/platform-objects declares no private and ships files: ["dist", ...], so skip-changeset is measurably wrong. Probed in dist after a real build, with controls:

probe raw \uXXXX lower \uXXXX UPPER \xNN lower \xNN UPPER
种子数据 (zh seed.label) 0 0 6 0 0
ケイパビリティ (ja capability.label) 0 0 42 0 0
Documentación del paquete (es) 0 0 0 0 6
Endpoint API (pure ASCII) 6 6 6 6 6
NEGATIVE CONTROL — non-ASCII never written 0 0 0 0 0
NEGATIVE CONTROL — ASCII never written 0 0 0 0 0

CJK ships as UPPER-CASE \uXXXX; U+0080–U+00FF as UPPER-CASE \xNN. Both negative controls read 0 everywhere, so a zero on the raw probe reads "escaped", not "absent". The pure-ASCII probe reads 6 in every column because its escaped spellings are identical to its raw one — the sanity note that the counter is counting. patch.

Acceptance notes

Zero cards filed, and that is a reading, not a silence. The leaf that could have carried a known trap is doc.description / capability.description's opening word Package: it is a legal MetadataProvenanceSchema value, both schemas really do accept _provenance: "package" and refuse a rendered one, and the check was performed at the schema and then pinned in the ledger, together with the derived assertion that the en metadata-forms catalog names no underscore-prefixed key anywhere. None of the five schema readings produced a defect: four of the words are not enum members at all, and the fifth is one an author never writes on these types because these types have no form.

Two observations, neither filed, both prose here because neither carries a class or a carrier:

  • permission.fields.systemPermissions.helpText renders "system capability keys" as システム機能キー in ja-JP, using the word round 6 measured as wrong for a capability token and replaced on the hook and action panels. It is not an echo — it is authored — so it is outside this card's family, and it is not a defect against any declared contract: no runtime reads it. Whether the objects-catalog rendering (ケイパビリティ) should reach the permission panel is a wording question for whoever takes that panel. 承接者:无 — no open PR and no queued family touches packages/platform-objects/src/apps/translations/ on the permission entry, so nothing is scheduled to pass through that file. Dedupe words: permission systemPermissions capability 機能 ja-JP.
  • es-ES answers "permission sets" two ways in the same catalog — conjuntos de permisos (permission.sections.identity.description) and the English permission sets (position.sections.position.description). This round took the Spanish form, because the leaf it describes is the permission domain's own definition side, and the row records the split rather than harmonising the other leaf. Not a defect; a recorded divergence. Dedupe words: es-ES permission sets conjuntos position panel.

One scratch file (packages/spec/probe-tmp.mts, an exploratory census probe) was swept into a commit by a git add -A and removed in its own commit before the merge; the final diff is 8 files, all in packages/platform-objects/src/apps/translations/ plus the changeset.


Generated by Claude Code

…three locales

Round 7 of the en-echo decision series — the 12 leaves of the six bare
metadata-type display pairs (`seed`, `mapping`, `api`, `doc`, `book`,
`capability`). Leaf values only; the extract regeneration and the
decision ledger follow.

Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE
Co-authored-by: Claude <noreply@anthropic.com>
…round-7 renderings

`pnpm i18n:extract` dropped exactly the 12 decided leaves' provenance rows
in each of the three locales and added none; the metadata-forms bundles came
back byte-identical to the hand-entered values.

Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE
Co-authored-by: Claude <noreply@anthropic.com>
…s a ledger

One row per string leaf with its per-locale verdict, the reason, and the `en`
source — pinned to the live catalog AND to the registry entry that manufactures
it. The population is derived from `DEFAULT_METADATA_TYPE_REGISTRY` crossed
with a bare-shape predicate, with dark controls outward (17 panel types,
`dataset` by name) and inward (four bare types that come back non-echoing).

Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE
Co-authored-by: Claude <noreply@anthropic.com>
`@objectstack/platform-objects` is publishable (`files: [dist, ...]`, no
`private`) and the rendered strings were measured in `dist`: CJK ships as
UPPER-CASE \uXXXX escapes, U+0080-U+00FF as UPPER-CASE \xNN, with raw
non-ASCII and ASCII negative controls both reading 0 — so a zero on the raw
probe reads 'escaped', not 'absent'. patch, not skip-changeset.

Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE
Co-authored-by: Claude <noreply@anthropic.com>
An exploratory tsx probe under `packages/spec/` was swept in by a `git add -A`
during the round's census work. It is not part of the change.

Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling labels Sep 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot 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

Coarse fallback — 3 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 b3615f1a4cd7f3ff59ff0548530daa042627f732packageMentionDocs.

Which tree this was computed on

This run read content/docs from cfa24579c115ea8c6c1612cdf62d530492d7e67f — the merge of head 699e2e8624ad06c7a07c614e9c8368a045479459 into base b3615f1a4cd7f3ff59ff0548530daa042627f732, 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 cfa24579c115ea8c6c1612cdf62d530492d7e67f && git checkout cfa24579c115ea8c6c1612cdf62d530492d7e67f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b3615f1a4cd7f3ff59ff0548530daa042627f732 699e2e8624ad06c7a07c614e9c8368a045479459 && git checkout -B drift-repro b3615f1a4cd7f3ff59ff0548530daa042627f732 && git merge --no-ff 699e2e8624ad06c7a07c614e9c8368a045479459

node scripts/docs-audit/affected-docs.mjs --json b3615f1a4cd7f3ff59ff0548530daa042627f732

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

@huangyiirene
huangyiirene marked this pull request as ready for review September 21, 2026 10:26
@huangyiirene
huangyiirene added this pull request to the merge queue Sep 21, 2026
Merged via the queue into main with commit 4d0bd23 Sep 21, 2026
36 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-19403-round7-en-echoes branch September 21, 2026 10:47
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/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants