From 2718262db3dc0765d609e0e6d866c97dff4d003b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 16:47:39 +0000 Subject: [PATCH 1/3] feat(i18n): roll the generated-leaf provenance companion out to the remaining bundle sets Seven of the eight uncovered i18n bundle sets now document `--source-hashes` in their extract config and commit the three `.source-hashes.generated.ts` companions produced by the same extract run as their bundles. The eighth, `service-realtime`, declares itself legacy-trusted with the measurement behind that choice, and a test beside its bundles fails the day the measurement stops holding. Also narrows the provenance table to the sections a run actually commits: the seven sets pass `--no-metadata-forms`, and their unrecorded `metadataForms` subtree arrived as a fresh fill of `en`, so the table carried 761 digests of a baseline `@objectstack/platform-objects` owns. `platform-objects` commits both sections, so its committed companions are unchanged. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01W6HFzyH98W1YaQXhJUJt6o --- .../i18n-source-hash-rollout-eight-sets.md | 66 +++++++ packages/cli/src/commands/i18n/extract.ts | 49 +++++- packages/cli/src/utils/i18n-extract.ts | 43 +++++ .../test/i18n-extract-source-hashes.test.ts | 60 +++++++ .../scripts/i18n-extract.config.ts | 12 ++ .../es-ES.source-hashes.generated.ts | 49 ++++++ .../ja-JP.source-hashes.generated.ts | 48 +++++ .../zh-CN.source-hashes.generated.ts | 27 +++ .../scripts/i18n-extract.config.ts | 12 ++ .../es-ES.source-hashes.generated.ts | 25 +++ .../ja-JP.source-hashes.generated.ts | 23 +++ .../zh-CN.source-hashes.generated.ts | 23 +++ .../scripts/i18n-extract.config.ts | 12 ++ .../es-ES.source-hashes.generated.ts | 32 ++++ .../ja-JP.source-hashes.generated.ts | 32 ++++ .../zh-CN.source-hashes.generated.ts | 32 ++++ .../scripts/i18n-extract.config.ts | 12 ++ .../es-ES.source-hashes.generated.ts | 22 +++ .../ja-JP.source-hashes.generated.ts | 21 +++ .../zh-CN.source-hashes.generated.ts | 21 +++ .../scripts/i18n-extract.config.ts | 12 ++ .../es-ES.source-hashes.generated.ts | 28 +++ .../ja-JP.source-hashes.generated.ts | 27 +++ .../zh-CN.source-hashes.generated.ts | 28 +++ .../scripts/i18n-extract.config.ts | 12 ++ .../es-ES.source-hashes.generated.ts | 164 ++++++++++++++++++ .../ja-JP.source-hashes.generated.ts | 164 ++++++++++++++++++ .../zh-CN.source-hashes.generated.ts | 24 +++ .../scripts/i18n-extract.config.ts | 30 ++++ .../presence-bundle-provenance.test.ts | 76 ++++++++ .../scripts/i18n-extract.config.ts | 12 ++ .../es-ES.source-hashes.generated.ts | 25 +++ .../ja-JP.source-hashes.generated.ts | 23 +++ .../zh-CN.source-hashes.generated.ts | 22 +++ 34 files changed, 1267 insertions(+), 1 deletion(-) create mode 100644 .changeset/i18n-source-hash-rollout-eight-sets.md create mode 100644 packages/plugins/plugin-approvals/src/translations/es-ES.source-hashes.generated.ts create mode 100644 packages/plugins/plugin-approvals/src/translations/ja-JP.source-hashes.generated.ts create mode 100644 packages/plugins/plugin-approvals/src/translations/zh-CN.source-hashes.generated.ts create mode 100644 packages/plugins/plugin-audit/src/translations/es-ES.source-hashes.generated.ts create mode 100644 packages/plugins/plugin-audit/src/translations/ja-JP.source-hashes.generated.ts create mode 100644 packages/plugins/plugin-audit/src/translations/zh-CN.source-hashes.generated.ts create mode 100644 packages/plugins/plugin-security/src/translations/es-ES.source-hashes.generated.ts create mode 100644 packages/plugins/plugin-security/src/translations/ja-JP.source-hashes.generated.ts create mode 100644 packages/plugins/plugin-security/src/translations/zh-CN.source-hashes.generated.ts create mode 100644 packages/plugins/plugin-sharing/src/translations/es-ES.source-hashes.generated.ts create mode 100644 packages/plugins/plugin-sharing/src/translations/ja-JP.source-hashes.generated.ts create mode 100644 packages/plugins/plugin-sharing/src/translations/zh-CN.source-hashes.generated.ts create mode 100644 packages/plugins/plugin-webhooks/src/translations/es-ES.source-hashes.generated.ts create mode 100644 packages/plugins/plugin-webhooks/src/translations/ja-JP.source-hashes.generated.ts create mode 100644 packages/plugins/plugin-webhooks/src/translations/zh-CN.source-hashes.generated.ts create mode 100644 packages/services/service-messaging/src/translations/es-ES.source-hashes.generated.ts create mode 100644 packages/services/service-messaging/src/translations/ja-JP.source-hashes.generated.ts create mode 100644 packages/services/service-messaging/src/translations/zh-CN.source-hashes.generated.ts create mode 100644 packages/services/service-realtime/src/translations/presence-bundle-provenance.test.ts create mode 100644 packages/services/service-storage/src/translations/es-ES.source-hashes.generated.ts create mode 100644 packages/services/service-storage/src/translations/ja-JP.source-hashes.generated.ts create mode 100644 packages/services/service-storage/src/translations/zh-CN.source-hashes.generated.ts diff --git a/.changeset/i18n-source-hash-rollout-eight-sets.md b/.changeset/i18n-source-hash-rollout-eight-sets.md new file mode 100644 index 0000000000..063c857b42 --- /dev/null +++ b/.changeset/i18n-source-hash-rollout-eight-sets.md @@ -0,0 +1,66 @@ +--- +"@objectstack/cli": patch +"@objectstack/plugin-approvals": patch +"@objectstack/plugin-audit": patch +"@objectstack/plugin-security": patch +"@objectstack/plugin-sharing": patch +"@objectstack/plugin-webhooks": patch +"@objectstack/service-messaging": patch +"@objectstack/service-storage": patch +--- + +chore(i18n): roll the generated-leaf provenance companion out to the remaining bundle sets (#12559) + +`os i18n extract --source-hashes` (#11671, maintainer ruling #12069 Option A) +records, per generated translation leaf, the digest of the source revision that +leaf is **still a byte copy of** — the one signal that tells a stale fill from a +real translation once the source has moved and the two stopped being +distinguishable by value. It shipped opt-in, and exactly one of the nine i18n +bundle sets opted in. A landed detector, a changeset announcing it and a green +gate read together as *"generated translation staleness is now caught"*; for +eight of nine sets it was not, and the thing making it not caught was a single +absent flag in an extract config — invisible from all three of those surfaces. + +Every remaining set now makes that choice **explicitly**, so the boundary is +written where a reader of the config actually looks: + +- **Seven sets opt in** — `plugin-approvals`, `plugin-audit`, `plugin-security`, + `plugin-sharing`, `plugin-webhooks`, `service-messaging`, `service-storage`. + Each documents `--source-hashes` in its extract config and commits three + `.source-hashes.generated.ts` companions, produced by the same + extract run as the bundles they sit beside (`check:i18n` compares them + byte-for-byte, so they cannot be written by hand). +- **One set declares itself legacy-trusted, with the measurement behind it** — + `service-realtime`. Not one of its 23 generated leaves, in any locale, is a + byte copy of the current `en` source, and a companion records a leaf only + while it *is* such a copy. Opting in there would have committed three tables + containing zero entries: an instrument that measures nothing, announcing + coverage of 23 leaves that stay legacy-trusted either way. Its extract config + now says so and cites the count, and a test beside the bundles fails the day + the first byte copy appears — which is the day that reasoning expires. + +**One extractor fix the rollout forced.** `--source-hashes` had one user, and +that user commits both generated sections, so the interaction with +`--no-metadata-forms` had never been exercised. The provenance table is computed +over every generated section the extractor builds; the seven sets here commit no +metadata-forms bundle, and their `metadataForms` subtree — absent from their +merge baseline — arrives as a fresh `--fill=default` copy of `en`, so every leaf +of it was recordable. First measured on `plugin-audit`: **763 records, of which +2 were its own objects and 761 were digests of the Studio metadata-form baseline +`@objectstack/platform-objects` owns.** Those records are unreadable in the +package holding them and would have rewritten all 21 companions on any unrelated +`*.form.ts` change in `packages/spec` — the cross-package coupling ADR-0029 D8 +and every `bundle-ownership.test.ts` keep out of committed bundles. The +companion now covers exactly the sections a run commits, decided by the same two +predicates that decide the bundle files. `platform-objects` commits both, so its +three committed companions are byte-for-byte unchanged. + +**Grade: `patch`, and behaviour on the day it lands is unchanged for every +leaf.** A record is written only where a leaf is currently a byte copy of the +**current** source, so every record written equals the current digest and none +of them can be stale; the mechanism cannot arrive red. Measured over the seven +opted-in sets: **419 records written, 0 stale**. No committed translation bundle +changed a byte, no public API moved, and no leaf's rendered text changed — the +companions are provenance data that only ever describes drift accruing after +this commit. + diff --git a/packages/cli/src/commands/i18n/extract.ts b/packages/cli/src/commands/i18n/extract.ts index c838ea9269..185158f930 100644 --- a/packages/cli/src/commands/i18n/extract.ts +++ b/packages/cli/src/commands/i18n/extract.ts @@ -21,6 +21,7 @@ import { renderTranslationModule, renderSourceHashModule, parseSourceHashModule, + narrowToCommittedSections, type FillStrategy, } from '../../utils/i18n-extract.js'; @@ -198,6 +199,52 @@ export default class I18nExtract extends Command { const emitsMetadataForms = (locale: string): boolean => flags['metadata-forms'] && (metadataFormsCounts[locale] ?? 0) > 0; + /** + * The provenance table for one locale, narrowed to the sections this run + * actually COMMITS (#12559). + * + * `extractTranslations` computes the table over every generated section it + * built — `objects` and `metadataForms` both — because the rule that fills + * it (`collectFilledFromHashes`) is a statement about generated leaves, not + * about files. Which of those sections becomes a committed bundle is this + * layer's decision, and the two must agree: a record describes the leaf + * sitting in a bundle beside it, and a record for a leaf this package does + * not commit describes nothing that exists here. + * + * The mismatch is not hypothetical — it is what the eight-set rollout in + * #12559 measured on first contact. A package that owns only its own + * objects passes `--no-metadata-forms`, and the emitter's own note two + * blocks up says why: "without it, `--check` demands a baseline copy the + * package deliberately does not commit". Its `metadataForms` subtree is + * nonetheless built, and — having no entry in that package's merge + * baseline — arrives as a fresh `--fill=default` copy of `en`, so EVERY + * leaf of it satisfies `value === currentSource` and gets recorded. + * Measured on `plugin-audit`: 763 records, of which **2** were its own + * objects and 761 were digests of the Studio metadata-form baseline that + * `@objectstack/platform-objects` owns. Those records are unreadable here + * (no `metadataForms` bundle exists in this package for them to be about), + * and they would move all three of this package's companions every time an + * unrelated `*.form.ts` in `packages/spec` changed — the same cross-package + * coupling ADR-0029 D8 and each package's `bundle-ownership.test.ts` exist + * to keep out of its committed bundles. + * + * So the section list is decided by the SAME predicates that decide the + * bundle files, never by a second rule: `result.counts` for `objects` and + * {@link emitsMetadataForms} for `metadataForms`. A set that commits both — + * `platform-objects` is the one today — keeps every record it had. The + * narrowing itself is `narrowToCommittedSections`, a pure function in the + * extractor's utils so it can be pinned without driving oclif; this layer + * contributes only the two booleans it alone knows. + */ + const committedSourceHashes = (locale: string): Record | undefined => { + const table = result.sourceHashes[locale]; + if (!table) return undefined; + const committed: string[] = []; + if ((result.counts[locale] ?? 0) > 0) committed.push('objects'); + if (emitsMetadataForms(locale)) committed.push('metadataForms'); + return narrowToCommittedSections(table, committed); + }; + if (flags.json) { await emitJson({ totalExpected: result.totalExpected, @@ -275,7 +322,7 @@ export default class I18nExtract extends Command { // The provenance companion rides in the SAME list, so `--check` compares // it by the same byte-for-byte rule as the bundles it belongs to and can // never diverge from what a real extract writes. - const table = result.sourceHashes[locale]; + const table = committedSourceHashes(locale); if (flags['source-hashes'] && table) { emitted.push({ file: path.join(resolvedOutDir, `${locale}.source-hashes.generated.ts`), diff --git a/packages/cli/src/utils/i18n-extract.ts b/packages/cli/src/utils/i18n-extract.ts index 9ab57a4c45..b15473bb0d 100644 --- a/packages/cli/src/utils/i18n-extract.ts +++ b/packages/cli/src/utils/i18n-extract.ts @@ -1475,6 +1475,49 @@ export function renderSourceHashModule( return lines.join('\n'); } +/** + * Narrow a provenance table to the generated sections a run actually COMMITS + * (#12559). + * + * {@link extractTranslations} computes the table over every generated section it + * built — `objects` and `metadataForms` both — because the rule that fills it + * (`collectFilledFromHashes`) is a statement about generated leaves, not about + * files. Which of those sections becomes a committed bundle is the command + * layer's decision, and the two must agree: a record describes the leaf sitting + * in a bundle beside it, so a record for a section this package does not commit + * describes nothing that exists there. + * + * The mismatch is measured, not hypothetical. A package that owns only its own + * objects passes `--no-metadata-forms`, and the emitter's own note says why: + * "without it, `--check` demands a baseline copy the package deliberately does + * not commit". Its `metadataForms` subtree is built anyway, and — having no + * entry in that package's merge baseline — arrives as a fresh `--fill=default` + * copy of `en`, so EVERY leaf of it satisfies `value === currentSource` and is + * recorded. Measured on `@objectstack/plugin-audit` while rolling the companion + * out in #12559: 763 records, of which **2** were its own objects and 761 were + * digests of the Studio metadata-form baseline `@objectstack/platform-objects` + * owns — unreadable in that package (no `metadataForms` bundle exists there for + * them to be about) and re-written in all three of its companions every time an + * unrelated `*.form.ts` in `packages/spec` moved. That is the cross-package + * coupling ADR-0029 D8 and each package's `bundle-ownership.test.ts` keep out of + * the committed bundles; the companion is not exempt from it. + * + * A section is named by a leaf path's FIRST dotted segment — the same identity + * `collectGeneratedLeaves` seeds its walk with, so the two cannot disagree about + * what section a path belongs to. Callers pass the sections they are committing; + * this function invents none, so a set that commits both (`platform-objects` is + * the one today) keeps every record it had. + */ +export function narrowToCommittedSections( + hashes: Record, + committedSections: Iterable, +): Record { + const committed = new Set(committedSections); + return Object.fromEntries( + Object.entries(hashes).filter(([leafPath]) => committed.has(leafPath.split('.', 1)[0])), + ); +} + /** * Read a committed `.source-hashes.generated.ts` back into a table. * diff --git a/packages/cli/test/i18n-extract-source-hashes.test.ts b/packages/cli/test/i18n-extract-source-hashes.test.ts index e29caffa19..4e256d239f 100644 --- a/packages/cli/test/i18n-extract-source-hashes.test.ts +++ b/packages/cli/test/i18n-extract-source-hashes.test.ts @@ -15,6 +15,7 @@ import { extractTranslations, renderSourceHashModule, parseSourceHashModule, + narrowToCommittedSections, } from '../src/utils/i18n-extract.js'; const stack = (help: string) => ({ @@ -84,3 +85,62 @@ describe('the emitted module', () => { expect(parseSourceHashModule('export const x: Readonly> = { "a": 3 };')).toBeUndefined(); }); }); + +// --------------------------------------------------------------------------- +// The companion covers the sections the run COMMITS (#12559) +// --------------------------------------------------------------------------- +// +// `extractTranslations` fills the table over every generated section it built, +// `metadataForms` included, because the rule behind it is about generated +// leaves rather than about files. A package that owns only its own objects +// passes `--no-metadata-forms` and commits no metadata-forms bundle — and its +// `metadataForms` subtree, absent from its merge baseline, arrives as a fresh +// `--fill=default` copy of `en`, so every leaf of it is recordable. Unnarrowed, +// that is what the table carries: measured on `@objectstack/plugin-audit` during +// the #12559 rollout, 763 records of which 2 were its own objects and 761 were +// digests of the metadata-form baseline `@objectstack/platform-objects` owns. +// +// The narrowing is pinned here rather than mirrored from the command, because a +// mirror of an emit rule is a second contract — it agrees until the day one side +// changes, and nothing says so on that day. + +describe('the provenance table is narrowed to the committed sections (#12559)', () => { + const result = extractTranslations( + { objects: [{ name: 'account', label: 'Account', fields: { name: { label: 'Name' } } }] }, + { defaultLocale: 'en', locales: ['zh-CN'], fill: 'default' }, + ); + const table = result.sourceHashes['zh-CN']; + const sectionsIn = (t: Record) => [...new Set(Object.keys(t).map((k) => k.split('.', 1)[0]))].sort(); + + it('has both sections to narrow — otherwise every case below would pass vacuously', () => { + // The registry-driven metadata-form baseline is present for any stack, so + // this is the precondition that makes the two cases a measurement rather + // than an empty walk. + expect(sectionsIn(table)).toEqual(['metadataForms', 'objects']); + expect(Object.keys(table).length).toBeGreaterThan(100); + }); + + it('drops the metadata-forms records for a set that commits no metadata-forms bundle', () => { + const narrowed = narrowToCommittedSections(table, ['objects']); + expect(sectionsIn(narrowed)).toEqual(['objects']); + // Every surviving record is unchanged — this narrows the table, it does not + // recompute it. + for (const [path, digest] of Object.entries(narrowed)) expect(digest).toBe(table[path]); + }); + + it('keeps every record for a set that commits both, so the covered set is untouched', () => { + expect(narrowToCommittedSections(table, ['objects', 'metadataForms'])).toEqual(table); + }); + + it('names a section by a leaf path\'s first dotted segment, never by a prefix match', () => { + // `objects` must not be reached through a section that merely starts with + // it, and a nested key called `objects` must not be mistaken for the section. + const odd = { 'objectsExtra.a.label': 'aaaaaaaaaaaaaaaa', 'metadataForms.x.objects.b': 'bbbbbbbbbbbbbbbb' }; + expect(narrowToCommittedSections(odd, ['objects'])).toEqual({}); + expect(narrowToCommittedSections(odd, ['metadataForms'])).toEqual({ 'metadataForms.x.objects.b': 'bbbbbbbbbbbbbbbb' }); + }); + + it('commits nothing when no section is committed', () => { + expect(narrowToCommittedSections(table, [])).toEqual({}); + }); +}); diff --git a/packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts b/packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts index effd57c807..dbd21ae83f 100644 --- a/packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts @@ -9,8 +9,20 @@ * * os i18n extract packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts \ * --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only --no-metadata-forms \ + * --source-hashes \ * --out=packages/plugins/plugin-approvals/src/translations * + * `--source-hashes` also emits `.source-hashes.generated.ts` — the + * provenance companion from maintainer ruling #12069 Option A (#11671), rolled + * out to this bundle set by #12559. Without it every generated leaf here is + * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the + * source was revised is indistinguishable BY VALUE from a real translation, so + * it publishes a superseded draft under a green `check:i18n` forever, and + * `check:i18n-stale-fill` cannot see it either unless two locales happen to + * hold the same stale bytes. A record is written only for a leaf that IS + * currently a byte copy of the CURRENT source, so the companion arrives 0-stale + * by construction and only ever reports drift that accrues after it. + * * `--no-metadata-forms` because the Studio metadata-form baseline is owned by * `@objectstack/platform-objects` — this plugin translates only the objects it * owns, so it must not commit a second copy. Add `--check` to run the same diff --git a/packages/plugins/plugin-approvals/src/translations/es-ES.source-hashes.generated.ts b/packages/plugins/plugin-approvals/src/translations/es-ES.source-hashes.generated.ts new file mode 100644 index 0000000000..5cc4abc43f --- /dev/null +++ b/packages/plugins/plugin-approvals/src/translations/es-ES.source-hashes.generated.ts @@ -0,0 +1,49 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'es-ES'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const esESGeneratedSourceHashes: Readonly> = { + "objects.sys_approval_action.fields.actor_id.label": "b155813f8a7f06e3", + "objects.sys_approval_delegation._views.active.emptyState.message": "1095d61c017b6db2", + "objects.sys_approval_delegation._views.active.emptyState.title": "799d28e51d88963e", + "objects.sys_approval_delegation._views.active.label": "eee107cf4e466779", + "objects.sys_approval_delegation.description": "432b0435aa15f6b9", + "objects.sys_approval_delegation.fields.created_at.label": "1f02d416befb595b", + "objects.sys_approval_delegation.fields.delegate_id.help": "9cfc641f7b0b04f9", + "objects.sys_approval_delegation.fields.delegate_id.label": "afd6d8733dc5bc14", + "objects.sys_approval_delegation.fields.delegator_id.help": "c4686c5c9f24e0be", + "objects.sys_approval_delegation.fields.delegator_id.label": "f76b1f95f2fdabff", + "objects.sys_approval_delegation.fields.id.label": "3383564051b4b76d", + "objects.sys_approval_delegation.fields.organization_id.help": "f02982e88229d9ca", + "objects.sys_approval_delegation.fields.organization_id.label": "3e55836156e1c1de", + "objects.sys_approval_delegation.fields.reason.help": "712516f1461dff2b", + "objects.sys_approval_delegation.fields.reason.label": "ac399f03d7f2e64d", + "objects.sys_approval_delegation.fields.updated_at.label": "aba63dc2a9c79b8d", + "objects.sys_approval_delegation.fields.valid_from.help": "01386010c454bfbd", + "objects.sys_approval_delegation.fields.valid_from.label": "02eaf201e57c9eda", + "objects.sys_approval_delegation.fields.valid_until.help": "a48ea34e65933e9d", + "objects.sys_approval_delegation.fields.valid_until.label": "a68db5bd4fc72a13", + "objects.sys_approval_delegation.label": "4389c4fe5f5c9746", + "objects.sys_approval_delegation.pluralLabel": "f07fec3ffd74e6db", + "objects.sys_approval_request.fields.flow_node_id.help": "154aa23b4eee4cae", + "objects.sys_approval_request.fields.flow_node_id.label": "052ad568aa41227c", + "objects.sys_approval_request.fields.flow_run_id.help": "35c92818f5e11090", + "objects.sys_approval_request.fields.flow_run_id.label": "d0124e323da966f9", + "objects.sys_approval_request.fields.node_config_json.help": "7ee00ef9b3ca7e80", + "objects.sys_approval_request.fields.node_config_json.label": "e64eb4bf7d7ea033", +}; diff --git a/packages/plugins/plugin-approvals/src/translations/ja-JP.source-hashes.generated.ts b/packages/plugins/plugin-approvals/src/translations/ja-JP.source-hashes.generated.ts new file mode 100644 index 0000000000..1ae3cd1db9 --- /dev/null +++ b/packages/plugins/plugin-approvals/src/translations/ja-JP.source-hashes.generated.ts @@ -0,0 +1,48 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ja-JP'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const jaJPGeneratedSourceHashes: Readonly> = { + "objects.sys_approval_delegation._views.active.emptyState.message": "1095d61c017b6db2", + "objects.sys_approval_delegation._views.active.emptyState.title": "799d28e51d88963e", + "objects.sys_approval_delegation._views.active.label": "eee107cf4e466779", + "objects.sys_approval_delegation.description": "432b0435aa15f6b9", + "objects.sys_approval_delegation.fields.created_at.label": "1f02d416befb595b", + "objects.sys_approval_delegation.fields.delegate_id.help": "9cfc641f7b0b04f9", + "objects.sys_approval_delegation.fields.delegate_id.label": "afd6d8733dc5bc14", + "objects.sys_approval_delegation.fields.delegator_id.help": "c4686c5c9f24e0be", + "objects.sys_approval_delegation.fields.delegator_id.label": "f76b1f95f2fdabff", + "objects.sys_approval_delegation.fields.id.label": "3383564051b4b76d", + "objects.sys_approval_delegation.fields.organization_id.help": "f02982e88229d9ca", + "objects.sys_approval_delegation.fields.organization_id.label": "3e55836156e1c1de", + "objects.sys_approval_delegation.fields.reason.help": "712516f1461dff2b", + "objects.sys_approval_delegation.fields.reason.label": "ac399f03d7f2e64d", + "objects.sys_approval_delegation.fields.updated_at.label": "aba63dc2a9c79b8d", + "objects.sys_approval_delegation.fields.valid_from.help": "01386010c454bfbd", + "objects.sys_approval_delegation.fields.valid_from.label": "02eaf201e57c9eda", + "objects.sys_approval_delegation.fields.valid_until.help": "a48ea34e65933e9d", + "objects.sys_approval_delegation.fields.valid_until.label": "a68db5bd4fc72a13", + "objects.sys_approval_delegation.label": "4389c4fe5f5c9746", + "objects.sys_approval_delegation.pluralLabel": "f07fec3ffd74e6db", + "objects.sys_approval_request.fields.flow_node_id.help": "154aa23b4eee4cae", + "objects.sys_approval_request.fields.flow_node_id.label": "052ad568aa41227c", + "objects.sys_approval_request.fields.flow_run_id.help": "35c92818f5e11090", + "objects.sys_approval_request.fields.flow_run_id.label": "d0124e323da966f9", + "objects.sys_approval_request.fields.node_config_json.help": "7ee00ef9b3ca7e80", + "objects.sys_approval_request.fields.node_config_json.label": "e64eb4bf7d7ea033", +}; diff --git a/packages/plugins/plugin-approvals/src/translations/zh-CN.source-hashes.generated.ts b/packages/plugins/plugin-approvals/src/translations/zh-CN.source-hashes.generated.ts new file mode 100644 index 0000000000..db1ec71ba1 --- /dev/null +++ b/packages/plugins/plugin-approvals/src/translations/zh-CN.source-hashes.generated.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-CN'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const zhCNGeneratedSourceHashes: Readonly> = { + "objects.sys_approval_request.fields.flow_node_id.help": "154aa23b4eee4cae", + "objects.sys_approval_request.fields.flow_node_id.label": "052ad568aa41227c", + "objects.sys_approval_request.fields.flow_run_id.help": "35c92818f5e11090", + "objects.sys_approval_request.fields.flow_run_id.label": "d0124e323da966f9", + "objects.sys_approval_request.fields.node_config_json.help": "7ee00ef9b3ca7e80", + "objects.sys_approval_request.fields.node_config_json.label": "e64eb4bf7d7ea033", +}; diff --git a/packages/plugins/plugin-audit/scripts/i18n-extract.config.ts b/packages/plugins/plugin-audit/scripts/i18n-extract.config.ts index 40da549b70..64158c6547 100644 --- a/packages/plugins/plugin-audit/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-audit/scripts/i18n-extract.config.ts @@ -9,7 +9,19 @@ * * os i18n extract packages/plugins/plugin-audit/scripts/i18n-extract.config.ts \ * --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only --no-metadata-forms \ + * --source-hashes \ * --out=packages/plugins/plugin-audit/src/translations + * + * `--source-hashes` also emits `.source-hashes.generated.ts` — the + * provenance companion from maintainer ruling #12069 Option A (#11671), rolled + * out to this bundle set by #12559. Without it every generated leaf here is + * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the + * source was revised is indistinguishable BY VALUE from a real translation, so + * it publishes a superseded draft under a green `check:i18n` forever, and + * `check:i18n-stale-fill` cannot see it either unless two locales happen to + * hold the same stale bytes. A record is written only for a leaf that IS + * currently a byte copy of the CURRENT source, so the companion arrives 0-stale + * by construction and only ever reports drift that accrues after it. */ import { defineStack, type ObjectStackDefinition } from '@objectstack/spec'; diff --git a/packages/plugins/plugin-audit/src/translations/es-ES.source-hashes.generated.ts b/packages/plugins/plugin-audit/src/translations/es-ES.source-hashes.generated.ts new file mode 100644 index 0000000000..e8114c4355 --- /dev/null +++ b/packages/plugins/plugin-audit/src/translations/es-ES.source-hashes.generated.ts @@ -0,0 +1,25 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'es-ES'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const esESGeneratedSourceHashes: Readonly> = { + "objects.sys_activity.fields.actor_id.label": "b155813f8a7f06e3", + "objects.sys_activity.fields.type.help": "bc8b10c89aacd494", + "objects.sys_activity.fields.url.label": "2203b0b9f72534f7", + "objects.sys_audit_log.fields.actor.label": "b155813f8a7f06e3", +}; diff --git a/packages/plugins/plugin-audit/src/translations/ja-JP.source-hashes.generated.ts b/packages/plugins/plugin-audit/src/translations/ja-JP.source-hashes.generated.ts new file mode 100644 index 0000000000..0ee7f8d206 --- /dev/null +++ b/packages/plugins/plugin-audit/src/translations/ja-JP.source-hashes.generated.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ja-JP'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const jaJPGeneratedSourceHashes: Readonly> = { + "objects.sys_activity.fields.type.help": "bc8b10c89aacd494", + "objects.sys_activity.fields.url.label": "2203b0b9f72534f7", +}; diff --git a/packages/plugins/plugin-audit/src/translations/zh-CN.source-hashes.generated.ts b/packages/plugins/plugin-audit/src/translations/zh-CN.source-hashes.generated.ts new file mode 100644 index 0000000000..c99e66bb2b --- /dev/null +++ b/packages/plugins/plugin-audit/src/translations/zh-CN.source-hashes.generated.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-CN'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const zhCNGeneratedSourceHashes: Readonly> = { + "objects.sys_activity.fields.type.help": "bc8b10c89aacd494", + "objects.sys_activity.fields.url.label": "2203b0b9f72534f7", +}; diff --git a/packages/plugins/plugin-security/scripts/i18n-extract.config.ts b/packages/plugins/plugin-security/scripts/i18n-extract.config.ts index 3b1bfcd3aa..d3a698caa9 100644 --- a/packages/plugins/plugin-security/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-security/scripts/i18n-extract.config.ts @@ -9,7 +9,19 @@ * * os i18n extract packages/plugins/plugin-security/scripts/i18n-extract.config.ts \ * --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only --no-metadata-forms \ + * --source-hashes \ * --out=packages/plugins/plugin-security/src/translations + * + * `--source-hashes` also emits `.source-hashes.generated.ts` — the + * provenance companion from maintainer ruling #12069 Option A (#11671), rolled + * out to this bundle set by #12559. Without it every generated leaf here is + * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the + * source was revised is indistinguishable BY VALUE from a real translation, so + * it publishes a superseded draft under a green `check:i18n` forever, and + * `check:i18n-stale-fill` cannot see it either unless two locales happen to + * hold the same stale bytes. A record is written only for a leaf that IS + * currently a byte copy of the CURRENT source, so the companion arrives 0-stale + * by construction and only ever reports drift that accrues after it. */ import { defineStack, type ObjectStackDefinition } from '@objectstack/spec'; diff --git a/packages/plugins/plugin-security/src/translations/es-ES.source-hashes.generated.ts b/packages/plugins/plugin-security/src/translations/es-ES.source-hashes.generated.ts new file mode 100644 index 0000000000..768fee7baf --- /dev/null +++ b/packages/plugins/plugin-security/src/translations/es-ES.source-hashes.generated.ts @@ -0,0 +1,32 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'es-ES'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const esESGeneratedSourceHashes: Readonly> = { + "objects.sys_permission_set._actions.discard_permission_set_overlay.confirmText": "02ce03bb6b6c58a2", + "objects.sys_permission_set._actions.discard_permission_set_overlay.label": "6523908dc7b796ed", + "objects.sys_permission_set._actions.discard_permission_set_overlay.successMessage": "938b7a7c989e5ff4", + "objects.sys_permission_set._views.drifted.label": "f7b9325c31edc552", + "objects.sys_permission_set.fields.drift_detail.help": "3572261f6d3bc8ab", + "objects.sys_permission_set.fields.drift_detail.label": "a84ebd221c37d10b", + "objects.sys_permission_set.fields.drift_status.help": "b16445c654ee6cec", + "objects.sys_permission_set.fields.drift_status.label": "7946a54138690b86", + "objects.sys_permission_set.fields.drift_status.options.other": "3ddcfb750014f293", + "objects.sys_permission_set.fields.drift_status.options.overlay_shadow": "7371472481b55b52", + "objects.sys_permission_set.fields.drift_status.options.provenance_skip": "fdd5f01e69fa0245", +}; diff --git a/packages/plugins/plugin-security/src/translations/ja-JP.source-hashes.generated.ts b/packages/plugins/plugin-security/src/translations/ja-JP.source-hashes.generated.ts new file mode 100644 index 0000000000..94c78ffffc --- /dev/null +++ b/packages/plugins/plugin-security/src/translations/ja-JP.source-hashes.generated.ts @@ -0,0 +1,32 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ja-JP'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const jaJPGeneratedSourceHashes: Readonly> = { + "objects.sys_permission_set._actions.discard_permission_set_overlay.confirmText": "02ce03bb6b6c58a2", + "objects.sys_permission_set._actions.discard_permission_set_overlay.label": "6523908dc7b796ed", + "objects.sys_permission_set._actions.discard_permission_set_overlay.successMessage": "938b7a7c989e5ff4", + "objects.sys_permission_set._views.drifted.label": "f7b9325c31edc552", + "objects.sys_permission_set.fields.drift_detail.help": "3572261f6d3bc8ab", + "objects.sys_permission_set.fields.drift_detail.label": "a84ebd221c37d10b", + "objects.sys_permission_set.fields.drift_status.help": "b16445c654ee6cec", + "objects.sys_permission_set.fields.drift_status.label": "7946a54138690b86", + "objects.sys_permission_set.fields.drift_status.options.other": "3ddcfb750014f293", + "objects.sys_permission_set.fields.drift_status.options.overlay_shadow": "7371472481b55b52", + "objects.sys_permission_set.fields.drift_status.options.provenance_skip": "fdd5f01e69fa0245", +}; diff --git a/packages/plugins/plugin-security/src/translations/zh-CN.source-hashes.generated.ts b/packages/plugins/plugin-security/src/translations/zh-CN.source-hashes.generated.ts new file mode 100644 index 0000000000..33418b78bf --- /dev/null +++ b/packages/plugins/plugin-security/src/translations/zh-CN.source-hashes.generated.ts @@ -0,0 +1,32 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-CN'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const zhCNGeneratedSourceHashes: Readonly> = { + "objects.sys_permission_set._actions.discard_permission_set_overlay.confirmText": "02ce03bb6b6c58a2", + "objects.sys_permission_set._actions.discard_permission_set_overlay.label": "6523908dc7b796ed", + "objects.sys_permission_set._actions.discard_permission_set_overlay.successMessage": "938b7a7c989e5ff4", + "objects.sys_permission_set._views.drifted.label": "f7b9325c31edc552", + "objects.sys_permission_set.fields.drift_detail.help": "3572261f6d3bc8ab", + "objects.sys_permission_set.fields.drift_detail.label": "a84ebd221c37d10b", + "objects.sys_permission_set.fields.drift_status.help": "b16445c654ee6cec", + "objects.sys_permission_set.fields.drift_status.label": "7946a54138690b86", + "objects.sys_permission_set.fields.drift_status.options.other": "3ddcfb750014f293", + "objects.sys_permission_set.fields.drift_status.options.overlay_shadow": "7371472481b55b52", + "objects.sys_permission_set.fields.drift_status.options.provenance_skip": "fdd5f01e69fa0245", +}; diff --git a/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts b/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts index 0023b6f607..0c288fcd57 100644 --- a/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts @@ -9,7 +9,19 @@ * * os i18n extract packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts \ * --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only --no-metadata-forms \ + * --source-hashes \ * --out=packages/plugins/plugin-sharing/src/translations + * + * `--source-hashes` also emits `.source-hashes.generated.ts` — the + * provenance companion from maintainer ruling #12069 Option A (#11671), rolled + * out to this bundle set by #12559. Without it every generated leaf here is + * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the + * source was revised is indistinguishable BY VALUE from a real translation, so + * it publishes a superseded draft under a green `check:i18n` forever, and + * `check:i18n-stale-fill` cannot see it either unless two locales happen to + * hold the same stale bytes. A record is written only for a leaf that IS + * currently a byte copy of the CURRENT source, so the companion arrives 0-stale + * by construction and only ever reports drift that accrues after it. */ import { defineStack, type ObjectStackDefinition } from '@objectstack/spec'; diff --git a/packages/plugins/plugin-sharing/src/translations/es-ES.source-hashes.generated.ts b/packages/plugins/plugin-sharing/src/translations/es-ES.source-hashes.generated.ts new file mode 100644 index 0000000000..c606d9bc74 --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/es-ES.source-hashes.generated.ts @@ -0,0 +1,22 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'es-ES'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const esESGeneratedSourceHashes: Readonly> = { + "objects.sys_share_link.fields.token.label": "6e8de3393d2cee54", +}; diff --git a/packages/plugins/plugin-sharing/src/translations/ja-JP.source-hashes.generated.ts b/packages/plugins/plugin-sharing/src/translations/ja-JP.source-hashes.generated.ts new file mode 100644 index 0000000000..517826c13d --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/ja-JP.source-hashes.generated.ts @@ -0,0 +1,21 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ja-JP'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const jaJPGeneratedSourceHashes: Readonly> = { +}; diff --git a/packages/plugins/plugin-sharing/src/translations/zh-CN.source-hashes.generated.ts b/packages/plugins/plugin-sharing/src/translations/zh-CN.source-hashes.generated.ts new file mode 100644 index 0000000000..f74e0d3d8a --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/zh-CN.source-hashes.generated.ts @@ -0,0 +1,21 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-CN'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const zhCNGeneratedSourceHashes: Readonly> = { +}; diff --git a/packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts b/packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts index a7ef6d2ebb..da67cf42b9 100644 --- a/packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts @@ -9,7 +9,19 @@ * * os i18n extract packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts \ * --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only --no-metadata-forms \ + * --source-hashes \ * --out=packages/plugins/plugin-webhooks/src/translations + * + * `--source-hashes` also emits `.source-hashes.generated.ts` — the + * provenance companion from maintainer ruling #12069 Option A (#11671), rolled + * out to this bundle set by #12559. Without it every generated leaf here is + * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the + * source was revised is indistinguishable BY VALUE from a real translation, so + * it publishes a superseded draft under a green `check:i18n` forever, and + * `check:i18n-stale-fill` cannot see it either unless two locales happen to + * hold the same stale bytes. A record is written only for a leaf that IS + * currently a byte copy of the CURRENT source, so the companion arrives 0-stale + * by construction and only ever reports drift that accrues after it. */ import { defineStack, type ObjectStackDefinition } from '@objectstack/spec'; diff --git a/packages/plugins/plugin-webhooks/src/translations/es-ES.source-hashes.generated.ts b/packages/plugins/plugin-webhooks/src/translations/es-ES.source-hashes.generated.ts new file mode 100644 index 0000000000..01e3ea3658 --- /dev/null +++ b/packages/plugins/plugin-webhooks/src/translations/es-ES.source-hashes.generated.ts @@ -0,0 +1,28 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'es-ES'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const esESGeneratedSourceHashes: Readonly> = { + "objects.sys_webhook.fields.method.options.delete": "f59e059ed51f9bfc", + "objects.sys_webhook.fields.method.options.get": "470848255971c548", + "objects.sys_webhook.fields.method.options.patch": "7de3603749decb30", + "objects.sys_webhook.fields.method.options.post": "004b24eab36d26a4", + "objects.sys_webhook.fields.method.options.put": "04d929ecae7aa435", + "objects.sys_webhook.label": "b608f1636b7e8184", + "objects.sys_webhook.pluralLabel": "8fdd81bc76459a6a", +}; diff --git a/packages/plugins/plugin-webhooks/src/translations/ja-JP.source-hashes.generated.ts b/packages/plugins/plugin-webhooks/src/translations/ja-JP.source-hashes.generated.ts new file mode 100644 index 0000000000..cfcaea9de7 --- /dev/null +++ b/packages/plugins/plugin-webhooks/src/translations/ja-JP.source-hashes.generated.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ja-JP'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const jaJPGeneratedSourceHashes: Readonly> = { + "objects.sys_webhook.fields.id.label": "9a4c8ec2a2567b95", + "objects.sys_webhook.fields.method.options.delete": "f59e059ed51f9bfc", + "objects.sys_webhook.fields.method.options.get": "470848255971c548", + "objects.sys_webhook.fields.method.options.patch": "7de3603749decb30", + "objects.sys_webhook.fields.method.options.post": "004b24eab36d26a4", + "objects.sys_webhook.fields.method.options.put": "04d929ecae7aa435", +}; diff --git a/packages/plugins/plugin-webhooks/src/translations/zh-CN.source-hashes.generated.ts b/packages/plugins/plugin-webhooks/src/translations/zh-CN.source-hashes.generated.ts new file mode 100644 index 0000000000..3d2de3c7fe --- /dev/null +++ b/packages/plugins/plugin-webhooks/src/translations/zh-CN.source-hashes.generated.ts @@ -0,0 +1,28 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-CN'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const zhCNGeneratedSourceHashes: Readonly> = { + "objects.sys_webhook.fields.id.label": "9a4c8ec2a2567b95", + "objects.sys_webhook.fields.method.options.delete": "f59e059ed51f9bfc", + "objects.sys_webhook.fields.method.options.get": "470848255971c548", + "objects.sys_webhook.fields.method.options.patch": "7de3603749decb30", + "objects.sys_webhook.fields.method.options.post": "004b24eab36d26a4", + "objects.sys_webhook.fields.method.options.put": "04d929ecae7aa435", + "objects.sys_webhook.label": "b608f1636b7e8184", +}; diff --git a/packages/services/service-messaging/scripts/i18n-extract.config.ts b/packages/services/service-messaging/scripts/i18n-extract.config.ts index 7e0992af5d..25186f7dfd 100644 --- a/packages/services/service-messaging/scripts/i18n-extract.config.ts +++ b/packages/services/service-messaging/scripts/i18n-extract.config.ts @@ -8,7 +8,19 @@ * * os i18n extract packages/services/service-messaging/scripts/i18n-extract.config.ts \ * --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only --no-metadata-forms \ + * --source-hashes \ * --out=packages/services/service-messaging/src/translations + * + * `--source-hashes` also emits `.source-hashes.generated.ts` — the + * provenance companion from maintainer ruling #12069 Option A (#11671), rolled + * out to this bundle set by #12559. Without it every generated leaf here is + * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the + * source was revised is indistinguishable BY VALUE from a real translation, so + * it publishes a superseded draft under a green `check:i18n` forever, and + * `check:i18n-stale-fill` cannot see it either unless two locales happen to + * hold the same stale bytes. A record is written only for a leaf that IS + * currently a byte copy of the CURRENT source, so the companion arrives 0-stale + * by construction and only ever reports drift that accrues after it. */ import { defineStack, type ObjectStackDefinition } from '@objectstack/spec'; diff --git a/packages/services/service-messaging/src/translations/es-ES.source-hashes.generated.ts b/packages/services/service-messaging/src/translations/es-ES.source-hashes.generated.ts new file mode 100644 index 0000000000..236b89382b --- /dev/null +++ b/packages/services/service-messaging/src/translations/es-ES.source-hashes.generated.ts @@ -0,0 +1,164 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'es-ES'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const esESGeneratedSourceHashes: Readonly> = { + "objects.sys_http_delivery._views.failures.label": "c4170eee981cbb84", + "objects.sys_http_delivery._views.pending.label": "23b532305c06373f", + "objects.sys_http_delivery._views.recent.label": "62d27bb9d0349c99", + "objects.sys_http_delivery.description": "1b5cc18e87fa69ae", + "objects.sys_http_delivery.fields.attempts.label": "096d2dbc7038a926", + "objects.sys_http_delivery.fields.claimed_at.label": "28fa2f052f552d6a", + "objects.sys_http_delivery.fields.claimed_by.label": "298232b57e56a870", + "objects.sys_http_delivery.fields.created_at.label": "1f02d416befb595b", + "objects.sys_http_delivery.fields.dedup_key.help": "5fbe8843e6268b38", + "objects.sys_http_delivery.fields.dedup_key.label": "33a740837f882c98", + "objects.sys_http_delivery.fields.error.help": "3edd3406757bedcf", + "objects.sys_http_delivery.fields.error.label": "786fed84bd8d5a32", + "objects.sys_http_delivery.fields.headers_json.help": "b14e8e640874e0a6", + "objects.sys_http_delivery.fields.headers_json.label": "e1b44aaae98059a7", + "objects.sys_http_delivery.fields.id.help": "12f58d17c3cdf5f8", + "objects.sys_http_delivery.fields.id.label": "05179727cb19255c", + "objects.sys_http_delivery.fields.label.help": "f6bd8da04c99c740", + "objects.sys_http_delivery.fields.label.label": "c680364257b11569", + "objects.sys_http_delivery.fields.last_attempted_at.label": "2123f8266be603b8", + "objects.sys_http_delivery.fields.method.label": "b92d456bcaaaa559", + "objects.sys_http_delivery.fields.next_retry_at.label": "a3be3a1ea3e87935", + "objects.sys_http_delivery.fields.partition_key.help": "3314028b4892f955", + "objects.sys_http_delivery.fields.partition_key.label": "ff6e8336a279020a", + "objects.sys_http_delivery.fields.payload_json.label": "f1d49f340a98d46e", + "objects.sys_http_delivery.fields.ref_id.help": "2ef42311bf901593", + "objects.sys_http_delivery.fields.ref_id.label": "86ca68af01f450a5", + "objects.sys_http_delivery.fields.response_body.label": "bf6a1ccfb062b374", + "objects.sys_http_delivery.fields.response_code.label": "be5ffdf6a1a0c48a", + "objects.sys_http_delivery.fields.signature.help": "73a692636ee3390f", + "objects.sys_http_delivery.fields.signature.label": "0d5e395be13b9fd8", + "objects.sys_http_delivery.fields.source.help": "c6046a29a9e8ef39", + "objects.sys_http_delivery.fields.source.label": "8fe786e8e29c8cec", + "objects.sys_http_delivery.fields.status.help": "e437ecf81dfb4715", + "objects.sys_http_delivery.fields.status.label": "64a420165247654b", + "objects.sys_http_delivery.fields.timeout_ms.label": "8e99c9f77556e013", + "objects.sys_http_delivery.fields.updated_at.label": "aba63dc2a9c79b8d", + "objects.sys_http_delivery.fields.url.help": "68b75de6ed1e3336", + "objects.sys_http_delivery.fields.url.label": "501585a180652bdf", + "objects.sys_http_delivery.label": "302f263363fe99ab", + "objects.sys_http_delivery.pluralLabel": "340f81dc6ea64987", + "objects.sys_notification_delivery.description": "6e93eafd2b3ec57a", + "objects.sys_notification_delivery.fields.attempts.label": "096d2dbc7038a926", + "objects.sys_notification_delivery.fields.channel.label": "979883fe73ee88cb", + "objects.sys_notification_delivery.fields.claimed_at.label": "28fa2f052f552d6a", + "objects.sys_notification_delivery.fields.claimed_by.help": "cb28f6419bc307e2", + "objects.sys_notification_delivery.fields.claimed_by.label": "298232b57e56a870", + "objects.sys_notification_delivery.fields.created_at.label": "1f02d416befb595b", + "objects.sys_notification_delivery.fields.digest_key.help": "6277161d780a5a7f", + "objects.sys_notification_delivery.fields.digest_key.label": "c67774ec9d8352cd", + "objects.sys_notification_delivery.fields.error.label": "786fed84bd8d5a32", + "objects.sys_notification_delivery.fields.id.label": "05179727cb19255c", + "objects.sys_notification_delivery.fields.last_attempted_at.label": "2123f8266be603b8", + "objects.sys_notification_delivery.fields.next_attempt_at.label": "d73e994fb1cd8d97", + "objects.sys_notification_delivery.fields.notification_id.help": "d3efe092d04d3e71", + "objects.sys_notification_delivery.fields.notification_id.label": "4eb344190c79dd4f", + "objects.sys_notification_delivery.fields.partition_key.label": "c3d981b1d6885418", + "objects.sys_notification_delivery.fields.payload.help": "310d32eb414a7fbe", + "objects.sys_notification_delivery.fields.payload.label": "4488e1328e37dcb2", + "objects.sys_notification_delivery.fields.recipient_id.label": "0fd8db52eea0eb8f", + "objects.sys_notification_delivery.fields.status.label": "64a420165247654b", + "objects.sys_notification_delivery.fields.status.options.dead": "4bc6e183e741bccc", + "objects.sys_notification_delivery.fields.status.options.failed": "83eaae562c4a1821", + "objects.sys_notification_delivery.fields.status.options.in_flight": "0adc2d8a7b42fc74", + "objects.sys_notification_delivery.fields.status.options.pending": "3c27125b8820d9b0", + "objects.sys_notification_delivery.fields.status.options.success": "e3a9faf0a124fb39", + "objects.sys_notification_delivery.fields.status.options.suppressed": "ffac12f09463c45b", + "objects.sys_notification_delivery.fields.topic.label": "819afdb3853e9d80", + "objects.sys_notification_delivery.fields.updated_at.label": "aba63dc2a9c79b8d", + "objects.sys_notification_delivery.label": "dab3eb6594ba3014", + "objects.sys_notification_delivery.pluralLabel": "654247db5abbd7d2", + "objects.sys_notification_preference.description": "560b825951c71086", + "objects.sys_notification_preference.fields.channel.help": "8c06b7c3f1a65fd7", + "objects.sys_notification_preference.fields.channel.label": "979883fe73ee88cb", + "objects.sys_notification_preference.fields.created_at.label": "1f02d416befb595b", + "objects.sys_notification_preference.fields.digest.help": "86c9da5370d5c2e3", + "objects.sys_notification_preference.fields.digest.label": "76e9b35f27134f0b", + "objects.sys_notification_preference.fields.digest.options.daily": "1dd810d2b5a02102", + "objects.sys_notification_preference.fields.digest.options.none": "f0ac498bc08c6383", + "objects.sys_notification_preference.fields.digest.options.weekly": "18ffbbec919e0d35", + "objects.sys_notification_preference.fields.enabled.help": "94e2727fd8c18296", + "objects.sys_notification_preference.fields.enabled.label": "41fb04f20e64dc4f", + "objects.sys_notification_preference.fields.id.label": "429cdf8675ce2833", + "objects.sys_notification_preference.fields.quiet_hours.help": "588a4adb4b5013e7", + "objects.sys_notification_preference.fields.quiet_hours.label": "fab43f24806d8f6f", + "objects.sys_notification_preference.fields.topic.help": "cf04f6b5ad2a5a33", + "objects.sys_notification_preference.fields.topic.label": "819afdb3853e9d80", + "objects.sys_notification_preference.fields.updated_at.label": "aba63dc2a9c79b8d", + "objects.sys_notification_preference.fields.user_id.help": "0ecc5b30fb3ec13c", + "objects.sys_notification_preference.fields.user_id.label": "6c0d22a931e235bd", + "objects.sys_notification_preference.label": "614ba2a098ea782c", + "objects.sys_notification_preference.pluralLabel": "8869f324c1bd545a", + "objects.sys_notification_receipt.description": "d07a68d191e3f76c", + "objects.sys_notification_receipt.fields.at.help": "f2dc84f60f311cb6", + "objects.sys_notification_receipt.fields.at.label": "dabd1da5bd8c1711", + "objects.sys_notification_receipt.fields.channel.help": "07a4fefa6d304c6c", + "objects.sys_notification_receipt.fields.channel.label": "979883fe73ee88cb", + "objects.sys_notification_receipt.fields.created_at.label": "1f02d416befb595b", + "objects.sys_notification_receipt.fields.delivery_id.help": "1172c7faf856a1c1", + "objects.sys_notification_receipt.fields.delivery_id.label": "5d66f7ce49251258", + "objects.sys_notification_receipt.fields.id.label": "35d9cd2a305993b9", + "objects.sys_notification_receipt.fields.notification_id.help": "d3efe092d04d3e71", + "objects.sys_notification_receipt.fields.notification_id.label": "4eb344190c79dd4f", + "objects.sys_notification_receipt.fields.state.label": "979534e5517c0efc", + "objects.sys_notification_receipt.fields.state.options.clicked": "74252f15f0defa81", + "objects.sys_notification_receipt.fields.state.options.delivered": "d356f1a0a5a39003", + "objects.sys_notification_receipt.fields.state.options.dismissed": "cbc3e8e878dc4685", + "objects.sys_notification_receipt.fields.state.options.read": "bd01091f9c8a6ddd", + "objects.sys_notification_receipt.fields.user_id.label": "0fd8db52eea0eb8f", + "objects.sys_notification_receipt.label": "c12ae2509f823fbf", + "objects.sys_notification_receipt.pluralLabel": "405256a600be0880", + "objects.sys_notification_subscription.description": "00de4abdcb0d43a8", + "objects.sys_notification_subscription.fields.created_at.label": "1f02d416befb595b", + "objects.sys_notification_subscription.fields.enabled.help": "37dd96827553974c", + "objects.sys_notification_subscription.fields.enabled.label": "41fb04f20e64dc4f", + "objects.sys_notification_subscription.fields.id.label": "9dd5d9fded88d363", + "objects.sys_notification_subscription.fields.principal.label": "e86ee0cdf997afed", + "objects.sys_notification_subscription.fields.topic.help": "ca33ba660ca4eb04", + "objects.sys_notification_subscription.fields.topic.label": "819afdb3853e9d80", + "objects.sys_notification_subscription.label": "cb96f976a7b36f34", + "objects.sys_notification_subscription.pluralLabel": "907cb60dda7b39c8", + "objects.sys_notification_template.description": "159a2c4fa16bf97c", + "objects.sys_notification_template.fields.body.help": "af87336e946106ce", + "objects.sys_notification_template.fields.body.label": "d1753a024d6a9bed", + "objects.sys_notification_template.fields.channel.help": "f71964058486f39e", + "objects.sys_notification_template.fields.channel.label": "979883fe73ee88cb", + "objects.sys_notification_template.fields.created_at.label": "1f02d416befb595b", + "objects.sys_notification_template.fields.format.label": "20d89df5129d933a", + "objects.sys_notification_template.fields.format.options.html": "071a232b3e2be262", + "objects.sys_notification_template.fields.format.options.markdown": "73da5c387f1a660d", + "objects.sys_notification_template.fields.format.options.mjml": "97e06d01b9117b1c", + "objects.sys_notification_template.fields.format.options.text": "434f11dd4c313183", + "objects.sys_notification_template.fields.id.label": "d2a8b4c89bfeddff", + "objects.sys_notification_template.fields.is_active.help": "c18ae718014145b7", + "objects.sys_notification_template.fields.is_active.label": "eee107cf4e466779", + "objects.sys_notification_template.fields.locale.help": "1d2b03117aa89de5", + "objects.sys_notification_template.fields.locale.label": "92b39430e88d3b51", + "objects.sys_notification_template.fields.subject.help": "9eef4993f2d0fbb8", + "objects.sys_notification_template.fields.subject.label": "92502991f5df9937", + "objects.sys_notification_template.fields.topic.label": "819afdb3853e9d80", + "objects.sys_notification_template.fields.updated_at.label": "aba63dc2a9c79b8d", + "objects.sys_notification_template.fields.version.label": "b74a37c0879ec50e", + "objects.sys_notification_template.label": "30ecafa4dac4b40d", + "objects.sys_notification_template.pluralLabel": "9e270c87ef2578df", +}; diff --git a/packages/services/service-messaging/src/translations/ja-JP.source-hashes.generated.ts b/packages/services/service-messaging/src/translations/ja-JP.source-hashes.generated.ts new file mode 100644 index 0000000000..b81556dd76 --- /dev/null +++ b/packages/services/service-messaging/src/translations/ja-JP.source-hashes.generated.ts @@ -0,0 +1,164 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ja-JP'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const jaJPGeneratedSourceHashes: Readonly> = { + "objects.sys_http_delivery._views.failures.label": "c4170eee981cbb84", + "objects.sys_http_delivery._views.pending.label": "23b532305c06373f", + "objects.sys_http_delivery._views.recent.label": "62d27bb9d0349c99", + "objects.sys_http_delivery.description": "1b5cc18e87fa69ae", + "objects.sys_http_delivery.fields.attempts.label": "096d2dbc7038a926", + "objects.sys_http_delivery.fields.claimed_at.label": "28fa2f052f552d6a", + "objects.sys_http_delivery.fields.claimed_by.label": "298232b57e56a870", + "objects.sys_http_delivery.fields.created_at.label": "1f02d416befb595b", + "objects.sys_http_delivery.fields.dedup_key.help": "5fbe8843e6268b38", + "objects.sys_http_delivery.fields.dedup_key.label": "33a740837f882c98", + "objects.sys_http_delivery.fields.error.help": "3edd3406757bedcf", + "objects.sys_http_delivery.fields.error.label": "786fed84bd8d5a32", + "objects.sys_http_delivery.fields.headers_json.help": "b14e8e640874e0a6", + "objects.sys_http_delivery.fields.headers_json.label": "e1b44aaae98059a7", + "objects.sys_http_delivery.fields.id.help": "12f58d17c3cdf5f8", + "objects.sys_http_delivery.fields.id.label": "05179727cb19255c", + "objects.sys_http_delivery.fields.label.help": "f6bd8da04c99c740", + "objects.sys_http_delivery.fields.label.label": "c680364257b11569", + "objects.sys_http_delivery.fields.last_attempted_at.label": "2123f8266be603b8", + "objects.sys_http_delivery.fields.method.label": "b92d456bcaaaa559", + "objects.sys_http_delivery.fields.next_retry_at.label": "a3be3a1ea3e87935", + "objects.sys_http_delivery.fields.partition_key.help": "3314028b4892f955", + "objects.sys_http_delivery.fields.partition_key.label": "ff6e8336a279020a", + "objects.sys_http_delivery.fields.payload_json.label": "f1d49f340a98d46e", + "objects.sys_http_delivery.fields.ref_id.help": "2ef42311bf901593", + "objects.sys_http_delivery.fields.ref_id.label": "86ca68af01f450a5", + "objects.sys_http_delivery.fields.response_body.label": "bf6a1ccfb062b374", + "objects.sys_http_delivery.fields.response_code.label": "be5ffdf6a1a0c48a", + "objects.sys_http_delivery.fields.signature.help": "73a692636ee3390f", + "objects.sys_http_delivery.fields.signature.label": "0d5e395be13b9fd8", + "objects.sys_http_delivery.fields.source.help": "c6046a29a9e8ef39", + "objects.sys_http_delivery.fields.source.label": "8fe786e8e29c8cec", + "objects.sys_http_delivery.fields.status.help": "e437ecf81dfb4715", + "objects.sys_http_delivery.fields.status.label": "64a420165247654b", + "objects.sys_http_delivery.fields.timeout_ms.label": "8e99c9f77556e013", + "objects.sys_http_delivery.fields.updated_at.label": "aba63dc2a9c79b8d", + "objects.sys_http_delivery.fields.url.help": "68b75de6ed1e3336", + "objects.sys_http_delivery.fields.url.label": "501585a180652bdf", + "objects.sys_http_delivery.label": "302f263363fe99ab", + "objects.sys_http_delivery.pluralLabel": "340f81dc6ea64987", + "objects.sys_notification_delivery.description": "6e93eafd2b3ec57a", + "objects.sys_notification_delivery.fields.attempts.label": "096d2dbc7038a926", + "objects.sys_notification_delivery.fields.channel.label": "979883fe73ee88cb", + "objects.sys_notification_delivery.fields.claimed_at.label": "28fa2f052f552d6a", + "objects.sys_notification_delivery.fields.claimed_by.help": "cb28f6419bc307e2", + "objects.sys_notification_delivery.fields.claimed_by.label": "298232b57e56a870", + "objects.sys_notification_delivery.fields.created_at.label": "1f02d416befb595b", + "objects.sys_notification_delivery.fields.digest_key.help": "6277161d780a5a7f", + "objects.sys_notification_delivery.fields.digest_key.label": "c67774ec9d8352cd", + "objects.sys_notification_delivery.fields.error.label": "786fed84bd8d5a32", + "objects.sys_notification_delivery.fields.id.label": "05179727cb19255c", + "objects.sys_notification_delivery.fields.last_attempted_at.label": "2123f8266be603b8", + "objects.sys_notification_delivery.fields.next_attempt_at.label": "d73e994fb1cd8d97", + "objects.sys_notification_delivery.fields.notification_id.help": "d3efe092d04d3e71", + "objects.sys_notification_delivery.fields.notification_id.label": "4eb344190c79dd4f", + "objects.sys_notification_delivery.fields.partition_key.label": "c3d981b1d6885418", + "objects.sys_notification_delivery.fields.payload.help": "310d32eb414a7fbe", + "objects.sys_notification_delivery.fields.payload.label": "4488e1328e37dcb2", + "objects.sys_notification_delivery.fields.recipient_id.label": "0fd8db52eea0eb8f", + "objects.sys_notification_delivery.fields.status.label": "64a420165247654b", + "objects.sys_notification_delivery.fields.status.options.dead": "4bc6e183e741bccc", + "objects.sys_notification_delivery.fields.status.options.failed": "83eaae562c4a1821", + "objects.sys_notification_delivery.fields.status.options.in_flight": "0adc2d8a7b42fc74", + "objects.sys_notification_delivery.fields.status.options.pending": "3c27125b8820d9b0", + "objects.sys_notification_delivery.fields.status.options.success": "e3a9faf0a124fb39", + "objects.sys_notification_delivery.fields.status.options.suppressed": "ffac12f09463c45b", + "objects.sys_notification_delivery.fields.topic.label": "819afdb3853e9d80", + "objects.sys_notification_delivery.fields.updated_at.label": "aba63dc2a9c79b8d", + "objects.sys_notification_delivery.label": "dab3eb6594ba3014", + "objects.sys_notification_delivery.pluralLabel": "654247db5abbd7d2", + "objects.sys_notification_preference.description": "560b825951c71086", + "objects.sys_notification_preference.fields.channel.help": "8c06b7c3f1a65fd7", + "objects.sys_notification_preference.fields.channel.label": "979883fe73ee88cb", + "objects.sys_notification_preference.fields.created_at.label": "1f02d416befb595b", + "objects.sys_notification_preference.fields.digest.help": "86c9da5370d5c2e3", + "objects.sys_notification_preference.fields.digest.label": "76e9b35f27134f0b", + "objects.sys_notification_preference.fields.digest.options.daily": "1dd810d2b5a02102", + "objects.sys_notification_preference.fields.digest.options.none": "f0ac498bc08c6383", + "objects.sys_notification_preference.fields.digest.options.weekly": "18ffbbec919e0d35", + "objects.sys_notification_preference.fields.enabled.help": "94e2727fd8c18296", + "objects.sys_notification_preference.fields.enabled.label": "41fb04f20e64dc4f", + "objects.sys_notification_preference.fields.id.label": "429cdf8675ce2833", + "objects.sys_notification_preference.fields.quiet_hours.help": "588a4adb4b5013e7", + "objects.sys_notification_preference.fields.quiet_hours.label": "fab43f24806d8f6f", + "objects.sys_notification_preference.fields.topic.help": "cf04f6b5ad2a5a33", + "objects.sys_notification_preference.fields.topic.label": "819afdb3853e9d80", + "objects.sys_notification_preference.fields.updated_at.label": "aba63dc2a9c79b8d", + "objects.sys_notification_preference.fields.user_id.help": "0ecc5b30fb3ec13c", + "objects.sys_notification_preference.fields.user_id.label": "6c0d22a931e235bd", + "objects.sys_notification_preference.label": "614ba2a098ea782c", + "objects.sys_notification_preference.pluralLabel": "8869f324c1bd545a", + "objects.sys_notification_receipt.description": "d07a68d191e3f76c", + "objects.sys_notification_receipt.fields.at.help": "f2dc84f60f311cb6", + "objects.sys_notification_receipt.fields.at.label": "dabd1da5bd8c1711", + "objects.sys_notification_receipt.fields.channel.help": "07a4fefa6d304c6c", + "objects.sys_notification_receipt.fields.channel.label": "979883fe73ee88cb", + "objects.sys_notification_receipt.fields.created_at.label": "1f02d416befb595b", + "objects.sys_notification_receipt.fields.delivery_id.help": "1172c7faf856a1c1", + "objects.sys_notification_receipt.fields.delivery_id.label": "5d66f7ce49251258", + "objects.sys_notification_receipt.fields.id.label": "35d9cd2a305993b9", + "objects.sys_notification_receipt.fields.notification_id.help": "d3efe092d04d3e71", + "objects.sys_notification_receipt.fields.notification_id.label": "4eb344190c79dd4f", + "objects.sys_notification_receipt.fields.state.label": "979534e5517c0efc", + "objects.sys_notification_receipt.fields.state.options.clicked": "74252f15f0defa81", + "objects.sys_notification_receipt.fields.state.options.delivered": "d356f1a0a5a39003", + "objects.sys_notification_receipt.fields.state.options.dismissed": "cbc3e8e878dc4685", + "objects.sys_notification_receipt.fields.state.options.read": "bd01091f9c8a6ddd", + "objects.sys_notification_receipt.fields.user_id.label": "0fd8db52eea0eb8f", + "objects.sys_notification_receipt.label": "c12ae2509f823fbf", + "objects.sys_notification_receipt.pluralLabel": "405256a600be0880", + "objects.sys_notification_subscription.description": "00de4abdcb0d43a8", + "objects.sys_notification_subscription.fields.created_at.label": "1f02d416befb595b", + "objects.sys_notification_subscription.fields.enabled.help": "37dd96827553974c", + "objects.sys_notification_subscription.fields.enabled.label": "41fb04f20e64dc4f", + "objects.sys_notification_subscription.fields.id.label": "9dd5d9fded88d363", + "objects.sys_notification_subscription.fields.principal.label": "e86ee0cdf997afed", + "objects.sys_notification_subscription.fields.topic.help": "ca33ba660ca4eb04", + "objects.sys_notification_subscription.fields.topic.label": "819afdb3853e9d80", + "objects.sys_notification_subscription.label": "cb96f976a7b36f34", + "objects.sys_notification_subscription.pluralLabel": "907cb60dda7b39c8", + "objects.sys_notification_template.description": "159a2c4fa16bf97c", + "objects.sys_notification_template.fields.body.help": "af87336e946106ce", + "objects.sys_notification_template.fields.body.label": "d1753a024d6a9bed", + "objects.sys_notification_template.fields.channel.help": "f71964058486f39e", + "objects.sys_notification_template.fields.channel.label": "979883fe73ee88cb", + "objects.sys_notification_template.fields.created_at.label": "1f02d416befb595b", + "objects.sys_notification_template.fields.format.label": "20d89df5129d933a", + "objects.sys_notification_template.fields.format.options.html": "071a232b3e2be262", + "objects.sys_notification_template.fields.format.options.markdown": "73da5c387f1a660d", + "objects.sys_notification_template.fields.format.options.mjml": "97e06d01b9117b1c", + "objects.sys_notification_template.fields.format.options.text": "434f11dd4c313183", + "objects.sys_notification_template.fields.id.label": "d2a8b4c89bfeddff", + "objects.sys_notification_template.fields.is_active.help": "c18ae718014145b7", + "objects.sys_notification_template.fields.is_active.label": "eee107cf4e466779", + "objects.sys_notification_template.fields.locale.help": "1d2b03117aa89de5", + "objects.sys_notification_template.fields.locale.label": "92b39430e88d3b51", + "objects.sys_notification_template.fields.subject.help": "9eef4993f2d0fbb8", + "objects.sys_notification_template.fields.subject.label": "92502991f5df9937", + "objects.sys_notification_template.fields.topic.label": "819afdb3853e9d80", + "objects.sys_notification_template.fields.updated_at.label": "aba63dc2a9c79b8d", + "objects.sys_notification_template.fields.version.label": "b74a37c0879ec50e", + "objects.sys_notification_template.label": "30ecafa4dac4b40d", + "objects.sys_notification_template.pluralLabel": "9e270c87ef2578df", +}; diff --git a/packages/services/service-messaging/src/translations/zh-CN.source-hashes.generated.ts b/packages/services/service-messaging/src/translations/zh-CN.source-hashes.generated.ts new file mode 100644 index 0000000000..8e3ba6ad75 --- /dev/null +++ b/packages/services/service-messaging/src/translations/zh-CN.source-hashes.generated.ts @@ -0,0 +1,24 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-CN'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const zhCNGeneratedSourceHashes: Readonly> = { + "objects.sys_http_delivery.fields.error.help": "3edd3406757bedcf", + "objects.sys_http_delivery.fields.headers_json.help": "b14e8e640874e0a6", + "objects.sys_http_delivery.fields.status.help": "e437ecf81dfb4715", +}; diff --git a/packages/services/service-realtime/scripts/i18n-extract.config.ts b/packages/services/service-realtime/scripts/i18n-extract.config.ts index 5767b08cbe..ef997b5b11 100644 --- a/packages/services/service-realtime/scripts/i18n-extract.config.ts +++ b/packages/services/service-realtime/scripts/i18n-extract.config.ts @@ -10,6 +10,36 @@ * os i18n extract packages/services/service-realtime/scripts/i18n-extract.config.ts \ * --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only --no-metadata-forms \ * --out=packages/services/service-realtime/src/translations + * + * ## Provenance: LEGACY-TRUSTED BY CHOICE, and here is the measurement + * + * #12559 rolled the generated-leaf provenance companion (maintainer ruling + * #12069 Option A, #11671) out to every other bundle set. This one is the + * measured exception, declared here rather than left silent — a set that is + * simply missing from a rollout reads as covered to anyone who does not go + * counting configs, which is the hazard #12559 was filed about. + * + * Measured on this set at rollout time: 23 generated leaves per locale, and + * **0** of them — in any of zh-CN, ja-JP, es-ES — is a byte copy of the current + * `en` source. The companion records a leaf only while it IS such a copy, so + * opting in here would emit three `.source-hashes.generated.ts` files + * containing zero entries. That is not coverage: all 23 leaves would stay + * legacy-trusted exactly as they are today, while three committed files + * announced an instrument that measures nothing. `sys_presence` is a single + * fully-translated object; there is no filled leaf here to record. + * + * **Revisit when that stops being true.** The declaration above is a claim + * about the tree, not a preference, and `presence-bundle-provenance.test.ts` + * next to the bundles keeps it honest: it fails the moment any locale's leaf + * becomes a byte copy of `en` — i.e. the moment this set gains a leaf the + * companion could record — and sends the reader back to this paragraph. + * + * ⛔ Do not name the opt-in flag with its leading dashes anywhere in THIS + * docstring, not even in prose arguing against it: `flagsFromDocstring` + * (scripts/i18n-bundle-surface.mjs) harvests every recognised flag spelling out + * of this comment and hands the result to the extractor, so a mention would + * silently convert this declaration into the opt-in it declares against. The + * flag is `source-hashes`; the files it would write are named above. */ import { defineStack, type ObjectStackDefinition } from '@objectstack/spec'; diff --git a/packages/services/service-realtime/src/translations/presence-bundle-provenance.test.ts b/packages/services/service-realtime/src/translations/presence-bundle-provenance.test.ts new file mode 100644 index 0000000000..3c49a8d79f --- /dev/null +++ b/packages/services/service-realtime/src/translations/presence-bundle-provenance.test.ts @@ -0,0 +1,76 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// Keeps this package's LEGACY-TRUSTED declaration honest (#12559). +// +// #12559 rolled the generated-leaf provenance companion (maintainer ruling +// #12069 Option A, #11671) out to eight of the nine i18n bundle sets. This one +// opted out, and `scripts/i18n-extract.config.ts` says why in prose: not one of +// its generated leaves is a byte copy of the current `en` source, and the +// companion records a leaf ONLY while it is such a copy — so opting in would +// commit three tables with zero entries, announcing an instrument that measures +// nothing while all 23 leaves stayed legacy-trusted regardless. +// +// That reason is a MEASUREMENT, and a measurement written in prose stops being +// true without telling anyone. The day `sys_presence` gains a field whose label +// is filled from the source — a new option, a new help string, anything the +// translators have not reached yet — the opt-out's premise is gone and the +// companion would have something real to record. This test is the trigger for +// that moment: it fails on the first byte copy and names the paragraph to go +// re-read. +// +// It deliberately does NOT assert the leaves differ in any particular way, and +// it is not a translation-quality check: `check:i18n-coverage` owns presence, +// and `check:i18n` owns bundle freshness. The one fact here is the one the +// opt-out rests on. + +import { describe, it, expect } from 'vitest'; +import { enObjects } from './en.objects.generated.js'; +import { zhCNObjects } from './zh-CN.objects.generated.js'; +import { jaJPObjects } from './ja-JP.objects.generated.js'; +import { esESObjects } from './es-ES.objects.generated.js'; + +/** Every string leaf keyed by dotted path — the extractor's own leaf identity. */ +function collectLeaves(node: unknown, path = '', out = new Map()): Map { + if (typeof node === 'string') { + out.set(path, node); + return out; + } + if (node && typeof node === 'object' && !Array.isArray(node)) { + for (const [key, child] of Object.entries(node)) collectLeaves(child, path ? `${path}.${key}` : key, out); + } + return out; +} + +const TRANSLATED = { + 'zh-CN': zhCNObjects, + 'ja-JP': jaJPObjects, + 'es-ES': esESObjects, +} as const; + +describe('service-realtime is legacy-trusted by measurement, not by omission (#12559)', () => { + const source = collectLeaves(enObjects); + + it('the bundles are non-empty, so an all-clear below is a reading rather than an empty walk', () => { + // Without this, every assertion beneath it would pass just as loudly on a + // bundle that failed to load — the "0 findings because 0 inputs" shape + // #12559 was filed about. + expect(source.size).toBeGreaterThan(0); + for (const [locale, bundle] of Object.entries(TRANSLATED)) { + expect(collectLeaves(bundle).size, `${locale} bundle walked to zero leaves`).toBe(source.size); + } + }); + + it.each(Object.keys(TRANSLATED))('%s holds no leaf that is a byte copy of the current en source', (locale) => { + const copies = [...collectLeaves(TRANSLATED[locale as keyof typeof TRANSLATED])] + .filter(([path, value]) => source.get(path) === value) + .map(([path]) => path); + expect( + copies, + `${locale} now carries ${copies.length} leaf/leaves filled from the source (${copies.join(', ')}). ` + + "That is exactly what the provenance companion records, so this package's opt-out in " + + 'scripts/i18n-extract.config.ts ("Provenance: LEGACY-TRUSTED BY CHOICE") no longer holds — ' + + 'either translate the leaf, or opt this set in by documenting the source-hashes flag in that ' + + 'config and committing the generated companions alongside the bundles.', + ).toEqual([]); + }); +}); diff --git a/packages/services/service-storage/scripts/i18n-extract.config.ts b/packages/services/service-storage/scripts/i18n-extract.config.ts index f9fd66bc2e..15e12ee768 100644 --- a/packages/services/service-storage/scripts/i18n-extract.config.ts +++ b/packages/services/service-storage/scripts/i18n-extract.config.ts @@ -12,7 +12,19 @@ * * os i18n extract packages/services/service-storage/scripts/i18n-extract.config.ts \ * --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only --no-metadata-forms \ + * --source-hashes \ * --out=packages/services/service-storage/src/translations + * + * `--source-hashes` also emits `.source-hashes.generated.ts` — the + * provenance companion from maintainer ruling #12069 Option A (#11671), rolled + * out to this bundle set by #12559. Without it every generated leaf here is + * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the + * source was revised is indistinguishable BY VALUE from a real translation, so + * it publishes a superseded draft under a green `check:i18n` forever, and + * `check:i18n-stale-fill` cannot see it either unless two locales happen to + * hold the same stale bytes. A record is written only for a leaf that IS + * currently a byte copy of the CURRENT source, so the companion arrives 0-stale + * by construction and only ever reports drift that accrues after it. */ import { defineStack, type ObjectStackDefinition } from '@objectstack/spec'; diff --git a/packages/services/service-storage/src/translations/es-ES.source-hashes.generated.ts b/packages/services/service-storage/src/translations/es-ES.source-hashes.generated.ts new file mode 100644 index 0000000000..5039467236 --- /dev/null +++ b/packages/services/service-storage/src/translations/es-ES.source-hashes.generated.ts @@ -0,0 +1,25 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'es-ES'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const esESGeneratedSourceHashes: Readonly> = { + "objects.sys_file.fields.acl.label": "f08120d40bf5d975", + "objects.sys_file.fields.bucket.label": "3aa22e888d1f90c0", + "objects.sys_file.fields.etag.label": "a62e7fe0bac46fcb", + "objects.sys_upload_session.fields.bucket.label": "3aa22e888d1f90c0", +}; diff --git a/packages/services/service-storage/src/translations/ja-JP.source-hashes.generated.ts b/packages/services/service-storage/src/translations/ja-JP.source-hashes.generated.ts new file mode 100644 index 0000000000..f9869fcfb8 --- /dev/null +++ b/packages/services/service-storage/src/translations/ja-JP.source-hashes.generated.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ja-JP'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const jaJPGeneratedSourceHashes: Readonly> = { + "objects.sys_file.fields.acl.label": "f08120d40bf5d975", + "objects.sys_file.fields.etag.label": "a62e7fe0bac46fcb", +}; diff --git a/packages/services/service-storage/src/translations/zh-CN.source-hashes.generated.ts b/packages/services/service-storage/src/translations/zh-CN.source-hashes.generated.ts new file mode 100644 index 0000000000..1a90cae2c0 --- /dev/null +++ b/packages/services/service-storage/src/translations/zh-CN.source-hashes.generated.ts @@ -0,0 +1,22 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-CN'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const zhCNGeneratedSourceHashes: Readonly> = { + "objects.sys_file.fields.etag.label": "a62e7fe0bac46fcb", +}; From f463beaa0d5057bc945f1bf0e8093a37d7ba8e4a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 17:03:41 +0000 Subject: [PATCH 2/3] docs(i18n): name the opt-in flag once, in the command the config documents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `flagsFromDocstring` harvests every recognised flag spelling out of a config's whole docstring, so the paragraph explaining `--source-hashes` was a SECOND declaration of it: deleting the flag from the documented command left the opt-in switched on, which is not what anyone editing that command would read. Measured while ablating the opt-out path — the extract still emitted 7 bundles for a config whose command block no longer named the flag. The prose now spells it without leading dashes and says why, the same rule service-realtime's legacy-trusted declaration already states from the other side. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01W6HFzyH98W1YaQXhJUJt6o --- .../plugin-approvals/scripts/i18n-extract.config.ts | 11 ++++++++--- .../plugin-audit/scripts/i18n-extract.config.ts | 11 ++++++++--- .../plugin-security/scripts/i18n-extract.config.ts | 11 ++++++++--- .../plugin-sharing/scripts/i18n-extract.config.ts | 11 ++++++++--- .../plugin-webhooks/scripts/i18n-extract.config.ts | 11 ++++++++--- .../service-messaging/scripts/i18n-extract.config.ts | 11 ++++++++--- .../service-storage/scripts/i18n-extract.config.ts | 11 ++++++++--- 7 files changed, 56 insertions(+), 21 deletions(-) diff --git a/packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts b/packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts index dbd21ae83f..fac403954d 100644 --- a/packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts @@ -12,9 +12,14 @@ * --source-hashes \ * --out=packages/plugins/plugin-approvals/src/translations * - * `--source-hashes` also emits `.source-hashes.generated.ts` — the - * provenance companion from maintainer ruling #12069 Option A (#11671), rolled - * out to this bundle set by #12559. Without it every generated leaf here is + * The `source-hashes` flag on the command above also emits + * `.source-hashes.generated.ts` — the provenance companion from + * maintainer ruling #12069 Option A (#11671), rolled out to this bundle set by + * #12559. It is named here WITHOUT its leading dashes on purpose: + * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every + * recognised flag spelling out of this whole comment, so a second spelling in + * prose would keep the opt-in switched on after someone deleted it from the + * command — the command block is the one place that decides. Without it every generated leaf here is * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the * source was revised is indistinguishable BY VALUE from a real translation, so * it publishes a superseded draft under a green `check:i18n` forever, and diff --git a/packages/plugins/plugin-audit/scripts/i18n-extract.config.ts b/packages/plugins/plugin-audit/scripts/i18n-extract.config.ts index 64158c6547..3607206899 100644 --- a/packages/plugins/plugin-audit/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-audit/scripts/i18n-extract.config.ts @@ -12,9 +12,14 @@ * --source-hashes \ * --out=packages/plugins/plugin-audit/src/translations * - * `--source-hashes` also emits `.source-hashes.generated.ts` — the - * provenance companion from maintainer ruling #12069 Option A (#11671), rolled - * out to this bundle set by #12559. Without it every generated leaf here is + * The `source-hashes` flag on the command above also emits + * `.source-hashes.generated.ts` — the provenance companion from + * maintainer ruling #12069 Option A (#11671), rolled out to this bundle set by + * #12559. It is named here WITHOUT its leading dashes on purpose: + * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every + * recognised flag spelling out of this whole comment, so a second spelling in + * prose would keep the opt-in switched on after someone deleted it from the + * command — the command block is the one place that decides. Without it every generated leaf here is * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the * source was revised is indistinguishable BY VALUE from a real translation, so * it publishes a superseded draft under a green `check:i18n` forever, and diff --git a/packages/plugins/plugin-security/scripts/i18n-extract.config.ts b/packages/plugins/plugin-security/scripts/i18n-extract.config.ts index d3a698caa9..1dd7841570 100644 --- a/packages/plugins/plugin-security/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-security/scripts/i18n-extract.config.ts @@ -12,9 +12,14 @@ * --source-hashes \ * --out=packages/plugins/plugin-security/src/translations * - * `--source-hashes` also emits `.source-hashes.generated.ts` — the - * provenance companion from maintainer ruling #12069 Option A (#11671), rolled - * out to this bundle set by #12559. Without it every generated leaf here is + * The `source-hashes` flag on the command above also emits + * `.source-hashes.generated.ts` — the provenance companion from + * maintainer ruling #12069 Option A (#11671), rolled out to this bundle set by + * #12559. It is named here WITHOUT its leading dashes on purpose: + * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every + * recognised flag spelling out of this whole comment, so a second spelling in + * prose would keep the opt-in switched on after someone deleted it from the + * command — the command block is the one place that decides. Without it every generated leaf here is * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the * source was revised is indistinguishable BY VALUE from a real translation, so * it publishes a superseded draft under a green `check:i18n` forever, and diff --git a/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts b/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts index 0c288fcd57..22192dea99 100644 --- a/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts @@ -12,9 +12,14 @@ * --source-hashes \ * --out=packages/plugins/plugin-sharing/src/translations * - * `--source-hashes` also emits `.source-hashes.generated.ts` — the - * provenance companion from maintainer ruling #12069 Option A (#11671), rolled - * out to this bundle set by #12559. Without it every generated leaf here is + * The `source-hashes` flag on the command above also emits + * `.source-hashes.generated.ts` — the provenance companion from + * maintainer ruling #12069 Option A (#11671), rolled out to this bundle set by + * #12559. It is named here WITHOUT its leading dashes on purpose: + * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every + * recognised flag spelling out of this whole comment, so a second spelling in + * prose would keep the opt-in switched on after someone deleted it from the + * command — the command block is the one place that decides. Without it every generated leaf here is * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the * source was revised is indistinguishable BY VALUE from a real translation, so * it publishes a superseded draft under a green `check:i18n` forever, and diff --git a/packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts b/packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts index da67cf42b9..082b5adca4 100644 --- a/packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts @@ -12,9 +12,14 @@ * --source-hashes \ * --out=packages/plugins/plugin-webhooks/src/translations * - * `--source-hashes` also emits `.source-hashes.generated.ts` — the - * provenance companion from maintainer ruling #12069 Option A (#11671), rolled - * out to this bundle set by #12559. Without it every generated leaf here is + * The `source-hashes` flag on the command above also emits + * `.source-hashes.generated.ts` — the provenance companion from + * maintainer ruling #12069 Option A (#11671), rolled out to this bundle set by + * #12559. It is named here WITHOUT its leading dashes on purpose: + * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every + * recognised flag spelling out of this whole comment, so a second spelling in + * prose would keep the opt-in switched on after someone deleted it from the + * command — the command block is the one place that decides. Without it every generated leaf here is * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the * source was revised is indistinguishable BY VALUE from a real translation, so * it publishes a superseded draft under a green `check:i18n` forever, and diff --git a/packages/services/service-messaging/scripts/i18n-extract.config.ts b/packages/services/service-messaging/scripts/i18n-extract.config.ts index 25186f7dfd..10d649769b 100644 --- a/packages/services/service-messaging/scripts/i18n-extract.config.ts +++ b/packages/services/service-messaging/scripts/i18n-extract.config.ts @@ -11,9 +11,14 @@ * --source-hashes \ * --out=packages/services/service-messaging/src/translations * - * `--source-hashes` also emits `.source-hashes.generated.ts` — the - * provenance companion from maintainer ruling #12069 Option A (#11671), rolled - * out to this bundle set by #12559. Without it every generated leaf here is + * The `source-hashes` flag on the command above also emits + * `.source-hashes.generated.ts` — the provenance companion from + * maintainer ruling #12069 Option A (#11671), rolled out to this bundle set by + * #12559. It is named here WITHOUT its leading dashes on purpose: + * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every + * recognised flag spelling out of this whole comment, so a second spelling in + * prose would keep the opt-in switched on after someone deleted it from the + * command — the command block is the one place that decides. Without it every generated leaf here is * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the * source was revised is indistinguishable BY VALUE from a real translation, so * it publishes a superseded draft under a green `check:i18n` forever, and diff --git a/packages/services/service-storage/scripts/i18n-extract.config.ts b/packages/services/service-storage/scripts/i18n-extract.config.ts index 15e12ee768..770d8abf8f 100644 --- a/packages/services/service-storage/scripts/i18n-extract.config.ts +++ b/packages/services/service-storage/scripts/i18n-extract.config.ts @@ -15,9 +15,14 @@ * --source-hashes \ * --out=packages/services/service-storage/src/translations * - * `--source-hashes` also emits `.source-hashes.generated.ts` — the - * provenance companion from maintainer ruling #12069 Option A (#11671), rolled - * out to this bundle set by #12559. Without it every generated leaf here is + * The `source-hashes` flag on the command above also emits + * `.source-hashes.generated.ts` — the provenance companion from + * maintainer ruling #12069 Option A (#11671), rolled out to this bundle set by + * #12559. It is named here WITHOUT its leading dashes on purpose: + * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every + * recognised flag spelling out of this whole comment, so a second spelling in + * prose would keep the opt-in switched on after someone deleted it from the + * command — the command block is the one place that decides. Without it every generated leaf here is * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the * source was revised is indistinguishable BY VALUE from a real translation, so * it publishes a superseded draft under a green `check:i18n` forever, and From d6049faed27ad1fe3abad148d829999275ee0eef Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 01:55:45 +0000 Subject: [PATCH 3/3] feat(i18n): opt every bundle set into the generated-leaf provenance companion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rolls `--source-hashes` out to the eighth and last uncovered set (`service-realtime`) and puts all eight remaining sets on ONE shared opt-in paragraph, so no set carries a hand-maintained exception. The recovered branch left `service-realtime` opted out on the ground that its companions would hold zero entries, and paired the opt-out with a bespoke test to keep that prose honest. Measured here, the same is true of `plugin-sharing` in zh-CN and ja-JP, which the rollout opted in anyway — so the exception was not a property of `service-realtime`, it was a property of a fully translated locale, and two of the nine sets shipped exactly the artifact the exception argued against. An empty companion is the instrument ARMED, not an instrument that measures nothing: the extractor rewrites it on every run, so a leaf that becomes a fill is recorded by itself. The opted-out shape needed a human to re-read a paragraph, which is why it needed a test. Uniform opt-in needs neither. Records therefore count what is currently RECORDABLE, never what is covered — stated in the shared paragraph, since it is the inference the card was filed about. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01W6HFzyH98W1YaQXhJUJt6o --- .../i18n-source-hash-rollout-eight-sets.md | 55 ++++++++------ .../scripts/i18n-extract.config.ts | 31 +++++--- .../scripts/i18n-extract.config.ts | 31 +++++--- .../scripts/i18n-extract.config.ts | 31 +++++--- .../scripts/i18n-extract.config.ts | 31 +++++--- .../scripts/i18n-extract.config.ts | 31 +++++--- .../scripts/i18n-extract.config.ts | 31 +++++--- .../scripts/i18n-extract.config.ts | 48 +++++------- .../es-ES.source-hashes.generated.ts | 21 +++++ .../ja-JP.source-hashes.generated.ts | 21 +++++ .../presence-bundle-provenance.test.ts | 76 ------------------- .../zh-CN.source-hashes.generated.ts | 21 +++++ .../scripts/i18n-extract.config.ts | 31 +++++--- 13 files changed, 249 insertions(+), 210 deletions(-) create mode 100644 packages/services/service-realtime/src/translations/es-ES.source-hashes.generated.ts create mode 100644 packages/services/service-realtime/src/translations/ja-JP.source-hashes.generated.ts delete mode 100644 packages/services/service-realtime/src/translations/presence-bundle-provenance.test.ts create mode 100644 packages/services/service-realtime/src/translations/zh-CN.source-hashes.generated.ts diff --git a/.changeset/i18n-source-hash-rollout-eight-sets.md b/.changeset/i18n-source-hash-rollout-eight-sets.md index 063c857b42..7f17e38131 100644 --- a/.changeset/i18n-source-hash-rollout-eight-sets.md +++ b/.changeset/i18n-source-hash-rollout-eight-sets.md @@ -6,6 +6,7 @@ "@objectstack/plugin-sharing": patch "@objectstack/plugin-webhooks": patch "@objectstack/service-messaging": patch +"@objectstack/service-realtime": patch "@objectstack/service-storage": patch --- @@ -21,34 +22,37 @@ gate read together as *"generated translation staleness is now caught"*; for eight of nine sets it was not, and the thing making it not caught was a single absent flag in an extract config — invisible from all three of those surfaces. -Every remaining set now makes that choice **explicitly**, so the boundary is -written where a reader of the config actually looks: +**All eight remaining sets now opt in** — `plugin-approvals`, `plugin-audit`, +`plugin-security`, `plugin-sharing`, `plugin-webhooks`, `service-messaging`, +`service-realtime`, `service-storage`. Each documents `source-hashes` in its +extract config and commits three `.source-hashes.generated.ts` +companions, produced by the same extract run as the bundles they sit beside +(`check:i18n` compares them byte-for-byte, so they cannot be written by hand). +`check:i18n` now reports 7 bundles per set where it reported 4, and 11 for +`platform-objects` where it reported 8. -- **Seven sets opt in** — `plugin-approvals`, `plugin-audit`, `plugin-security`, - `plugin-sharing`, `plugin-webhooks`, `service-messaging`, `service-storage`. - Each documents `--source-hashes` in its extract config and commits three - `.source-hashes.generated.ts` companions, produced by the same - extract run as the bundles they sit beside (`check:i18n` compares them - byte-for-byte, so they cannot be written by hand). -- **One set declares itself legacy-trusted, with the measurement behind it** — - `service-realtime`. Not one of its 23 generated leaves, in any locale, is a - byte copy of the current `en` source, and a companion records a leaf only - while it *is* such a copy. Opting in there would have committed three tables - containing zero entries: an instrument that measures nothing, announcing - coverage of 23 leaves that stay legacy-trusted either way. Its extract config - now says so and cites the count, and a test beside the bundles fails the day - the first byte copy appears — which is the day that reasoning expires. +**Records count what is currently RECORDABLE, never what is covered.** A record +is written only for a leaf that *is* right now a byte copy of the current +source, so a fully translated locale starts with an empty table — which is the +instrument armed, not an instrument that measures nothing: the entry appears by +itself on the first extract after a leaf becomes a fill. Measured at this +commit, per set over its three translated locales: `service-messaging` 289, +`plugin-approvals` 61, `plugin-security` 33, `plugin-webhooks` 20, +`plugin-audit` 8, `service-storage` 7, `plugin-sharing` 1 (es-ES only; zh-CN and +ja-JP are fully translated and start empty), `service-realtime` 0 (all three +locales fully translated). **419 records written across the eight sets, 0 +stale.** **One extractor fix the rollout forced.** `--source-hashes` had one user, and that user commits both generated sections, so the interaction with `--no-metadata-forms` had never been exercised. The provenance table is computed -over every generated section the extractor builds; the seven sets here commit no +over every generated section the extractor builds; the eight sets here commit no metadata-forms bundle, and their `metadataForms` subtree — absent from their merge baseline — arrives as a fresh `--fill=default` copy of `en`, so every leaf of it was recordable. First measured on `plugin-audit`: **763 records, of which 2 were its own objects and 761 were digests of the Studio metadata-form baseline `@objectstack/platform-objects` owns.** Those records are unreadable in the -package holding them and would have rewritten all 21 companions on any unrelated +package holding them and would have rewritten all 24 companions on any unrelated `*.form.ts` change in `packages/spec` — the cross-package coupling ADR-0029 D8 and every `bundle-ownership.test.ts` keep out of committed bundles. The companion now covers exactly the sections a run commits, decided by the same two @@ -58,9 +62,14 @@ three committed companions are byte-for-byte unchanged. **Grade: `patch`, and behaviour on the day it lands is unchanged for every leaf.** A record is written only where a leaf is currently a byte copy of the **current** source, so every record written equals the current digest and none -of them can be stale; the mechanism cannot arrive red. Measured over the seven -opted-in sets: **419 records written, 0 stale**. No committed translation bundle -changed a byte, no public API moved, and no leaf's rendered text changed — the -companions are provenance data that only ever describes drift accruing after -this commit. +of them can be stale; the mechanism cannot arrive red. No committed translation +bundle changed a byte, no public API moved, and no leaf's rendered text changed. +`narrowToCommittedSections` is new but internal to `@objectstack/cli` — the +package's entrypoint does not re-export the extractor utils. +**What this does not do**, stated so the boundary is not inferred wrongly a +second time: these eight sets now *record* provenance. Reading it at serving +time is `withSourceFallback`, and that is still wired in +`@objectstack/platform-objects` alone — so a stale fill in one of the eight is +now recorded and reportable, but not yet substituted at runtime. Tracked +separately. diff --git a/packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts b/packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts index fac403954d..46112a080a 100644 --- a/packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts @@ -14,19 +14,26 @@ * * The `source-hashes` flag on the command above also emits * `.source-hashes.generated.ts` — the provenance companion from - * maintainer ruling #12069 Option A (#11671), rolled out to this bundle set by - * #12559. It is named here WITHOUT its leading dashes on purpose: - * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every - * recognised flag spelling out of this whole comment, so a second spelling in - * prose would keep the opt-in switched on after someone deleted it from the - * command — the command block is the one place that decides. Without it every generated leaf here is - * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the - * source was revised is indistinguishable BY VALUE from a real translation, so - * it publishes a superseded draft under a green `check:i18n` forever, and + * maintainer ruling #12069 Option A (#11671), rolled out to every bundle set by + * #12559. Without it every generated leaf here is LEGACY-TRUSTED: a leaf filled + * from the source and then left behind when the source was revised is + * indistinguishable BY VALUE from a real translation, so it publishes a + * superseded draft under a green `check:i18n` forever, and * `check:i18n-stale-fill` cannot see it either unless two locales happen to - * hold the same stale bytes. A record is written only for a leaf that IS - * currently a byte copy of the CURRENT source, so the companion arrives 0-stale - * by construction and only ever reports drift that accrues after it. + * hold the same stale bytes. + * + * A record is written only for a leaf that IS currently a byte copy of the + * CURRENT source, so the companion arrives 0-stale by construction and only + * ever reports drift accruing afterwards. Records count the leaves currently + * RECORDABLE, never the leaves covered — a table with few entries, or with none + * at all where a locale is fully translated, is the instrument armed, and an + * entry appears by itself on the first extract after a leaf becomes a fill. + * + * The flag is named in prose WITHOUT its leading dashes on purpose: + * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every + * recognised flag spelling out of this whole comment, so a second spelling + * would keep the opt-in switched on after someone deleted it from the command + * block — the one place that decides. * * `--no-metadata-forms` because the Studio metadata-form baseline is owned by * `@objectstack/platform-objects` — this plugin translates only the objects it diff --git a/packages/plugins/plugin-audit/scripts/i18n-extract.config.ts b/packages/plugins/plugin-audit/scripts/i18n-extract.config.ts index 3607206899..479453ff92 100644 --- a/packages/plugins/plugin-audit/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-audit/scripts/i18n-extract.config.ts @@ -14,19 +14,26 @@ * * The `source-hashes` flag on the command above also emits * `.source-hashes.generated.ts` — the provenance companion from - * maintainer ruling #12069 Option A (#11671), rolled out to this bundle set by - * #12559. It is named here WITHOUT its leading dashes on purpose: - * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every - * recognised flag spelling out of this whole comment, so a second spelling in - * prose would keep the opt-in switched on after someone deleted it from the - * command — the command block is the one place that decides. Without it every generated leaf here is - * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the - * source was revised is indistinguishable BY VALUE from a real translation, so - * it publishes a superseded draft under a green `check:i18n` forever, and + * maintainer ruling #12069 Option A (#11671), rolled out to every bundle set by + * #12559. Without it every generated leaf here is LEGACY-TRUSTED: a leaf filled + * from the source and then left behind when the source was revised is + * indistinguishable BY VALUE from a real translation, so it publishes a + * superseded draft under a green `check:i18n` forever, and * `check:i18n-stale-fill` cannot see it either unless two locales happen to - * hold the same stale bytes. A record is written only for a leaf that IS - * currently a byte copy of the CURRENT source, so the companion arrives 0-stale - * by construction and only ever reports drift that accrues after it. + * hold the same stale bytes. + * + * A record is written only for a leaf that IS currently a byte copy of the + * CURRENT source, so the companion arrives 0-stale by construction and only + * ever reports drift accruing afterwards. Records count the leaves currently + * RECORDABLE, never the leaves covered — a table with few entries, or with none + * at all where a locale is fully translated, is the instrument armed, and an + * entry appears by itself on the first extract after a leaf becomes a fill. + * + * The flag is named in prose WITHOUT its leading dashes on purpose: + * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every + * recognised flag spelling out of this whole comment, so a second spelling + * would keep the opt-in switched on after someone deleted it from the command + * block — the one place that decides. */ import { defineStack, type ObjectStackDefinition } from '@objectstack/spec'; diff --git a/packages/plugins/plugin-security/scripts/i18n-extract.config.ts b/packages/plugins/plugin-security/scripts/i18n-extract.config.ts index 1dd7841570..0a49255aa8 100644 --- a/packages/plugins/plugin-security/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-security/scripts/i18n-extract.config.ts @@ -14,19 +14,26 @@ * * The `source-hashes` flag on the command above also emits * `.source-hashes.generated.ts` — the provenance companion from - * maintainer ruling #12069 Option A (#11671), rolled out to this bundle set by - * #12559. It is named here WITHOUT its leading dashes on purpose: - * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every - * recognised flag spelling out of this whole comment, so a second spelling in - * prose would keep the opt-in switched on after someone deleted it from the - * command — the command block is the one place that decides. Without it every generated leaf here is - * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the - * source was revised is indistinguishable BY VALUE from a real translation, so - * it publishes a superseded draft under a green `check:i18n` forever, and + * maintainer ruling #12069 Option A (#11671), rolled out to every bundle set by + * #12559. Without it every generated leaf here is LEGACY-TRUSTED: a leaf filled + * from the source and then left behind when the source was revised is + * indistinguishable BY VALUE from a real translation, so it publishes a + * superseded draft under a green `check:i18n` forever, and * `check:i18n-stale-fill` cannot see it either unless two locales happen to - * hold the same stale bytes. A record is written only for a leaf that IS - * currently a byte copy of the CURRENT source, so the companion arrives 0-stale - * by construction and only ever reports drift that accrues after it. + * hold the same stale bytes. + * + * A record is written only for a leaf that IS currently a byte copy of the + * CURRENT source, so the companion arrives 0-stale by construction and only + * ever reports drift accruing afterwards. Records count the leaves currently + * RECORDABLE, never the leaves covered — a table with few entries, or with none + * at all where a locale is fully translated, is the instrument armed, and an + * entry appears by itself on the first extract after a leaf becomes a fill. + * + * The flag is named in prose WITHOUT its leading dashes on purpose: + * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every + * recognised flag spelling out of this whole comment, so a second spelling + * would keep the opt-in switched on after someone deleted it from the command + * block — the one place that decides. */ import { defineStack, type ObjectStackDefinition } from '@objectstack/spec'; diff --git a/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts b/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts index 22192dea99..1524ec6fb7 100644 --- a/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts @@ -14,19 +14,26 @@ * * The `source-hashes` flag on the command above also emits * `.source-hashes.generated.ts` — the provenance companion from - * maintainer ruling #12069 Option A (#11671), rolled out to this bundle set by - * #12559. It is named here WITHOUT its leading dashes on purpose: - * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every - * recognised flag spelling out of this whole comment, so a second spelling in - * prose would keep the opt-in switched on after someone deleted it from the - * command — the command block is the one place that decides. Without it every generated leaf here is - * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the - * source was revised is indistinguishable BY VALUE from a real translation, so - * it publishes a superseded draft under a green `check:i18n` forever, and + * maintainer ruling #12069 Option A (#11671), rolled out to every bundle set by + * #12559. Without it every generated leaf here is LEGACY-TRUSTED: a leaf filled + * from the source and then left behind when the source was revised is + * indistinguishable BY VALUE from a real translation, so it publishes a + * superseded draft under a green `check:i18n` forever, and * `check:i18n-stale-fill` cannot see it either unless two locales happen to - * hold the same stale bytes. A record is written only for a leaf that IS - * currently a byte copy of the CURRENT source, so the companion arrives 0-stale - * by construction and only ever reports drift that accrues after it. + * hold the same stale bytes. + * + * A record is written only for a leaf that IS currently a byte copy of the + * CURRENT source, so the companion arrives 0-stale by construction and only + * ever reports drift accruing afterwards. Records count the leaves currently + * RECORDABLE, never the leaves covered — a table with few entries, or with none + * at all where a locale is fully translated, is the instrument armed, and an + * entry appears by itself on the first extract after a leaf becomes a fill. + * + * The flag is named in prose WITHOUT its leading dashes on purpose: + * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every + * recognised flag spelling out of this whole comment, so a second spelling + * would keep the opt-in switched on after someone deleted it from the command + * block — the one place that decides. */ import { defineStack, type ObjectStackDefinition } from '@objectstack/spec'; diff --git a/packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts b/packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts index 082b5adca4..786d18e203 100644 --- a/packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-webhooks/scripts/i18n-extract.config.ts @@ -14,19 +14,26 @@ * * The `source-hashes` flag on the command above also emits * `.source-hashes.generated.ts` — the provenance companion from - * maintainer ruling #12069 Option A (#11671), rolled out to this bundle set by - * #12559. It is named here WITHOUT its leading dashes on purpose: - * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every - * recognised flag spelling out of this whole comment, so a second spelling in - * prose would keep the opt-in switched on after someone deleted it from the - * command — the command block is the one place that decides. Without it every generated leaf here is - * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the - * source was revised is indistinguishable BY VALUE from a real translation, so - * it publishes a superseded draft under a green `check:i18n` forever, and + * maintainer ruling #12069 Option A (#11671), rolled out to every bundle set by + * #12559. Without it every generated leaf here is LEGACY-TRUSTED: a leaf filled + * from the source and then left behind when the source was revised is + * indistinguishable BY VALUE from a real translation, so it publishes a + * superseded draft under a green `check:i18n` forever, and * `check:i18n-stale-fill` cannot see it either unless two locales happen to - * hold the same stale bytes. A record is written only for a leaf that IS - * currently a byte copy of the CURRENT source, so the companion arrives 0-stale - * by construction and only ever reports drift that accrues after it. + * hold the same stale bytes. + * + * A record is written only for a leaf that IS currently a byte copy of the + * CURRENT source, so the companion arrives 0-stale by construction and only + * ever reports drift accruing afterwards. Records count the leaves currently + * RECORDABLE, never the leaves covered — a table with few entries, or with none + * at all where a locale is fully translated, is the instrument armed, and an + * entry appears by itself on the first extract after a leaf becomes a fill. + * + * The flag is named in prose WITHOUT its leading dashes on purpose: + * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every + * recognised flag spelling out of this whole comment, so a second spelling + * would keep the opt-in switched on after someone deleted it from the command + * block — the one place that decides. */ import { defineStack, type ObjectStackDefinition } from '@objectstack/spec'; diff --git a/packages/services/service-messaging/scripts/i18n-extract.config.ts b/packages/services/service-messaging/scripts/i18n-extract.config.ts index 10d649769b..ba53299533 100644 --- a/packages/services/service-messaging/scripts/i18n-extract.config.ts +++ b/packages/services/service-messaging/scripts/i18n-extract.config.ts @@ -13,19 +13,26 @@ * * The `source-hashes` flag on the command above also emits * `.source-hashes.generated.ts` — the provenance companion from - * maintainer ruling #12069 Option A (#11671), rolled out to this bundle set by - * #12559. It is named here WITHOUT its leading dashes on purpose: - * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every - * recognised flag spelling out of this whole comment, so a second spelling in - * prose would keep the opt-in switched on after someone deleted it from the - * command — the command block is the one place that decides. Without it every generated leaf here is - * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the - * source was revised is indistinguishable BY VALUE from a real translation, so - * it publishes a superseded draft under a green `check:i18n` forever, and + * maintainer ruling #12069 Option A (#11671), rolled out to every bundle set by + * #12559. Without it every generated leaf here is LEGACY-TRUSTED: a leaf filled + * from the source and then left behind when the source was revised is + * indistinguishable BY VALUE from a real translation, so it publishes a + * superseded draft under a green `check:i18n` forever, and * `check:i18n-stale-fill` cannot see it either unless two locales happen to - * hold the same stale bytes. A record is written only for a leaf that IS - * currently a byte copy of the CURRENT source, so the companion arrives 0-stale - * by construction and only ever reports drift that accrues after it. + * hold the same stale bytes. + * + * A record is written only for a leaf that IS currently a byte copy of the + * CURRENT source, so the companion arrives 0-stale by construction and only + * ever reports drift accruing afterwards. Records count the leaves currently + * RECORDABLE, never the leaves covered — a table with few entries, or with none + * at all where a locale is fully translated, is the instrument armed, and an + * entry appears by itself on the first extract after a leaf becomes a fill. + * + * The flag is named in prose WITHOUT its leading dashes on purpose: + * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every + * recognised flag spelling out of this whole comment, so a second spelling + * would keep the opt-in switched on after someone deleted it from the command + * block — the one place that decides. */ import { defineStack, type ObjectStackDefinition } from '@objectstack/spec'; diff --git a/packages/services/service-realtime/scripts/i18n-extract.config.ts b/packages/services/service-realtime/scripts/i18n-extract.config.ts index ef997b5b11..c196fdfd5c 100644 --- a/packages/services/service-realtime/scripts/i18n-extract.config.ts +++ b/packages/services/service-realtime/scripts/i18n-extract.config.ts @@ -9,37 +9,31 @@ * * os i18n extract packages/services/service-realtime/scripts/i18n-extract.config.ts \ * --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only --no-metadata-forms \ + * --source-hashes \ * --out=packages/services/service-realtime/src/translations * - * ## Provenance: LEGACY-TRUSTED BY CHOICE, and here is the measurement + * The `source-hashes` flag on the command above also emits + * `.source-hashes.generated.ts` — the provenance companion from + * maintainer ruling #12069 Option A (#11671), rolled out to every bundle set by + * #12559. Without it every generated leaf here is LEGACY-TRUSTED: a leaf filled + * from the source and then left behind when the source was revised is + * indistinguishable BY VALUE from a real translation, so it publishes a + * superseded draft under a green `check:i18n` forever, and + * `check:i18n-stale-fill` cannot see it either unless two locales happen to + * hold the same stale bytes. * - * #12559 rolled the generated-leaf provenance companion (maintainer ruling - * #12069 Option A, #11671) out to every other bundle set. This one is the - * measured exception, declared here rather than left silent — a set that is - * simply missing from a rollout reads as covered to anyone who does not go - * counting configs, which is the hazard #12559 was filed about. + * A record is written only for a leaf that IS currently a byte copy of the + * CURRENT source, so the companion arrives 0-stale by construction and only + * ever reports drift accruing afterwards. Records count the leaves currently + * RECORDABLE, never the leaves covered — a table with few entries, or with none + * at all where a locale is fully translated, is the instrument armed, and an + * entry appears by itself on the first extract after a leaf becomes a fill. * - * Measured on this set at rollout time: 23 generated leaves per locale, and - * **0** of them — in any of zh-CN, ja-JP, es-ES — is a byte copy of the current - * `en` source. The companion records a leaf only while it IS such a copy, so - * opting in here would emit three `.source-hashes.generated.ts` files - * containing zero entries. That is not coverage: all 23 leaves would stay - * legacy-trusted exactly as they are today, while three committed files - * announced an instrument that measures nothing. `sys_presence` is a single - * fully-translated object; there is no filled leaf here to record. - * - * **Revisit when that stops being true.** The declaration above is a claim - * about the tree, not a preference, and `presence-bundle-provenance.test.ts` - * next to the bundles keeps it honest: it fails the moment any locale's leaf - * becomes a byte copy of `en` — i.e. the moment this set gains a leaf the - * companion could record — and sends the reader back to this paragraph. - * - * ⛔ Do not name the opt-in flag with its leading dashes anywhere in THIS - * docstring, not even in prose arguing against it: `flagsFromDocstring` - * (scripts/i18n-bundle-surface.mjs) harvests every recognised flag spelling out - * of this comment and hands the result to the extractor, so a mention would - * silently convert this declaration into the opt-in it declares against. The - * flag is `source-hashes`; the files it would write are named above. + * The flag is named in prose WITHOUT its leading dashes on purpose: + * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every + * recognised flag spelling out of this whole comment, so a second spelling + * would keep the opt-in switched on after someone deleted it from the command + * block — the one place that decides. */ import { defineStack, type ObjectStackDefinition } from '@objectstack/spec'; diff --git a/packages/services/service-realtime/src/translations/es-ES.source-hashes.generated.ts b/packages/services/service-realtime/src/translations/es-ES.source-hashes.generated.ts new file mode 100644 index 0000000000..854c8b71cb --- /dev/null +++ b/packages/services/service-realtime/src/translations/es-ES.source-hashes.generated.ts @@ -0,0 +1,21 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'es-ES'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const esESGeneratedSourceHashes: Readonly> = { +}; diff --git a/packages/services/service-realtime/src/translations/ja-JP.source-hashes.generated.ts b/packages/services/service-realtime/src/translations/ja-JP.source-hashes.generated.ts new file mode 100644 index 0000000000..517826c13d --- /dev/null +++ b/packages/services/service-realtime/src/translations/ja-JP.source-hashes.generated.ts @@ -0,0 +1,21 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ja-JP'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const jaJPGeneratedSourceHashes: Readonly> = { +}; diff --git a/packages/services/service-realtime/src/translations/presence-bundle-provenance.test.ts b/packages/services/service-realtime/src/translations/presence-bundle-provenance.test.ts deleted file mode 100644 index 3c49a8d79f..0000000000 --- a/packages/services/service-realtime/src/translations/presence-bundle-provenance.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. -// -// Keeps this package's LEGACY-TRUSTED declaration honest (#12559). -// -// #12559 rolled the generated-leaf provenance companion (maintainer ruling -// #12069 Option A, #11671) out to eight of the nine i18n bundle sets. This one -// opted out, and `scripts/i18n-extract.config.ts` says why in prose: not one of -// its generated leaves is a byte copy of the current `en` source, and the -// companion records a leaf ONLY while it is such a copy — so opting in would -// commit three tables with zero entries, announcing an instrument that measures -// nothing while all 23 leaves stayed legacy-trusted regardless. -// -// That reason is a MEASUREMENT, and a measurement written in prose stops being -// true without telling anyone. The day `sys_presence` gains a field whose label -// is filled from the source — a new option, a new help string, anything the -// translators have not reached yet — the opt-out's premise is gone and the -// companion would have something real to record. This test is the trigger for -// that moment: it fails on the first byte copy and names the paragraph to go -// re-read. -// -// It deliberately does NOT assert the leaves differ in any particular way, and -// it is not a translation-quality check: `check:i18n-coverage` owns presence, -// and `check:i18n` owns bundle freshness. The one fact here is the one the -// opt-out rests on. - -import { describe, it, expect } from 'vitest'; -import { enObjects } from './en.objects.generated.js'; -import { zhCNObjects } from './zh-CN.objects.generated.js'; -import { jaJPObjects } from './ja-JP.objects.generated.js'; -import { esESObjects } from './es-ES.objects.generated.js'; - -/** Every string leaf keyed by dotted path — the extractor's own leaf identity. */ -function collectLeaves(node: unknown, path = '', out = new Map()): Map { - if (typeof node === 'string') { - out.set(path, node); - return out; - } - if (node && typeof node === 'object' && !Array.isArray(node)) { - for (const [key, child] of Object.entries(node)) collectLeaves(child, path ? `${path}.${key}` : key, out); - } - return out; -} - -const TRANSLATED = { - 'zh-CN': zhCNObjects, - 'ja-JP': jaJPObjects, - 'es-ES': esESObjects, -} as const; - -describe('service-realtime is legacy-trusted by measurement, not by omission (#12559)', () => { - const source = collectLeaves(enObjects); - - it('the bundles are non-empty, so an all-clear below is a reading rather than an empty walk', () => { - // Without this, every assertion beneath it would pass just as loudly on a - // bundle that failed to load — the "0 findings because 0 inputs" shape - // #12559 was filed about. - expect(source.size).toBeGreaterThan(0); - for (const [locale, bundle] of Object.entries(TRANSLATED)) { - expect(collectLeaves(bundle).size, `${locale} bundle walked to zero leaves`).toBe(source.size); - } - }); - - it.each(Object.keys(TRANSLATED))('%s holds no leaf that is a byte copy of the current en source', (locale) => { - const copies = [...collectLeaves(TRANSLATED[locale as keyof typeof TRANSLATED])] - .filter(([path, value]) => source.get(path) === value) - .map(([path]) => path); - expect( - copies, - `${locale} now carries ${copies.length} leaf/leaves filled from the source (${copies.join(', ')}). ` + - "That is exactly what the provenance companion records, so this package's opt-out in " + - 'scripts/i18n-extract.config.ts ("Provenance: LEGACY-TRUSTED BY CHOICE") no longer holds — ' + - 'either translate the leaf, or opt this set in by documenting the source-hashes flag in that ' + - 'config and committing the generated companions alongside the bundles.', - ).toEqual([]); - }); -}); diff --git a/packages/services/service-realtime/src/translations/zh-CN.source-hashes.generated.ts b/packages/services/service-realtime/src/translations/zh-CN.source-hashes.generated.ts new file mode 100644 index 0000000000..f74e0d3d8a --- /dev/null +++ b/packages/services/service-realtime/src/translations/zh-CN.source-hashes.generated.ts @@ -0,0 +1,21 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-CN'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const zhCNGeneratedSourceHashes: Readonly> = { +}; diff --git a/packages/services/service-storage/scripts/i18n-extract.config.ts b/packages/services/service-storage/scripts/i18n-extract.config.ts index 770d8abf8f..aeea76ab3e 100644 --- a/packages/services/service-storage/scripts/i18n-extract.config.ts +++ b/packages/services/service-storage/scripts/i18n-extract.config.ts @@ -17,19 +17,26 @@ * * The `source-hashes` flag on the command above also emits * `.source-hashes.generated.ts` — the provenance companion from - * maintainer ruling #12069 Option A (#11671), rolled out to this bundle set by - * #12559. It is named here WITHOUT its leading dashes on purpose: - * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every - * recognised flag spelling out of this whole comment, so a second spelling in - * prose would keep the opt-in switched on after someone deleted it from the - * command — the command block is the one place that decides. Without it every generated leaf here is - * LEGACY-TRUSTED: a leaf filled from the source and then left behind when the - * source was revised is indistinguishable BY VALUE from a real translation, so - * it publishes a superseded draft under a green `check:i18n` forever, and + * maintainer ruling #12069 Option A (#11671), rolled out to every bundle set by + * #12559. Without it every generated leaf here is LEGACY-TRUSTED: a leaf filled + * from the source and then left behind when the source was revised is + * indistinguishable BY VALUE from a real translation, so it publishes a + * superseded draft under a green `check:i18n` forever, and * `check:i18n-stale-fill` cannot see it either unless two locales happen to - * hold the same stale bytes. A record is written only for a leaf that IS - * currently a byte copy of the CURRENT source, so the companion arrives 0-stale - * by construction and only ever reports drift that accrues after it. + * hold the same stale bytes. + * + * A record is written only for a leaf that IS currently a byte copy of the + * CURRENT source, so the companion arrives 0-stale by construction and only + * ever reports drift accruing afterwards. Records count the leaves currently + * RECORDABLE, never the leaves covered — a table with few entries, or with none + * at all where a locale is fully translated, is the instrument armed, and an + * entry appears by itself on the first extract after a leaf becomes a fill. + * + * The flag is named in prose WITHOUT its leading dashes on purpose: + * `flagsFromDocstring` (scripts/i18n-bundle-surface.mjs) harvests every + * recognised flag spelling out of this whole comment, so a second spelling + * would keep the opt-in switched on after someone deleted it from the command + * block — the one place that decides. */ import { defineStack, type ObjectStackDefinition } from '@objectstack/spec';