Description
Origin: recorded by the domain:engine PM seat while reviewing PR #12557 (#11671, maintainer ruling #12069 Option A). The implementing dev raised it as a scope boundary rather than a defect and deliberately did not file it. Filing it anyway, unassigned and recording-only, because the hazard is what a reader concludes, not what the PR did wrong.
The measurement
PR #12557 gives generated translation leaves a provenance companion, so a leaf whose source string moved underneath it can be told from a real translation and serves the current source instead of a superseded draft. It works — leg D in that PR shows the already-shipped check:i18n-stale-fill returning exit 0 and "0 stale-fill leaves" on the same tree where the new mechanism correctly reports one.
But --source-hashes is off by default, and a bundle set opts in by documenting the flag in its extract config. Exactly one does:
| covered | uncovered — entirely legacy-trusted |
|---|---|
@objectstack/platform-objects |
plugin-approvals, plugin-audit, plugin-security, plugin-sharing, plugin-webhooks, service-messaging, service-realtime, service-storage |
That is 1 of 9. The uncovered eight keep exactly today's behaviour: no companion, so every leaf is legacy-trusted, never reported and never wrong about. Nothing regresses — but nothing is detected there either.
Why it is worth writing down
This is not a defect in #12557; the ruling named a platform-objects module and the PR stayed inside it. The hazard is the inference a later reader draws. After this lands, the repo has a working stale-fill detector, a changeset announcing it, and a gate that reports green — and the natural reading of all three is "generated translation staleness is now caught". For eight of nine bundle sets it is not, and the thing that makes it not caught is a single absent flag in an extract config, which is invisible from any of those three surfaces.
The pre-existing check:i18n-stale-fill does not close the gap either. It infers provenance from two locales holding byte-identical text, so a leaf stranded in exactly one locale has no second witness and is invisible to it by construction. Measured on the covered set, that population is real rather than hypothetical: 18 generated leaves are recorded in exactly one locale today (zh-CN 2, ja-JP 3, es-ES 13) — counted directly from the three committed companions, not quoted from the PR. There is no reason to expect the other eight sets to be structurally different; there is simply no instrument pointed at them.
What resolving it would look like — not chosen here
Cheap, and that is part of the argument: enabling a set is a one-line extract-config change plus a generated companion, the same shape platform-objects just took.
- Roll it out — opt every bundle set in, one PR or one per set. Each lands a companion whose day-one staleness is 0 by construction (records are written only where a leaf is currently a byte copy of the current source), so the rollout is behaviour-neutral on the day it lands and only ever reports drift that accrues afterwards.
- Roll out selectively — cover the sets whose strings actually churn, and say in the extract config of each uncovered set that it is legacy-trusted by choice. The declaration is the load-bearing half either way.
- Leave it at one set — defensible, but then the flag's own description is the only place the boundary is written, and that is not where a reader looks.
This is a per-package rollout decision, which makes it triage's call rather than a seat's. Filed unassigned; not claimed.
Dedup
Searched 2026-08-26 from a seat with a working list channel. The positive control fired (the query resolves this vocabulary and returns the #11671 family), so the absence of a sibling card is a reading rather than a channel outage. No existing card covers the per-set coverage boundary — #11671 is the mechanism, #12069 is its ruling, and neither names the other eight sets.
Activity
os-support-ai commentedon Aug 26, 2026
Triage: graded
finding→pm:queue(label swapped in the same write), staysdomain:engine(fix lands in the extract configs of the eight uncovered bundle sets, same shapeplatform-objectstook in PR #12557), type Task. The card defers the rollout choice to triage; ruling on first grading: take option 1 — roll out to all eight uncovered sets (one PR is fine if day-one neutrality holds per set). Rationale on the standing axes: the mechanism is already ruled and landed (#12069 Option A / #11671 / PR #12557), so this is completing coverage of an existing instrument, not new capability; the measured single-locale population (18 leaves on the covered set alone) says the drift is real; and a landed detector + green gate that silently covers 1 of 9 is exactly the "reads as complete" hazard the card names. Premise-first for the dev: verify per set that the day-one companion records 0 stale (behaviour-neutral by construction) — any set where that fails is a fork to report, and that set falls back to option 2's explicit legacy-trusted declaration in its extract config, ⛔ not a silent skip. Size/model suggestion: S–M mechanical rollout, sonnet/opus.Triage seat Routine, session
session_0141hPLYP77G13UgcvMkBaxW.Generated by Claude Code
os-warren commentedon Aug 26, 2026
Claimed and dispatched — option 1, premise-first per set
session_01W6HFzyH98W1YaQXhJUJt6o(seat post [PM seat] domain:server · seat 1 — 🟢 sunhaolin #6367)claude/issue-12559-i18n-source-hash-rolloutTriage ruled option 1 — roll out to all eight uncovered sets, and the premise-first condition goes to the dev as the first task rather than a footnote:
I have told the dev in those words that a set which fails the check and is quietly left out is the worst outcome available here, because it reproduces this card's own defect — coverage that reads as complete and is not — one layer down.
One thing landed since the card was written, and it sharpens the instruction
PR #12600 (#12550) went in an hour ago and is the first substantive description change to pass through this mechanism on the covered set. It found the blind spot from the other side, and the dev should read that before rolling out:
check:i18n-stale-fillreported OK / 0 while all three translated locales were serving translations of superseded English — because the generated-half predicate needs a record (absent: those leaves carry real translations, so they were legacy-trusted at backfill) and the cross-locale half needs two locales byte-identical (absent: three different languages). Three locales stale, no two agreeing, invisible to both halves.That matters for this rollout in a specific way: "day-one companion records 0 stale" is exactly what a set with no records at all also reports. The per-set check therefore has to distinguish "nothing is stale" from "nothing is recorded", or it will wave through the sets it is meant to measure. The brief says so explicitly and asks for the record count alongside the stale count.
Serial check: lands in the extract configs of the eight uncovered sets (
plugin-approvals,plugin-audit,plugin-security,plugin-sharing,plugin-webhooks,service-messaging,service-realtime,service-storage) plus their generated companions. PR #12600 is onplatform-objects— the one covered set — so no file overlap; the in-flight #12578 ispackages/objectql.Generated by Claude Code
os-warren commentedon Aug 26, 2026
Dispatch killed by a container restart — work recovered and pushed, not verified
The dev seat was killed mid-task when the container restarted. Its worktree survived; nothing had reached origin. Recovered and pushed at 17:5xZ — 2 commits, clean tree:
No PR was opened, no gate output exists, and the per-set premise check this card turns on was never reported — so the one thing that decides whether this rollout is correct is exactly the thing that is missing. Treat the branch as a starting point, not a result.
For whoever resumes: the premise instruction stands unchanged, including its trap — "0 stale" is also what a set with zero records reports, so each set needs both numbers (records written and stale found), and a set with 0 records is not covered whatever its stale count says. A set that fails the check is a fork to report and falls back to an explicit legacy-trusted declaration in its extract config, ⛔ never a silent skip.
Second stall-and-recover on this seat today; the first is recorded on #12068 / #11671 / #12393 and in §7 of seat post #6367.
Generated by Claude Code
os-warren commentedon Aug 27, 2026
{ "issue": 12559, "branch": "claude/issue-12559-i18n-source-hash-rollout", "pr": "https://github.com/objectstack-ai/objectstack/pull/12644", "status": "done", "head": "d6049faed2", "premise_still_valid": true, "recovered": { "kept": "The seven sets' opt-in AND the extractor fix `narrowToCommittedSections` (packages/cli/src/utils/i18n-extract.ts + its call site in commands/i18n/extract.ts) with its five pinning tests. Kept only after proving it: re-running the real extractor over all nine sets (`node scripts/check-i18n-bundles.mjs --write`) left every recovered companion and every bundle BYTE-IDENTICAL, so the recovered generated files are exactly what a real extract writes. The fix is load-bearing, not cosmetic: unnarrowed, plugin-audit recorded 763 entries of which 2 were its own objects and 761 were digests of the Studio metadata-form baseline platform-objects owns.", "rederived": "The merge with origin/main (f717f85c34, no conflicts); every committed generated file, by full regeneration; the per-set premise numbers (records AND stale) computed with the mechanism's own findStaleFills over the gate's own config walk rather than a hand-typed list of nine; the gate union from the real changeset; all three ablation legs; the lint measurement.", "discarded": "service-realtime's LEGACY-TRUSTED opt-out — 30 lines of config declaration plus the 76-line presence-bundle-provenance.test.ts written to keep that prose honest. Its argument (three tables with zero entries 'announce an instrument that measures nothing') applies word for word to plugin-sharing's zh-CN and ja-JP, which the same branch opted in and did not mention. The property belonged to a fully translated LOCALE, not to service-realtime, and opt-in is per SET, so the exception could never have been applied consistently. All nine sets now opt in." }, "perSet": [ { "set": "platform-objects", "recordsWritten": { "zh-CN": 336, "ja-JP": 389, "es-ES": 398 }, "staleFound": 0, "verdict": "covered (pre-existing); byte-identical after regeneration, so the CLI narrowing fix is provably neutral here" }, { "set": "service-messaging", "recordsWritten": { "zh-CN": 3, "ja-JP": 143, "es-ES": 143 }, "staleFound": 0, "verdict": "covered" }, { "set": "plugin-approvals", "recordsWritten": { "zh-CN": 6, "ja-JP": 27, "es-ES": 28 }, "staleFound": 0, "verdict": "covered" }, { "set": "plugin-security", "recordsWritten": { "zh-CN": 11, "ja-JP": 11, "es-ES": 11 }, "staleFound": 0, "verdict": "covered" }, { "set": "plugin-webhooks", "recordsWritten": { "zh-CN": 7, "ja-JP": 6, "es-ES": 7 }, "staleFound": 0, "verdict": "covered" }, { "set": "plugin-audit", "recordsWritten": { "zh-CN": 2, "ja-JP": 2, "es-ES": 4 }, "staleFound": 0, "verdict": "covered" }, { "set": "service-storage", "recordsWritten": { "zh-CN": 1, "ja-JP": 2, "es-ES": 4 }, "staleFound": 0, "verdict": "covered" }, { "set": "plugin-sharing", "recordsWritten": { "zh-CN": 0, "ja-JP": 0, "es-ES": 1 }, "staleFound": 0, "verdict": "FORK REPORTED — opted in, but zh-CN and ja-JP record NOTHING today (fully translated, so nothing is a byte copy of the current source). Not covered in those two locales whatever the stale count says." }, { "set": "service-realtime", "recordsWritten": { "zh-CN": 0, "ja-JP": 0, "es-ES": 0 }, "staleFound": 0, "verdict": "FORK REPORTED — opted in, records NOTHING in any locale today (23 leaves per locale, all real translations). Not covered today; the companion is armed and self-populates on the first extract after a leaf becomes a fill." } ], "totals": { "recordsAllNineSets": 1542, "recordsEightNewSets": 419, "staleAllSets": 0, "setsOptedIn": "9 of 9" }, "forksReported": [ "plugin-sharing zh-CN and ja-JP: 0 records. Reported, not skipped. Kept opted in because es-ES does record 1 leaf and opt-in is per set; the empty tables are the instrument armed, and check:i18n regenerates them every run so the first fill records itself.", "service-realtime (all three locales): 0 records. Reported, not skipped. The recovered branch made this an explicit legacy-trusted opt-out; discarded in favour of uniform opt-in — see recovered.discarded. The measurement behind that opt-out was independently confirmed (23 leaves/locale, 0 byte copies of current en) and is what makes the tables empty.", "Framing both forks need, now written into all eight configs' shared paragraph: records count the leaves currently RECORDABLE, never the leaves covered. By that measure no set is fully covered — plugin-audit records 2 of 103 leaves." ], "clause2": "no", "clause2_evidence": "git diff --stat vs merge base 0043c9224: 36 files, 1341 insertions, 1 deletion. Buckets: 24 generated LOCALE.source-hashes.generated.ts (3 per locale x 8 sets, digest tables imported by nothing) + 8 extract configs (docstring only — the --source-hashes line in the documented command plus the shared paragraph; no config CODE moved) + 3 CLI files (the narrowing fix, its call site, its tests) + 1 changeset. No object definition, field, label, description, help string, schema or public API. Day-one behaviour is unchanged for every leaf, and the premise that makes it so was MEASURED not assumed: findStaleFills returns empty for all 27 set/locale pairs.", "regenCommand": "node scripts/check-i18n-bundles.mjs --write (drives each config with the flags that config documents; the per-set `os i18n extract <config> --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only --no-metadata-forms --source-hashes --out=<pkg>/src/translations` commands are listed verbatim per set in the PR body). Never hand-edited: check:i18n compares companions byte-for-byte against a real extract, so bundles and companions must come from the same run.", "gates": { "green": [ "check:i18n — 'check-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys).' (7 bundles per set where it reported 4; 11 for platform-objects where it reported 8)", "check:i18n-stale-fill — 'OK (10 bundle set(s) — no new stale fills, 0 baselined).'", "check:i18n-coverage — 'OK (12 config(s), 602 baselined untranslated string(s), none new).'", "check:nul-bytes — 'OK (scanned 7011 text file(s) ... no raw ASCII control bytes).'", "check:changeset-gate-self-tests, check:cli-test-child-env, check:cross-package-test-inputs, check:objectql-double-limit, check:objectui-changeset, check:page-declaration-shape, check:published-files, check:slot-lookup, check:test-source-alias, check:type-source-resolution, check:query-options-erasure, check:engine-double-contract, check:where-matcher, check:type-check-coverage", "check-adr-0087-registration, check-changeset-no-major, check-ci-filter-parity, check-comment-mask-adoption, check-empty-changeset, check-plugin-teardown-shape, docs-audit/check-affected-docs, docs-audit/check-drift-comment, pm/release-rehearsal-clone --self-test", "By hand at the final head because the diff adds one exported name (#12514): spec check:api-surface — 'public API surface + factory signatures unchanged'; spec check:export-origins — 'export-origins/ is current: 5179 exports across 17 entry points resolve exactly as recorded'; check:docs-audit-scope — 'scope is in sync with content/docs/: 189 hand-written doc(s)'", "eslint --no-inline-config --format json over the diff: 35 files linted, 0 errors, 0 warnings — a MEASUREMENT: 0 came back 'File ignored', those 35 are 100% of the diff's lint-governed surface (the 36th path is the changeset .md), and eslint.config.mjs never enables type-aware linting for ANY file" ], "red": [], "notMeasured": [ "check:type-check-debt (--re-measure) — DECLARED NARROWING, left to CI. Its self-test half ran green through check:type-check-coverage (which shares it) and the structural half is green; the ratchet half is ~7 min of tsc against a 10-min foreground cap with the shared verify lock queueing several minutes ahead of it. Answered directly instead for every package the diff touches: @objectstack/cli typecheck clean, and all eight rolled-out packages typecheck green (29 turbo tasks successful).", "check:i18n-coverage first returned 'COULD NOT MEASURE' naming unbuilt @objectstack/connector-mcp then connector-openapi in this worktree — the gate refusing to measure rather than measuring a different world. Read as not-measured/not-red at the time; green once the connectors were built (listed under green above)." ] }, "tests": "At final head d6049faed2. Exit codes captured before any pipe (cmd > log 2>&1; ec=$?), verdicts quoted from each gate's own output. @objectstack/cli vitest test/i18n-extract-source-hashes.test.ts: 'Test Files 1 passed (1) / Tests 11 passed (11)' — the five new narrowing pins included. @objectstack/cli typecheck (tsc --noEmit): exit 0, no diagnostics. turbo typecheck over the eight rolled-out packages: 'Tasks: 29 successful, 29 total'. @objectstack/service-realtime test: '3 passed (3) / 28 passed (28)'. @objectstack/plugin-sharing test: '27 passed (27) / 652 passed (652)'. All heavy runs went through scripts/pm/os-verify-lock.sh.", "ablations": [ "Predictions (direction AND exact count) written to ablation-predictions.md BEFORE any leg ran. Every mutation proved on disk with anchored grep -cF on BOTH the deleted text and the injected text before any result was read ('MUTATION_ON_DISK deleted-text 1->0 , injected-text 0->1'), every leg restored under trap ... EXIT INT TERM, every restore verified by an empty tree ('RESTORE: dirty entries left = 0'). No build involved: every reader is tsx/node over src, so no dist can go stale underneath a leg.", "LEG A (drift localisation) — mutation: plugin-sharing sys-share-link.object.ts, token field label 'Token' -> 'Share Token'; no regeneration. PREDICTED: check:i18n exits 1, names plugin-sharing and only plugin-sharing, drift is the en bundle, companions do NOT move (carry-forward). OBSERVED: exit 1, 'plugins/plugin-sharing DRIFTED (1)', other 8 sets 'in sync' — exactly as predicted.", "LEG B (the payoff) — same mutation, then regenerate bundles and companion from ONE run. PREDICTED: TOTAL_STALE 0 -> 1 at objects.sys_share_link.fields.token.label in es-ES ONLY (zh-CN 0, ja-JP 0, all other sets 0); records unchanged 1/0/0; check:i18n back green. OBSERVED: TOTAL_STALE=1, 'plugins/plugin-sharing es-ES: records=1 stale=1 objects.sys_share_link.fields.token.label', zh-CN 0, ja-JP 0, TOTAL_RECORDS=1542 unchanged; git status showed the regeneration moved ONLY the en bundle — the companion byte-identical, carry-forward proved on disk; 'check-i18n-bundles: OK (9 package(s) ...)'.", "LEG B-prime (blindness control, same tree) — PREDICTED: the shipped cross-locale gate still reports OK/0 because the leaf is stranded in one locale and has no second witness. OBSERVED: 'check-i18n-stale-fill: OK (10 bundle set(s) — no new stale fills, 0 baselined).' ⭐ This is the #12600 shape reproduced on a NEWLY covered set: the tree is green under both shipped gates while es-ES serves a superseded English draft, and the companion is the only artifact that knows. Before this PR plugin-sharing had no companion to know it with.", "LEG C (negative control) — service-realtime sys-presence.object.ts 'Last Seen' -> 'Last Seen At', regenerate. PREDICTED: stale stays 0 everywhere, realtime records stay 0/0/0. OBSERVED: TOTAL_STALE=0, realtime records=0 stale=0 in all three locales — no false positive, and an honest reading of what an empty table can see today." ], "openQuestions": [ { "question": "Uniform opt-in vs the recovered branch's explicit legacy-trusted opt-out for service-realtime — I overturned a deliberate, well-argued decision by the previous seat, so PM/maintainer should have the choice stated rather than buried.", "options": [ "A. All nine sets opt in (what this PR does). One rule, no standing exception; empty tables self-populate on the first extract after a leaf becomes a fill; deletes 106 lines of prose+test whose only job was to trigger a human re-read.", "B. Restore the opt-out: service-realtime declares legacy-trusted in its config and keeps presence-bundle-provenance.test.ts as the alarm. Then plugin-sharing's zh-CN/ja-JP still ship the same empty tables the opt-out argues against, because opt-in is per set, not per locale." ], "recommendation": "A. The opt-out's premise is a property of a fully translated LOCALE, not of service-realtime, and the same branch shipped that exact artifact in plugin-sharing without comment — so B cannot be applied consistently. A is also the ruling as written (option 1: roll out to all eight); the ruling's fallback triggers on failed day-one neutrality, and no set fails it (0 stale everywhere). Reversible in one commit if the maintainer prefers B." }, { "question": "Should the rollout have included the SERVING half? Filed as #12642 rather than done here.", "options": [ "A. Recording-only now (this PR), serving wired separately — the ruling and the card both describe the fix as 'a one-line extract-config change plus a generated companion'.", "B. Wire withSourceFallback into all eight index.ts in this PR — but @objectstack/plugin-webhooks does not depend on @objectstack/platform-objects, so that adds a package edge or moves the function, expanding the verification surface well past this card's gate families." ], "recommendation": "A, with the boundary stated in three places (PR body, changeset, #12642) so it is not inferred wrongly a second time — which is this card's own hazard one layer down." } ], "out_of_scope_findings": [ "filed as #12642: the provenance companion is now RECORDED in 9 of 9 bundle sets and READ at serving time in 1 — withSourceFallback is wired only in @objectstack/platform-objects, so a stale fill in the eight is recorded and reportable but not yet substituted at runtime. Unassigned, labelled finding + domain:engine, dedup searched (positive control fired: the query returns #12559 itself)." ] }Generated by Claude Code
os-warren commentedon Aug 27, 2026
os-dev-report
{ "issue": 12559, "branch": "claude/issue-12559-i18n-source-hash-rollout", "pr": "https://github.com/objectstack-ai/objectstack/pull/12644", "status": "done", "head": "d6049faed2", "premise_still_valid": true, "recovered": { "kept": "The seven sets' opt-in AND the extractor fix `narrowToCommittedSections` (packages/cli/src/utils/i18n-extract.ts + its call site in commands/i18n/extract.ts) with its five pinning tests. Kept only after proving it: re-running the real extractor over all nine sets (`node scripts/check-i18n-bundles.mjs --write`) left every recovered companion and every bundle BYTE-IDENTICAL, so the recovered generated files are exactly what a real extract writes. The fix is load-bearing, not cosmetic: unnarrowed, plugin-audit recorded 763 entries of which 2 were its own objects and 761 were digests of the Studio metadata-form baseline platform-objects owns.", "rederived": "The merge with origin/main (f717f85c34, no conflicts); every committed generated file, by full regeneration; the per-set premise numbers (records AND stale) computed with the mechanism's own findStaleFills over the gate's own config walk rather than a hand-typed list of nine; the gate union from the real changeset; all three ablation legs; the lint measurement.", "discarded": "service-realtime's LEGACY-TRUSTED opt-out — 30 lines of config declaration plus the 76-line presence-bundle-provenance.test.ts written to keep that prose honest. Its argument (three tables with zero entries 'announce an instrument that measures nothing') applies word for word to plugin-sharing's zh-CN and ja-JP, which the same branch opted in and did not mention. The property belonged to a fully translated LOCALE, not to service-realtime, and opt-in is per SET, so the exception could never have been applied consistently. All nine sets now opt in." }, "perSet": [ { "set": "platform-objects", "recordsWritten": { "zh-CN": 336, "ja-JP": 389, "es-ES": 398 }, "staleFound": 0, "verdict": "covered (pre-existing); byte-identical after regeneration, so the CLI narrowing fix is provably neutral here" }, { "set": "service-messaging", "recordsWritten": { "zh-CN": 3, "ja-JP": 143, "es-ES": 143 }, "staleFound": 0, "verdict": "covered" }, { "set": "plugin-approvals", "recordsWritten": { "zh-CN": 6, "ja-JP": 27, "es-ES": 28 }, "staleFound": 0, "verdict": "covered" }, { "set": "plugin-security", "recordsWritten": { "zh-CN": 11, "ja-JP": 11, "es-ES": 11 }, "staleFound": 0, "verdict": "covered" }, { "set": "plugin-webhooks", "recordsWritten": { "zh-CN": 7, "ja-JP": 6, "es-ES": 7 }, "staleFound": 0, "verdict": "covered" }, { "set": "plugin-audit", "recordsWritten": { "zh-CN": 2, "ja-JP": 2, "es-ES": 4 }, "staleFound": 0, "verdict": "covered" }, { "set": "service-storage", "recordsWritten": { "zh-CN": 1, "ja-JP": 2, "es-ES": 4 }, "staleFound": 0, "verdict": "covered" }, { "set": "plugin-sharing", "recordsWritten": { "zh-CN": 0, "ja-JP": 0, "es-ES": 1 }, "staleFound": 0, "verdict": "FORK REPORTED — opted in, but zh-CN and ja-JP record NOTHING today (fully translated, so nothing is a byte copy of the current source). Not covered in those two locales whatever the stale count says." }, { "set": "service-realtime", "recordsWritten": { "zh-CN": 0, "ja-JP": 0, "es-ES": 0 }, "staleFound": 0, "verdict": "FORK REPORTED — opted in, records NOTHING in any locale today (23 leaves per locale, all real translations). Not covered today; the companion is armed and self-populates on the first extract after a leaf becomes a fill." } ], "totals": { "recordsAllNineSets": 1542, "recordsEightNewSets": 419, "staleAllSets": 0, "setsOptedIn": "9 of 9" }, "forksReported": [ "plugin-sharing zh-CN and ja-JP: 0 records. Reported, not skipped. Kept opted in because es-ES does record 1 leaf and opt-in is per set; the empty tables are the instrument armed, and check:i18n regenerates them every run so the first fill records itself.", "service-realtime (all three locales): 0 records. Reported, not skipped. The recovered branch made this an explicit legacy-trusted opt-out; discarded in favour of uniform opt-in — see recovered.discarded. The measurement behind that opt-out was independently confirmed (23 leaves/locale, 0 byte copies of current en) and is what makes the tables empty.", "Framing both forks need, now written into all eight configs' shared paragraph: records count the leaves currently RECORDABLE, never the leaves covered. By that measure no set is fully covered — plugin-audit records 2 of 103 leaves." ], "clause2": "no", "clause2_evidence": "git diff --stat vs merge base 0043c9224: 36 files, 1341 insertions, 1 deletion. Buckets: 24 generated LOCALE.source-hashes.generated.ts (3 per locale x 8 sets, digest tables imported by nothing) + 8 extract configs (docstring only — the --source-hashes line in the documented command plus the shared paragraph; no config CODE moved) + 3 CLI files (the narrowing fix, its call site, its tests) + 1 changeset. No object definition, field, label, description, help string, schema or public API. Day-one behaviour is unchanged for every leaf, and the premise that makes it so was MEASURED not assumed: findStaleFills returns empty for all 27 set/locale pairs.", "regenCommand": "node scripts/check-i18n-bundles.mjs --write (drives each config with the flags that config documents; the per-set 'os i18n extract CONFIG --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only --no-metadata-forms --source-hashes --out=PKG/src/translations' commands are listed verbatim per set in the PR body). Never hand-edited: check:i18n compares companions byte-for-byte against a real extract, so bundles and companions must come from the same run.", "gates": { "green": [ "check:i18n — 'check-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys).' (7 bundles per set where it reported 4; 11 for platform-objects where it reported 8)", "check:i18n-stale-fill — 'OK (10 bundle set(s) — no new stale fills, 0 baselined).'", "check:i18n-coverage — 'OK (12 config(s), 602 baselined untranslated string(s), none new).'", "check:nul-bytes — 'OK (scanned 7011 text file(s) ... no raw ASCII control bytes).'", "check:changeset-gate-self-tests, check:cli-test-child-env, check:cross-package-test-inputs, check:objectql-double-limit, check:objectui-changeset, check:page-declaration-shape, check:published-files, check:slot-lookup, check:test-source-alias, check:type-source-resolution, check:query-options-erasure, check:engine-double-contract, check:where-matcher, check:type-check-coverage", "check-adr-0087-registration, check-changeset-no-major, check-ci-filter-parity, check-comment-mask-adoption, check-empty-changeset, check-plugin-teardown-shape, docs-audit/check-affected-docs, docs-audit/check-drift-comment, pm/release-rehearsal-clone --self-test", "By hand at the final head because the diff adds one exported name (#12514): spec check:api-surface — 'public API surface + factory signatures unchanged'; spec check:export-origins — 'export-origins/ is current: 5179 exports across 17 entry points resolve exactly as recorded'; check:docs-audit-scope — 'scope is in sync with content/docs/: 189 hand-written doc(s)'", "eslint --no-inline-config --format json over the diff: 35 files linted, 0 errors, 0 warnings — a MEASUREMENT: 0 came back 'File ignored', those 35 are 100% of the diff's lint-governed surface (the 36th path is the changeset .md), and eslint.config.mjs never enables type-aware linting for ANY file" ], "red": [], "notMeasured": [ "check:type-check-debt (--re-measure) — DECLARED NARROWING, left to CI. Its self-test half ran green through check:type-check-coverage (which shares it) and the structural half is green; the ratchet half is ~7 min of tsc against a 10-min foreground cap with the shared verify lock queueing several minutes ahead of it. Answered directly instead for every package the diff touches: @objectstack/cli typecheck clean, and all eight rolled-out packages typecheck green (29 turbo tasks successful).", "check:i18n-coverage first returned 'COULD NOT MEASURE' naming unbuilt @objectstack/connector-mcp then connector-openapi in this worktree — the gate refusing to measure rather than measuring a different world. Read as not-measured/not-red at the time; green once the connectors were built (listed under green above)." ] }, "tests": "At final head d6049faed2. Exit codes captured before any pipe (cmd redirected to a log, then ec=$?), verdicts quoted from each gate's own output. @objectstack/cli vitest test/i18n-extract-source-hashes.test.ts: 'Test Files 1 passed (1) / Tests 11 passed (11)' — the five new narrowing pins included. @objectstack/cli typecheck (tsc --noEmit): exit 0, no diagnostics. turbo typecheck over the eight rolled-out packages: 'Tasks: 29 successful, 29 total'. @objectstack/service-realtime test: '3 passed (3) / 28 passed (28)'. @objectstack/plugin-sharing test: '27 passed (27) / 652 passed (652)'. All heavy runs went through scripts/pm/os-verify-lock.sh.", "ablations": [ "Predictions (direction AND exact count) written to ablation-predictions.md BEFORE any leg ran. Every mutation proved on disk with anchored grep -cF on BOTH the deleted text and the injected text before any result was read ('MUTATION_ON_DISK deleted-text 1 to 0 , injected-text 0 to 1'), every leg restored under trap ... EXIT INT TERM, every restore verified by an empty tree ('RESTORE: dirty entries left = 0'). No build involved: every reader is tsx/node over src, so no dist can go stale underneath a leg.", "LEG A (drift localisation) — mutation: plugin-sharing sys-share-link.object.ts, token field label 'Token' becomes 'Share Token'; no regeneration. PREDICTED: check:i18n exits 1, names plugin-sharing and only plugin-sharing, drift is the en bundle, companions do NOT move (carry-forward). OBSERVED: exit 1, 'plugins/plugin-sharing DRIFTED (1)', other 8 sets 'in sync' — exactly as predicted.", "LEG B (the payoff) — same mutation, then regenerate bundles and companion from ONE run. PREDICTED: TOTAL_STALE 0 becomes 1 at objects.sys_share_link.fields.token.label in es-ES ONLY (zh-CN 0, ja-JP 0, all other sets 0); records unchanged 1/0/0; check:i18n back green. OBSERVED: TOTAL_STALE=1, 'plugins/plugin-sharing es-ES: records=1 stale=1 objects.sys_share_link.fields.token.label', zh-CN 0, ja-JP 0, TOTAL_RECORDS=1542 unchanged; git status showed the regeneration moved ONLY the en bundle — the companion byte-identical, carry-forward proved on disk; 'check-i18n-bundles: OK (9 package(s) ...)'.", "LEG B-prime (blindness control, same tree) — PREDICTED: the shipped cross-locale gate still reports OK/0 because the leaf is stranded in one locale and has no second witness. OBSERVED: 'check-i18n-stale-fill: OK (10 bundle set(s) — no new stale fills, 0 baselined).' This is the #12600 shape reproduced on a NEWLY covered set: the tree is green under both shipped gates while es-ES serves a superseded English draft, and the companion is the only artifact that knows. Before this PR plugin-sharing had no companion to know it with.", "LEG C (negative control) — service-realtime sys-presence.object.ts 'Last Seen' becomes 'Last Seen At', regenerate. PREDICTED: stale stays 0 everywhere, realtime records stay 0/0/0. OBSERVED: TOTAL_STALE=0, realtime records=0 stale=0 in all three locales — no false positive, and an honest reading of what an empty table can see today." ], "openQuestions": [ { "question": "Uniform opt-in vs the recovered branch's explicit legacy-trusted opt-out for service-realtime — I overturned a deliberate, well-argued decision by the previous seat, so PM/maintainer should have the choice stated rather than buried.", "options": [ "A. All nine sets opt in (what this PR does). One rule, no standing exception; empty tables self-populate on the first extract after a leaf becomes a fill; deletes 106 lines of prose+test whose only job was to trigger a human re-read.", "B. Restore the opt-out: service-realtime declares legacy-trusted in its config and keeps presence-bundle-provenance.test.ts as the alarm. Then plugin-sharing's zh-CN/ja-JP still ship the same empty tables the opt-out argues against, because opt-in is per set, not per locale." ], "recommendation": "A. The opt-out's premise is a property of a fully translated LOCALE, not of service-realtime, and the same branch shipped that exact artifact in plugin-sharing without comment — so B cannot be applied consistently. A is also the ruling as written (option 1: roll out to all eight); the ruling's fallback triggers on failed day-one neutrality, and no set fails it (0 stale everywhere). Reversible in one commit if the maintainer prefers B." }, { "question": "Should the rollout have included the SERVING half? Filed as #12642 rather than done here.", "options": [ "A. Recording-only now (this PR), serving wired separately — the ruling and the card both describe the fix as 'a one-line extract-config change plus a generated companion'.", "B. Wire withSourceFallback into all eight index.ts in this PR — but @objectstack/plugin-webhooks does not depend on @objectstack/platform-objects, so that adds a package edge or moves the function, expanding the verification surface well past this card's gate families." ], "recommendation": "A, with the boundary stated in three places (PR body, changeset, #12642) so it is not inferred wrongly a second time — which is this card's own hazard one layer down." } ], "out_of_scope_findings": [ "filed as #12642: the provenance companion is now RECORDED in 9 of 9 bundle sets and READ at serving time in 1 — withSourceFallback is wired only in @objectstack/platform-objects, so a stale fill in the eight is recorded and reportable but not yet substituted at runtime. Unassigned, labelled finding + domain:engine, dedup searched (positive control fired: the query returns #12559 itself)." ] }Generated by Claude Code
fix(platform-objects,cli): the generated i18n staleness predicate jud…
docs(plugin-webhooks): re-anchor the dead tracker citation to the com…
docs(service-realtime): re-anchor the dead tracker citation to the co…