Repository navigation
fix(platform-objects): decide the lifecycle panel and the email-template sample per leaf — the whole remainder of #19403 - #19613
Conversation
…ate variables sample in ja-JP and es-ES 33 `en` leaves x 2 locales = 66 locale-leaves that shipped their English source. Values authored by hand, structure regenerated with `pnpm i18n:extract`; 0 keys added, 0 removed. Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE Co-authored-by: Claude <noreply@anthropic.com>
…two landed assertions this diff falsified New ledger decides all 33 leaves per locale with the reason and the `en` source, over two populations derived from a schema shape and a widget. Round 8's deferral is discharged and its bound tightened to the exact reading; round 9's positive sample draw is widened to the untouched `objects.` slice, which this diff emptied on the `metadataForms.` one. Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE Co-authored-by: Claude <noreply@anthropic.com>
… ledger Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
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): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1fffe7b09f1afafe6f1f444c2b679f93fe8fc6b2 && git checkout 1fffe7b09f1afafe6f1f444c2b679f93fe8fc6b2
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5c5b67fc4140f76ca3158acea9e0845d9eebfad8 0cfec25d27147558f6d380950e135f8d109a55e9 && git checkout -B drift-repro 5c5b67fc4140f76ca3158acea9e0845d9eebfad8 && git merge --no-ff 0cfec25d27147558f6d380950e135f8d109a55e9
node scripts/docs-audit/affected-docs.mjs --json 5c5b67fc4140f76ca3158acea9e0845d9eebfad8
|
Part of #19403
Clause-②: no
Round 10 of #19403, and it takes the whole remainder: every leaf in
packages/platform-objects/src/apps/translations/*.metadata-forms.generated.tsthat still echoed its
ensource in any locale.The remainder this round took — re-measured on base
236cec19aenleaveszh-CNja-JPes-ESobject.fields.lifecycle.storageobject.fields.lifecycle.archiveobject.fields.lifecycle.ttlobject.fields.lifecycle.retentionobject.fields.lifecycle.classobject.fields.lifecycle.reclaimobject.fields.lifecycle(label+helpText)email_template.fields.variables.helpText33
enleaves x 2 locales = 66 locale-leaves. Every count is this round's ownreading, taken before a word was written, and it reproduces the dispatch table exactly.
Census, before and after — with the control moving the other way
enstring leaves /.labelleaves.labelgenuinely translated (zh-CN).labelgenuinely translated (ja-JP).labelgenuinely translated (es-ES).labelechoesEchoes down 33 in each of two locales while the control rises 16 in each, same
population, same run: a discrimination, not a parser matching everything.
The remainder this round LEAVES — 0, and it is 0 for BOTH predicates
This card has been bitten twice by the difference, so both are stated:
zh-CNANDja-JPANDes-ES): 0.It also read 0 before this round, over 33 leaves a Japanese author was reading
in English. That zero carried no information.
is the one that had something to say.
Per locale and per family:
zh-CN0,ja-JP0,es-ES0; every familyin the table above reads 0 / 0 / 0. The new ledger asserts both readings on the
live tree, and proves they are different questions on a synthetic catalog where
the all-three predicate returns 0 while the per-locale one returns 1 — so the
property holds whatever the tree contains.
The judgement, which is the whole card
An echo is not automatically a defect, so all 66 are decided per leaf with the
verdict, the per-locale reason and the
ensource they were judged against, inobject-lifecycle-panel-echo-decisions.test.ts. Every verdict came outtranslate; the reasons record what each rendering was derived FROM.zh-CN's answers were used as EVIDENCE, not as a translation. They prove thetwo other locales were unauthored fills and they settle what each leaf means;
the Japanese and Spanish words come from each locale's own authored twins, and
every row says which of the two it leaned on. Three rows record a deliberate
departure from
zh-CN, with the reason:lifecycle.storage.label—zh-CNexpands it to 存储策略; an exact authored twinfor the bare word sits in this same panel's section heading
(
object.sections.advanced.description= ストレージ / almacenamiento), andexpanding would leave the child row
storage.strategyholding its parent's word.lifecycle.class.helpText—zh-CNreplaces "rows" with 数据; the schema statementis about rows specifically and both target locales have an authored word for it.
lifecycle.archive.after.label—zh-CNwrites 归档时点 (a point in time); theschema types the value as a duration literal, so both locales name an AGE and share
a head noun with
retention.maxAge, which ADR-0057 requires it to EQUAL.One schema key, one rendering — what was copied, and where nothing existed
22 of the 33 rows copy an authored twin, asserted per locale against the live
catalogs (56 locale assertions) so the two positions can only ever move together.
The sharpest ones:
lifecycle.archive.to.labelobject.fields.datasource.labellifecycle.ttl.field.labelobject.fields.fields.summaryOperations.field.labellifecycle.archive.keep.labellifecycle.retention.label, this ledger's own rowemail_template.variables.helpTextemail_template.fields.fromOverride.helpText[{ … }][{ … }]The other 11 rows say NO TWIN EXISTS rather than leaning on one. Probed and
stated:
shard,telemetry,cold store,driverand the disk-space sense ofspacehave zero authored renderings in either catalog, in either locale. Two ofthose rows also record a near twin that was looked up and refused: Japanese
領域 is spent on the layout-region sense in this catalog
(
page.sections.layout.description,app.fields.areas.label), so reusing it fordisk space would collide two concepts on one word.
One refusal is asserted in the suite so nobody leans on it later:
sys_oauth_resource.fields.access_token_ttl.label("Access Token TTL") isbyte-identical in all three locales and reads like a standing decision to keep
TTLverbatim. It is an unauthored fill in every locale,zh-CNincluded — itcarries a provenance row in all three companions. The
TTLdecision rests onzh-CN's own authored answers and on round 7's landed machine-token treatment.Machine tokens kept verbatim, with a predicate that can say NO
19 tokens x 3 locales = 57 assertions:
ADR-0057,record,audit,TTL,created_at,expires_at,retention.maxAge,h/d/w/y,"30d","1d","7y",SQLite,DROP,O(1)and the multiplication sign. The guard is a Unicodeword-boundary predicate, and the suite proves it can fail on these very strings.
Round 9's recorded limit is now executable rather than prose. The suite asserts
all four halves: a substring guard LIES on kana (
キャンセルcontainsセル); thispredicate does not make that mistake; it pays with an UNDER-REACH (a genuine
standalone
セルinside running Japanese,このセルを編集, is MISSED); and it onlysees a kana token that happens to be delimited. Sound and incomplete on kana — and
the suite also asserts that every token this ledger guards is Latin, a digit-unit
literal or a dotted path, so the limit does not bite here.
The phantom-translation trap, asserted three ways
objectFormdeclares no label on any of the sixteen lifecycle fields (asserted,all sixteen), so every
enlabel here ishumanizeFieldPathof the path — and oneis WRONG English:
lifecycle.ttlhumanizes to"Ttl", whose correct English is theinitialism
"TTL"that the form's own helpText andLifecycleSchema's own TSDocboth spell.
Correcting it would differ in bytes, satisfy the echo predicate in all three locales,
drop this card's census by a leaf and tell a Japanese author nothing. It is not done
here and it discharges nothing. The suite asserts the two English spellings differ,
that no rendering is either of them, and that the initialism survives as a token in
all three. It is written up in full in the Acceptance notes below. Round 8's
Api Enabledis the same producer and is still unfiled; this is its second instance.ADR-0057 was read at the live schema before a word was rendered
Five
itblocks assert the claims the renderings rest on, each with its refusal leg:LifecycleClassSchema's five members (sorecordandauditare values an authortypes, not words); §3.1 (a
recordclass with a policy is refused) and §3.5 (anon-
recordclass with none is refused);archive.aftermust EQUALretention.maxAge(a mismatch is refused, the aligned pair parses);
LIFECYCLE_DURATION_REGEXaccepts30d/1d/7yand refuses30 days,30 天and30 días; and storage is a COUNTplus a GRANULARITY (
shardsmin 2 refused at 1,unitenum refused atquarter,strategyliteral refused atmerge).The instrument — populations DERIVED, never hand-listed
Two populations, two shape predicates:
LifecycleSchema.shapehas sixkeys; the population is the one form composite whose DIRECT children are exactly
those six. It follows a rename of the form field, grows when ADR-0057 grows, and
can be wrong in a way a
startsWith('lifecycle')walk never could. 32 leaves.widget: 'json'onemailTemplateForm,which reaches exactly two fields and four leaves.
Controls. OUTWARD: 94+ leaves of these two forms are excluded, five object paths
and three email paths asserted by name, and nothing excluded echoes anywhere.
INWARD: 3 of the 4 email-json leaves are NOT decided here and come back
NON-ECHOING in the same walk — and one of those three is the exact structural twin
the decided row copies from. One leaf of a matched pair authored by a translator and
the other left as a byte copy: one leaf cannot be a decision and a fill at once.
DARK: fed the
encatalog, the derived predicate flags every leaf of both populations.Three-leg pin, carried from rounds 8 and 9: every row is held equal to the live
bundle, to the
ensource, AND to the form declaration that manufactures it (ahelpTextto the form's own text, alabelto the form declaring NONE).Round 8's deferral is DISCHARGED, and its bound TIGHTENED in the same act
DEFERRED_SUBTREE = 'lifecycle'withRECORDED_DEFERRAL = 32is not deleted —the shape stays, because it is the dark control that made this population visible.
The 32 leaves moved out of the deferral into decided rows,
withinRecorded(0)holds,and round 9's two legs (
oldBoundunfalsifiable, shrink-only) are kept exactly asthey were.
Tightened, not weakened: with the deferral empty, the
inDeferralskip in round8's ratchet would let a NEW lifecycle echo through unnoticed, and
<= 32over a truevalue of 0 can no longer fail. Two lines now pin the exact reading beside the
shrink-only bound — the hole round 10 created by discharging the deferral, closed by
round 10. Ablation A1 reds them on the live tree.
Two landed assertions this diff falsified — repaired in place, declared, none weakened
report-form-echo-decisions.test.ts(round 9) drew the POSITIVE half of itsprovenance discrimination from the
metadataForms.slice, and survived only becauseja-JPandes-ESstill carried this round's 33 leaves. Emptying that slice madetoBeGreaterThan(0)false in two of the three locales.The repair is round 9's own precedent applied to round 9's own file: draw the
positive sample from a population this card does not touch. The
objects.slicestill carries hundreds of echoing leaves in every locale, so the composer now shows a
real positive AND a real negative in all three locales, where before it managed
it in two. The contract assertion is untouched — what moved is the SAMPLE DRAW, not
the claim. Ablation A2 reds it.
Regeneration —
pnpm i18n:extract, never by handValues were authored by hand (the documented seam for translated-locale strings);
the structure was regenerated with
pnpm i18n:extract.Idempotence control, taken first: a no-op
pnpm i18n:extracton the untouchedbase rewrote all 11 files and left
git status --porcelainempty over the wholetree. Every later diff is therefore attributable to the edits.
No keys added, none removed, proved on full flattened key sets over all four
bundles of both catalogs:
diffexit 0, 0 deltaSecond witness — the
metadataForms.-prefixed companions, as SET EQUALITYThe provenance tables key under a
metadataForms.prefix the bundles do not carry(asserted), and hold an entry exactly while a leaf is still a byte copy of the source.
metadataForms.rows remainingzh-CNja-JPes-ESSet equality against "exactly my N leaves" reads true in all three, every dropped
key carries the prefix, and the perturbed-expectation control reads false in both
directions (+1 and -1).
One disclosure: for
zh-CNthe expected-drop set is empty, so the-1perturbationis a no-op and reads
true. That is recorded as a vacuous leg, not counted as acontrol; the
+1direction still fires forzh-CN, and both directions fire for thetwo locales that moved.
Ablations — 6, each proved on disk, 18 assertions reddened
Mutation proved by anchor count plus blob hash; restore proved by blob hash and a
whole-tree
git status --porcelain, which read clean after every one.ja-JPlifecycle.reclaim.labelto itsensourcemetadataForms.row toja-JP's provenance tableobject.fields.datasource.labelines-ESTTLtoken fromja-JPlifecycle.ttl.labelttlcomposite inobjectForm(spec, rebuilt)zh-CNlifecycle.archive.labelto itsensourceA1 reds round 8's newly tightened bound on the live tree, which the old
<= 32nevercould. A2 reds three assertions across three different ledgers. A3 reds the copy
assertion with the exact message
es-ES object.fields.lifecycle.archive.to.label no longer contains its twin object.fields.datasource.label. A6 reds the sibling-evidenceleg and five others.
A5 crosses a package boundary, so it carries the build discipline:
@objectstack/specresolves through its
exportstodistandpackages/platform-objectsdeclares novitest alias for it. Mutate,
turbo run build --force --filter=@objectstack/spec,ablation-dist-preflight packages/spec 'TTL__ABLATION__'(hits indist/browser/data/index.jsand others), then the suite. The marker did notpre-exist in
dist(checked before mutating). The restore leg rebuilt and ran thepreflight with
--absent: "marker absent from all 216 built files" and "workingtree clean against HEAD". No ablation was re-run until something landed and no
mutation was left on disk.
Verification
pnpm --filter @objectstack/platform-objects testpnpm --filter @objectstack/platform-objects typechecktsc --noEmit, scripts project, andcheck:test-typecheckscripts/pm/dispatch-gates.mjs)pnpm lint(whole repo,eslint . --no-inline-config)--format json, 0 errors, 0 warningspnpm --filter @objectstack/spec check:generatedAll of the above were re-run on the final merged head
0cfec25d2, after the mergebelow.
pnpm lintran over the entire population, so no narrowing was claimed andnone is owed.
Two families exited 3 on the pre-merge sweep —
pnpm check:i18nandpnpm check:dual-build-cjs-loads, bothPREREQUISITE NOT MET(unbuiltdist/),which is "nothing was measured", not a pass. Both discharged by a full
turbo run build --concurrency=2(74/74 tasks) and re-run to exit 0:check-i18n-bundles: OK (9 packages, all bundles in sync, no undeclared authoring keys)andcheck:dual-build-cjs-loadsover 104 entry points / 620 CJS files. On thepost-merge sweep neither reached exit 3 at all.
Changeset —
patch, and the diff really publishesProbed against the built
files[]set (dist,README.md,CHANGELOG.md), 67files, 26.5 MB. Round 9's escape reading replicates and extends: the same build
spells the same characters two different ways.
distデ), U+5BB9 (容)\uXXXXonly — raw ABSENTó)\xNNescape — two spellings, one buildja-JPlabelデータライフサイクル\uXXXXes-ESlabelCiclo de vida de los datosEvery probe was built from the character itself (
codePointAtthen padded hex inboth cases and both widths), never typed by hand — a hand-typed raw probe reads a
FALSE ZERO for the Japanese strings here.
Merge with
origin/mainMerged immediately before the final push.
origin/mainmoved from236cec19ato5c5b67fc4(one commit,#19595), and nothing it brought touches this region —git diff --name-onlyover the two refs returns no path underpackages/platform-objects/src/apps/translations/orpackages/spec/src/data/object.form.ts.Two-legged proof: base
236cec19a!=5c5b67fc4(TRUE), andgit merge-base --is-ancestor origin/main HEADexits 0, with a control leg on theknown ancestor
236cec19aalso exiting 0. The merge was clean, left noos-regendeferral, and because it moved
packages/specthe spec was rebuilt andcheck:generatedre-run (15/15 up to date) before anything else was believed.Acceptance notes
1.
lifecycle.ttlships the wrong English — class (a), reachable today, and thisround could not file it. Written out in full so a taker needs nothing else.
objectForm(packages/spec/src/data/object.form.ts) declares nolabelon thelifecycle.ttlcomposite.walkFormField(
packages/cli/src/utils/i18n-extract.ts, thepushEntryformetadataForms.TYPE.fields.PATH.label) therefore emitshumanizeFieldPath('lifecycle.ttl'), which lower-cases everything after the firstletter of the path leaf and yields
"Ttl".humanizeFieldPath('lifecycle.ttl') === 'Ttl', and the shippeden.metadata-forms.generated.tscarriesobject.fields["lifecycle.ttl"].label === "Ttl". The same file'shelpTextone line down reads "Per-row TTL expiry" andLifecycleSchema's TSDoc spellsTTLtoo, so the panel disagrees with itself onscreen. Correct English:
"TTL".predicate in every locale and drops this card's census by a leaf while translating
nothing. It must not be counted as discharging a translation row — round 8 recorded
the identical shape on
enable.apiEnabled("Api Enabled"/"API Enabled"), whichis the same producer and still unfiled.
objectForm(
label: 'TTL'on that composite), not inhumanizeFieldPath. Teaching thehumanizer an initialism list would silently change every other humanized label across
all nine packages' bundles and would re-fill
enleaves translators have alreadyanswered against the old spelling.
humanizeFieldPath initialism·Ttl label wrong English·objectForm declares no label lifecycle·Api Enabled humanize·metadata form label capitalisation.2.
*.objects.generated.tscarries its own echoing population — 362 / 383 / 392echoing against 1203 / 1182 / 1173 translated. Re-measured here and unchanged. It has
no taker (承接者:无), which is why it is written down rather than assumed to be
someone's, and it is out of this card's stated population.
3.
zh-CNrenderslifecycle.storage.labelandlifecycle.storage.strategy.helpTextwith the same string (存储策略), so in Chinese the composite and its child tooltip
read identically. Noted, not filed: it is a landed decision of an earlier hand on a
leaf this round does not own, both strings are authored, and nothing is an echo. The
ja-JPandes-ESrenderings keep parent and child distinguishable, as the English does.4. The generated-bundle merge hazard —
git check-attr mergereadsunspecifiedfor these paths, so two branches adding keys merge clean with one side silently lost.
Re-measured, not acted on: the card body already rules it an
AGENTS.mddecision.Route
Draft, and it stays draft.
Part of #19403, not a closing keyword: whether the cardcloses is a reading the PM seat takes at the landing, against the trunk, with the
census. Nothing is armed and auto-merge is not set.
Generated by Claude Code