Repository navigation
fix(plugin-webhooks,plugin-audit,plugin-security): re-translate the object leaves that contradict their current en source - #20684
Conversation
…dict their current en source sys_webhook.fields.definition_json.help told a ja-JP / es-ES / zh-CN admin that the JSON carries the full headers / auth / retry / payload config; en says credentials are NOT stored there (signing_secret and headers_secret are the encrypted fields). sys_webhook.description named an HTTP connector plugin as the executor; en names the webhook auto-enqueuer and the shared HTTP outbox. Values only. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
…r current en source sys_activity.fields.environment_id.label / .help still named the retired v5.0 term project (ja-JP / es-ES / zh-CN), where en says Environment; the v5.0 rename ships no alias. sys_audit_log.fields.user_id.label still called the sys_user lookup the actor, where en relabelled it User when the object gained its own `actor` principal field (ADR-0014 D2): es-ES showed two fields labelled "Actor", ja-JP / zh-CN two synonyms. Values only. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
…its current en source The ja-JP / es-ES / zh-CN leaves still described positions as definitions "for RBAC access control": the ADR-0090 P1 rename replaced the word role and kept the old framing. en says positions distribute capability (ADR-0090: capability = permission_set, distribution = position). The es-ES leaf was edited after en moved, so only the penultimate-edit check sees it. Values only. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
…rity for the re-translated leaves Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
…ugin-bundle-leaves
📓 Docs Drift CheckThis PR changes 3 package(s): 13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 9 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ed20046e47d58f22f8eb01c455f6fadd81f7a773 && git checkout ed20046e47d58f22f8eb01c455f6fadd81f7a773
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 139bef809cda0084d56544bce1e6c58783da1a8e 5444bb130b1ed024c63208e0880796e116643b96 && git checkout -B drift-repro 139bef809cda0084d56544bce1e6c58783da1a8e && git merge --no-ff 5444bb130b1ed024c63208e0880796e116643b96
node scripts/docs-audit/affected-docs.mjs --json 139bef809cda0084d56544bce1e6c58783da1a8e
|
… commits that decided them (objectstack-ai#20693) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the fifth stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/services/service-datasource/src/**` and nothing else. By the seat's fresh census at the claim (`5894843429`), it is the largest package in the lane that no in-flight work holds. Later stages cover the other packages, so this PR says `Part of` and the card stays open. Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), by the method of stages 1 to 4 (PR objectstack-ai#20609 as `422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR objectstack-ai#20658 as `9a4b2bb38`). That is **75 sites on 74 lines in 23 files, covering 16 numbers**: - 44 census sites (every census site this package has); - 31 sites in test comments, which the census defers. Each rewritten line now cites the commit in `origin/main` history that decided what the line describes, and says in its own words what was decided: **17 distinct shas**. `objectstack-ai#8696` was one card fixed in two halves, so its lines cite the half they describe: the mysql DSN branch (`72050cc47`) or the mongodb DSN branch (`90a12fb18`). `PR objectstack-ai#8588` was itself a pull request, and it now cites its squash commit `3dede582b`. No number in this package has an ADR or ruling record of its own in the repository (a grep of `docs/adr/` for all 16 finds none), so every anchor is a commit, per ruling C's order. No number was dropped. Only comments changed. Every touched source file keeps its line count (78 lines out, 78 in, over 23 files), so no line citation into these files moves. 4 of those 78 lines hold no dead citation; they are reflow, listed under Wordings below. No code token moves (see the guard below). **No citation number is added.** Every tracker number on an added line was already on the line it replaces: the only one is `objectstack-ai#12482`, which resolves and stood on `datasource-connection-service.ts:101` before. Over the whole diff, added minus removed is 0 or negative for every number, and no number is new to the diff. No PR number stands on an added line. Thirteen dead sites are left on purpose, all of them test titles (see the list below). One more file: a `patch` changeset for `@objectstack/service-datasource`, because the rewritten docblocks ship (see Changeset below). ## Census: `service-datasource`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/services/service-datasource/`. Each run counts as a reading only because its board frontier equals the newest issue number, read by a separate request just before and just after the run. | reading | tree | board | whole-repo `allocated-but-absent` | service-datasource sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `6981abfd2`, run 2026-09-29T17:02:34Z to 17:06:27Z | enumerated, 186 pages, frontier objectstack-ai#20684 (newest objectstack-ai#20684 before and after), 18,511 numbers | 1,318 | **44** | 43 | 9 | 14 | | after | head `f5ec6bacd`, run 17:18:37Z to 17:22:33Z | enumerated, 186 pages, frontier objectstack-ai#20686 (newest objectstack-ai#20686 before and after), 18,513 numbers | 1,274 | **0** | 0 | 0 | 0 | The before count matches the seat's census at the claim (44 sites in 9 files, at `6bff748b`). The whole-repo drop is 44, exactly this diff's census sites. The `resolves` tally is 32,909 in both runs, and `resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (994) did not move either. The after run was taken on `f5ec6bacd`; the head `265dc6861` adds only the changeset. No run was truncated or discarded: all four enumerations in this stage (two census runs and the two supplementary boards below) read 186 pages at the newest frontier. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `classifyCitation` over every `.ts` file under `service-datasource/src` (61 files). It uses one board for both trees, enumerated by the gate's own `enumerateBoard` at 17:22:42Z (186 pages, frontier objectstack-ai#20686, equal to the newest). | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `6981abfd2` | 791 | **88** | 44 | 31 | 0 | 13 | | after, `f5ec6bacd` | 716 | **13** | 0 | 0 | 0 | 13 | Its src-comment column equals the census's 44, which is the control on the second instrument. The 646 resolving and 57 pull-request citations are the same in both readings, and the drop of 75 citations is exactly the rewritten sites. An earlier board (17:07:08Z, frontier objectstack-ai#20685) gave the same base reading. A third, raw reading (every `#` followed by digits, judged against the same board, whatever surrounds it) finds 88 dead occurrences before and 13 after, and its residue equals the gate's residue site for site. ## Per-number table Sites and files count every dead occurrence in scope at the base (comments and strings, tests included). `rewritten / left` counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject. | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#6268` | 6/4 | 6/0 | `68f5eccb1`: the libSQL/Turso host loader gets one owner (`@objectstack/runtime`), and `MissingDriverPackageError` becomes one class across both hosts, because `serve.ts` decides fatality with `instanceof`. The cli and runtime stages' anchor | | `objectstack-ai#6345` | 9/5 | 9/0 | `e2798fab7`: one driver vocabulary; `mongo` renamed to `mongodb`, `turso` made a full builtin with a contract, and this factory's dispatch made exhaustive. The spec stages' anchor | | `objectstack-ai#8588` | 1/1 | 1/0 | `3dede582b`: `external.credentialsRef` (and only it) allowed on `schemaMode: 'managed'`; `objectstack-ai#8588` was that pull request, and this is its squash commit | | `objectstack-ai#8696` | 13/4 | 10/3 | two halves: `72050cc47`, a bound `credentialsRef` reaches the mysql client on the DSN branch as `{ uri, password }` (5 sites); `90a12fb18`, the mongodb DSN branch carries it in `options.auth` beside an unmodified url (5 sites). The spec stage's anchor for the mongo half | | `objectstack-ai#8873` | 8/3 | 7/1 | `096106522`: a bound `credentialsRef` reaches the postgres SERVER on the DSN branch; `connectionString` is dropped and `pg` gets its own parse of the url with the credential attached. The spec stage's anchor | | `objectstack-ai#8874` | 9/2 | 7/2 | `d70428ae7`: a declared `ssl` reaches the mysql client on both branches, in the spelling `mysql2` accepts (`{}`, never `true`). The spec stage's anchor | | `objectstack-ai#8876` | 1/1 | 1/0 | `d634e665b`: `urlUserinfoUsername` exported, the username half of the shared URL userinfo grammar. The spec stage's anchor | | `objectstack-ai#9040` | 4/3 | 2/2 | `24206416a`: a credential in the mongo options passthrough (`config.options.auth.password`) is refused at publish. The spec stage's anchor | | `objectstack-ai#9041` | 2/2 | 2/0 | `d491625c1`: a bound `credentialsRef` with a user-less mongo `config.url` is refused at the one door that sees both halves. The spec stage's anchor | | `objectstack-ai#10537` | 3/2 | 3/0 | `e634ecf6a`: `POST /external/validate` scoped to the URL's datasource; it adds `validateDatasource`. The rest and runtime stages' anchor | | `objectstack-ai#10962` | 5/2 | 4/1 | `29d067646`: one live introspection per datasource per validation sweep, memoised per call and never per instance (its message names `objectstack-ai#10962`) | | `objectstack-ai#11166` | 5/1 | 4/1 | `735f5c709`: an unreachable remote is the new `unreachable` diff kind, not `missing_table`. The runtime stage's anchor | | `objectstack-ai#12010` | 9/5 | 8/1 | `77b91bdb4`: `ConnectionEngineLike` derived from the engine contract, and `registerDriver` stops promising it accepts any value. The runtime stage's anchor | | `objectstack-ai#12248` | 1/1 | 1/0 | `8425c17cc`: the ruled engine members adopted onto `IDataEngine`, the datasource-lifecycle trio among them. The spec stage's anchor | | `objectstack-ai#12943` | 3/2 | 3/0 | `090f2302e`: the guarded optional-driver loads declared as optional peers of this package. The cli and runtime stages' anchor | | `objectstack-ai#13279` | 9/4 | 7/2 | `6a180e42d`: a failed permission-store read raises `AuthzStoreUnavailableError` (503) instead of reading as zero grants; its message carries the 2026-08-30 ruling, and it moved `driver-error-classification.ts` into `@objectstack/types`. The anchor of stage 2 and of the rest, runtime and types stages | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each of the 17), and every one is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 17; the history is complete, `--is-shallow-repository` false, 15,110 commits). Where an earlier stage already anchored a number, this stage reuses that anchor after checking it against this package's lines. New to the sweep here: `72050cc47` (the mysql half of `objectstack-ai#8696`), `3dede582b` and `29d067646`. ## Wordings to check - **`objectstack-ai#8696`'s two halves.** The mysql arm's lines (`default-datasource-driver-factory.ts:718`, `:824`, and `bound-secret-dsn-branches.test.ts:4`, `mysql-dsn-ssl.test.ts:189`, `:263`) cite `72050cc47`; the mongo arm's lines (`default-datasource-driver-factory.ts:926`, `:954`, `:1243`, `datasource-credential-migration.ts:182`, and the heading `bound-secret-dsn-branches.test.ts:72`, 「the mongodb half, added second」) cite `90a12fb18`. - **A referent, `datasource-connection-service.ts:95-96`.** 「the inventory that filed that card」 lost its referent with the number, so it now says 「the inventory that filed its card」, the card behind `77b91bdb4` (1 reflow line). - **`datasource-connection-service.ts:100-101`.** 「objectstack-ai#12248 adjudicated all three onto IDataEngine」 became 「Commit 8425c17 adopted all three onto IDataEngine per the ruling」: the ruling decided and the commit carried it out, as its changeset says (1 reflow line). - **What the card described, `default-datasource-driver-factory.ts:412` and `mysql-dsn-ssl.test.ts:40`.** 「the one objectstack-ai#8874 describes as honouring」 became 「the one commit d70428a's card describes as honouring」, since the words quote the card, not the commit. - **A cross-reference, `default-datasource-driver-factory.ts:736`.** 「the falsy-value note under objectstack-ai#8874 below」 points at the heading at `:767`, which now carries `commit d70428a`, so the pointer names the same anchor. - **A future tense made past, `postgres-dsn-bound-secret.test.ts:223`.** 「the authoring door (objectstack-ai#9041), which this card lands before」 became 「(commit d491625), which landed after this pin」. `096106522` (this file's commit) is an ancestor of `d491625c1`, both on 2026-08-16. - **`external-datasource-service.test.ts:444`.** 「The card's measured defect」 became 「Its card's measured defect」, the card behind `735f5c709`; `:588` 「the pre-objectstack-ai#10537 route」 became 「the route … before commit e634ecf」. - **`datasource-admin-service.test.ts:674`.** 「Before PR objectstack-ai#8588」 became 「Before commit 3dede58」, the squash commit of that pull request, which answers 404. - **Reflow, 4 lines with no dead site** (every file keeps its line count): `datasource-connection-service.ts:96`, `:101`, `default-datasource-driver-factory.ts:825`, `:826`. ## The 13 sites left - **Test titles, 13 sites.** `describe` / `it` titles, which are string tokens, left as stages 1 to 4 left theirs: `admin-routes-authz-outage-envelope.test.ts:158` (`objectstack-ai#13279`); `admin-routes-tenancy-posture-admission.test.ts:557` (`objectstack-ai#13279`); `bound-secret-dsn-branches.test.ts:136`, `:244` (`objectstack-ai#8696`); `connection-engine-like-contract.test.ts:21` (`objectstack-ai#12010`); `datasource-config-redaction.test.ts:406` (`objectstack-ai#9040`); `datasource-credential-migration.test.ts:226` (`objectstack-ai#9040`); `external-datasource-service.test.ts:453` (`objectstack-ai#11166`), `:690` (`objectstack-ai#10962`); `mysql-dsn-ssl.test.ts:165`, `:324` (`objectstack-ai#8874`), `:260` (`objectstack-ai#8696`); `postgres-dsn-bound-secret.test.ts:160` (`objectstack-ai#8873`). - There is no operator string, assertion message, generated header or quoted ruling carrying a dead number in this package. The verbatim maintainer quotations in scope (「同意」 and 「同意所有」, on 8 lines) carry no dead number and are untouched. ## Mechanical guard: no code token moves The guard compares the TypeScript parser's leaf nodes, with comments as trivia and JSDoc nodes never visited, base `6981abfd2` against head. Template literals are therefore read in context. It ran over all 23 touched `.ts` files. - Real run: 24,055 base leaf tokens, **0 files with a token change** (exit 0). - Comment control in `default-datasource-driver-factory.ts` (`Lazy + caught exactly like` to `Lazy and caught exactly like`): 0 files changed, as expected (exit 0). - Positive control, a code token added in `default-datasource-driver-factory.ts` (`const url = resolveTursoUrl(spec);` given a trailing `?? undefined`): DIFFER (exit 1). - Positive control, one digit changed inside a kept test title (`mysql-dsn-ssl.test.ts:165`): DIFFER (exit 1). Every mutation went through `scripts/ablation-replace.mjs`, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (`8c164aa6178f`, `2feeaeb0411d`), with `git diff HEAD` empty and a clean tree afterwards. A first draft of the guard used the bare scanner, which loses template context and reported token changes inside comments; it was replaced by the parser walk before any reading was taken from it. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/service-datasource` (`.changeset/20596-service-datasource-provenance-anchors.md`) is included. It says only that the provenance comments were re-anchored, in stages 3 and 4's words. Measured on the built package (A3): `files[]` is `dist`, `README.md` and `CHANGELOG.md`. After the build, the rewritten comments reach `dist`: `6a180e42d`, `e2798fab7` and `e634ecf6a` once, and `29d067646` three times, in each of `dist/index.d.ts`, `index.d.cts`, `index.js` and `index.cjs`; `68f5eccb1` 4 times, `090f2302e` twice, and `77b91bdb4` and `8425c17cc` once each, in both declaration files. Positive control: the unchanged line 「`registerDatasourceDef`, `markDatasourceUnavailable`,」 beside the shipped rewrite at `datasource-connection-service.ts:95` is found once in `index.d.ts`. A never-written negative phrase appears nowhere in `dist`. No dead number of the 16 is left anywhere in `dist`. ## Gates (head `265dc6861`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` (self-test) exits 0. `node scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged 1 citation (`objectstack-ai#12482`), and it resolves. - **Doc authoring:** `pnpm check:doc-authoring` exits 0, with the sibling-package prose ids at their baseline and no growth. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `265dc6861` derived 63 commands: all 54 derived at dispatch, plus `check:duration-unit-keys`, `check:dispatcher-error-vocabulary`, `check:engine-double-contract`, `check:logger-receiver-detach`, `check:objectql-double-limit`, `check:query-options-erasure`, `check:type-check-coverage`, `check:type-check-debt` and `check:where-matcher`. Each ran with its exit code captured before any pipe, and all 63 exit 0. `--ran`, fed each command with its exit code, reports 63 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. A full `turbo run build` of `./packages/*` and `./packages/*/*` ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace. - **Roster families the derivation lists outside its commands** (their rosters sit in directories this diff touches): `node scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm check:filter-alias-parity`, each exit 0. - **Tests and typecheck, under the verify lock:** - `pnpm --filter @objectstack/service-datasource test`: 34 files pass and 693 tests pass. That is every test file in the package, the 14 touched ones included. - `pnpm --filter @objectstack/service-datasource typecheck` exits 0. Its `tsconfig.json` includes all of `src`, and `--listFiles` shows all 61 files under `src/`, the 34 test files included, and all 23 touched files in the program. - **Lint, as a proven narrowing:** `eslint --no-inline-config --format json` over the 23 touched `.ts` files gives 23 files, 0 errors and 0 warnings. All 23 are in eslint's own population (`isPathIgnored` is false for each). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. - **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of the 24 changed files for control bytes finds none. ## Acceptance notes - **The gate-invisible spellings, grepped as the claim asked.** `CITATION_RE` refuses a hyphen after the digits and a `/` before the `#` (objectstack-ai#20636). In this package there is no `#N-word` spelling at all. There are 7 `#A/#B` lines (`admin-routes.ts:28`, `datasource-route-ledger.ts:159`, `turso-driver-config.ts:132`, `external-introspection-seam.test.ts:14`, `:102`, `:163`, `turso-bound-secret-authoring.test.ts:8`), and every second number on them is live: `objectstack-ai#10998`, `objectstack-ai#4251` and `objectstack-ai#4249` are issues, and `objectstack-ai#8078`, `objectstack-ai#4176` and `objectstack-ai#4202` are pull requests. So nothing there needed rewriting. The raw scan above, which sees both spellings, agrees. - **The census instrument did not truncate in this stage.** Four enumerations read 186 pages each at the newest frontier. - **Anchors the next stages can reuse**, each checked here: `objectstack-ai#13279` → `6a180e42d`; `objectstack-ai#12010` → `77b91bdb4`; `objectstack-ai#6345` → `e2798fab7`; `objectstack-ai#6268` → `68f5eccb1`; `objectstack-ai#12943` → `090f2302e`; `objectstack-ai#8696` → `72050cc47` (mysql) or `90a12fb18` (mongodb); `objectstack-ai#8873` → `096106522`; `objectstack-ai#8874` → `d70428ae7`; `objectstack-ai#10962` → `29d067646`. - **Base.** The branch is 5 commits behind `origin/main` (`14f80e239`, read at 17:27Z). None touches `service-datasource`, `scripts/` or `.changeset/config.json`, so there was no merge. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…tradict their current en source (objectstack-ai#20707) Fixes objectstack-ai#20666 Clause-②: no ## What changed A translated leaf in `packages/platform-objects/src/apps/translations/{ja-JP,es-ES,zh-CN}.metadata-forms.generated.ts` that a translator wrote by hand keeps its value when its `en` source changes later. This PR measures that set first, with PR objectstack-ai#20652's instrument and the two widening checks the services seat named, then re-translates the leaves whose meaning now **contradicts** the current `en`: **12 leaves** (ja-JP 4, es-ES 4, zh-CN 4). - Values only. No key is added or dropped, no `en` file is edited, and no provenance companion or echo-decision ledger changes (measured below; the tooling requires none). - `.changeset/20666-stale-authored-metadata-form-leaves.md`: `@objectstack/platform-objects` `patch`. - No new gate, per triage. No test pins any of the old or new strings. - File surface held: the three translated `metadata-forms` bundles and the changeset. Not the object bundles, not the plugin bundles, and not `{en,ja-JP,es-ES,zh-CN}.ts` or `*.source-hashes.ts` (PR objectstack-ai#20699). ## The measurement (base `a8acee28d`, before any edit) ### Instruments The clone is not shallow (`git rev-parse --is-shallow-repository` answers `false`, 15122 commits), so every walk below reads full history. 1. **Since last edit** (PR objectstack-ai#20652's condensed instrument, ported with two edits: the bundle kind `objects.generated.ts` becomes `metadata-forms.generated.ts`, and the recorded-copy test reads `"metadataForms.PATH"` in both `LOCALE.source-hashes.generated.ts` and `LOCALE.source-hashes.ts`). Population: every string leaf of the locale's metadata-forms bundle, minus echoes of the current `en` and recorded byte copies. Last edit: the first-parent commit where the parsed value last changed. A leaf is a candidate when `en` at that commit differs from `en` today. Meaning is then judged by hand. 2. **Renamed key.** A leaf whose path was born at its last edit, while the same value sat under a path (any path in the bundle, not only a sibling) that disappeared in that commit. For each, `en` at the old path's last edit is compared with `en` at the new path today. 3. **Penultimate edit.** For each non-candidate, `en` at its previous edit, and whether `en` moved in the last-edit commit itself. Every flag was read by hand, including the ones where `en` moved in the same commit. 4. **Merge side.** For a leaf last edited by a merge commit, `en` is also read at the second parent. 5. **Retired term.** The locale's word for `project`, `department`, `role` (ADR-0090) or `profile` (ADR-0090 D2), where `en` at the same path does not carry the English term. The raw ja-JP `role` pattern hit four leaves, all false positives (ロールアップ roll-up three times, コントロール control once); the refined pattern excludes both and hits 0. 6. **Retired concept** (added here; the concept-level twin of PR objectstack-ai#20652's retired-term scan). For each re-model the contradicting candidates surfaced, the locale's words for the retired concept, flagged where `en` at the same path does not carry it: agent tools (ADR-0109 removed `agent.tools[]`, scope `agent.*`); `PermissionSet.contextVariables` (removed by the ADR-0105 commit `879ea1304`); sharing rules named for RLS; an email template sent by `id` with a content type (`47a92f427` re-modelled the type); a report block that joins objects (ADR-0021 dataset-bound reports, `18178454c`, scope `report.*`). 7. **Carve-in continuity.** The zh-CN leaves last edited at `e0077ea36` were carried in from a hand overlay, `packages/platform-objects/src/metadata-translations/zh-CN.ts`, born at `dc721729a` and consolidated at `e0077ea36` the same day. So the instrument dates them at the carve-in. Between those two commits `packages/spec/src` changed no form label, description or help string (`git diff dc72172 e0077ea -- packages/spec/src`: two changed `label` / `description` lines, both schema declarations, no string). The carve-in dating therefore hides no source move inside the overlay's lifetime. ### Known-positive controls - `permission.sections.tab_and_row_level_security.description` (line 2251 of all four bundles) and `agent.sections.capabilities.description` (line 2347). The since-last-edit instrument flags both in **all three** locales. In ja-JP and es-ES both promise what `en` dropped (custom context variables; tools). zh-CN's agent leaf promises tools too, but zh-CN's permission leaf never promised context variables: it names the section after sharing rules, read below. - Renamed-key check: `dashboard.fields.refreshIntervalSeconds.label`, renamed from `refreshInterval` at `e9fcd6bbd` with the value carried. It fires in all three locales. - Retired-concept scan: it fires on every one of the four contradicting paths in ja-JP and es-ES, and on the three in zh-CN. - Provenance (for H2): with ja-JP `agent.sections.capabilities.description` set to the `en` string, `check-i18n-bundles --filter=platform-objects` goes red, `platform-objects DRIFTED (1)` (through `scripts/ablation-replace.mjs`: anchor 1 to 0, blob `1b912e07c279` to `70cb7a0e1ba3`, restored: blob equals HEAD and `git diff HEAD` is empty). In hold mode, `--write` adds exactly one row, `"metadataForms.agent.sections.capabilities.description"`, to `ja-JP.source-hashes.generated.ts`. Both files were restored with `git checkout HEAD --`, and their blobs equal HEAD's (`1b912e07c279`, `2429910c839c`). ### Population and radius - **Covered:** the `metadata-forms` bundles of `@objectstack/platform-objects` for ja-JP, es-ES and zh-CN. There are 1116 leaves per locale. Echoes of the current `en` number 0 / 0 / 0, recorded byte copies 0, and authored leaves **1116 / 1116 / 1116**. History: every first-parent commit since the bundles were created at `ae2da1e7d` (69 / 70 / 70 commits touching each locale file, 56 for `en`), plus the zh-CN overlay's lifetime (instrument 7). - **Not covered:** a leaf that was wrong when it was authored and whose `en` never moved, outside the vocabularies of instruments 5 and 6; where the zh-CN overlay's strings came from before `dc721729a` (the file was born with them); the object bundles, the plugin bundles and the other packages' bundle sets. ### Counts per locale | | ja-JP | es-ES | zh-CN | |---|---|---|---| | authored leaves | 1116 | 1116 | 1116 | | candidates (`en` moved since last edit), before | 9 | 9 | 6 | | of which contradicting, before | 4 | 4 | 3 | | penultimate-edit flags, before (contradicting) | 18 (0) | 18 (0) | 22 (0) | | renamed-key flags, before (contradicting) | 1 (0) | 1 (0) | 1 (0) | | merge-side flags, before | 0 | 0 | 0 | | retired-term flags, before | 0 | 0 | 0 | | retired-concept flags outside the candidates, before (contradicting) | 0 | 0 | 1 (1) | | **re-translated here** | **4** | **4** | **4** | | candidates after (at `aaee46778`) | 5 | 5 | 3 | | contradicting after | 0 | 0 | 0 | | retired-concept flags after | 0 | 0 | 0 | The raw candidate counts equal the objectstack-ai#20653 dev's 9 / 9 / 6 exactly. After, both walks re-run at `aaee46778`: each after-candidate set is exactly the before set minus the re-translated paths (0 added), the echo count is still 0 (no new value equals its `en` leaf), and the renamed-key flag is the same one. The penultimate-edit flags after are the before set plus the 11 re-translated candidates (their last edit is now this PR, after `en` moved), which is the expected shape. ### The judgment rule As in PR objectstack-ai#20652 and PR objectstack-ai#20684. A leaf **contradicts** the current `en` when a reader who acts on it would believe something about the current form that `en` now says is false: `en` now denies what the leaf asserts; the type was re-modelled, so the leaf describes a different thing; or the leaf names a mechanism the section does not hold. Added detail, narrowing, rewording, punctuation and title-casing are not contradictions. ## Re-translated (12): before and after **`agent.sections.capabilities.description`**, all three. ADR-0109 removed `agent.tools[]` (`e2616e0cf`); an agent's tools come from its skills. `en`: `Skills and knowledge sources the agent can use.` - ja-JP: 「エージェントが使用できるスキル、ツール、ナレッジソース。」 → 「エージェントが使用できるスキルとナレッジソース。」 - es-ES: 「Skills, herramientas y fuentes de conocimiento que puede usar el agente.」 → 「Skills y fuentes de conocimiento que puede usar el agente.」 - zh-CN: 「代理可使用的技能、工具与知识来源」 → 「代理可使用的技能与知识来源」 **`permission.sections.tab_and_row_level_security.description`**, all three. `879ea1304` removed `PermissionSet.contextVariables` (enforce-or-remove, zero consumers) and its form row. `en`: `Tab visibility and RLS policies.` - ja-JP: 「タブ表示、RLS ポリシー、述語評価用カスタムコンテキスト変数。」 → 「タブ表示と RLS ポリシー。」 - es-ES: 「Visibilidad de pestañas, políticas RLS y variables de contexto personalizadas para evaluar predicados.」 → 「Visibilidad de pestañas y políticas RLS.」 - zh-CN: 「导航可见性与共享规则」 → 「标签页可见性与行级安全策略」. The old value named the section after **sharing rules**. In this repository 共享规则 is the zh-CN name of a different mechanism, the `sys_sharing_rule` object (`plugin-sharing`) and its own Setup entry `nav_sharing_rules`. The section holds `tabPermissions` and `rowLevelSecurity`. The new value uses the bundle's own words: 标签页 (this section's label), 行级安全策略 (the bundle's rendering of an RLS policy, as in `object.fields["access.default"].helpText`). **`report.fields.blocks.helpText`**, all three. The 9.0 single-form cutover (ADR-0021, `18178454c`) made every report block a dataset-bound sub-report; no block joins objects. `en`: `Dataset-bound sub-reports (joined report only)` - ja-JP: 「複数オブジェクトを結合(joined レポートのみ)」 → 「データセットにバインドされたサブレポート(joined レポートのみ)」 - es-ES: 「Une varios objetos (solo informe joined)」 → 「Subinformes vinculados a un conjunto de datos (solo informe joined)」 - zh-CN: 「joined 报表的联合查询块」 → 「绑定数据集的子报表(仅 joined 报表)」 **`email_template.sections.identity.description`**, ja-JP and es-ES. `47a92f427` promoted `email_template` to a first-class metadata type and rewrote its form. The Identity section now holds `name` / `label` / `category` / `locale` / `description`, with no `id` and no content type, and `IEmailService.sendTemplate` resolves the template by `name`. A reader who copied the old sample would pass `template: id`. zh-CN already said what `en` says. `en`: `Template identifier resolved by IEmailService.sendTemplate({ template: name, locale, ... }).` - ja-JP: 「識別子とコンテンツ型。id は sendTemplate({ template: id, ... }) で参照される。」 → 「IEmailService.sendTemplate({ template: name, locale, ... }) が解決するテンプレート識別子。」 - es-ES: 「Identificador y tipo de contenido. El id se referencia con sendTemplate({ template: id, ... }).」 → 「Identificador de plantilla que resuelve IEmailService.sendTemplate({ template: name, locale, ... }).」 **`report.sections.joined_blocks.label`**, zh-CN only. It was found by the retired-concept scan, not by the since-last-edit instrument, because `en` (`Joined blocks`) never moved. The zh-CN label said 关联对象 ("related objects"), the pre-9.0 model in which blocks joined objects. The section holds the `blocks` repeater of dataset-bound sub-reports. ja-JP 結合ブロック and es-ES Bloques unidos render `en` literally and are unchanged. - zh-CN: 「关联对象」 → 「joined 报表分块」 (分块 is `report.fields.blocks.label`, and `joined` stays the report-type token, as in 仅 joined 报表). ## Stale but not contradicting (listed, left as written) | path | locales | what `en` did | |---|---|---| | `object.sections.fields.description` | all three | "each row becomes a column" became "each entry becomes a column" (`9f8ec35c8`). Rewording. | | `hook.fields.condition.helpText` | all three | "Optional formula — skip the hook when this evaluates to false" became "CEL predicate — the hook runs only when TRUE" (`48efe915b`, which changed the row's editor language from `javascript` to `expression`). The runtime gate is `if (!conditionFn(ctx))` in `packages/objectql/src/hook-wrappers.ts`, whose own docblock says "skip when the formula evaluates FALSE", and the leaf makes no JavaScript claim. This is the closest call among the leaves left alone. | | `app.fields.defaultAgent.helpText` | ja-JP, es-ES | "AI agent" became "Platform agent", with the `ask` / `build` defaults added. Narrowing and detail. zh-CN already says what `en` says. | | `report.sections.joined_blocks.description` | ja-JP, es-ES | "blocks joined into a single report" became "dataset-bound blocks stacked into a single report". Combined into one report is still true, and the `en` label still says Joined blocks. The second closest call. zh-CN already says what `en` says. | | `report.sections.filter_and_chart.description` | all three | "Report-level filters" became "Render-time scope filter". The filter is still the report's. | | `dashboard.fields.refreshIntervalSeconds.label` (renamed key) | all three | The rename added the unit to the label. Each locale's `helpText` on the same field already says seconds (自動更新(秒), Actualización automática (segundos), 自动刷新间隔(秒)). | Penultimate-edit flags, all read by hand. Every one already says what `en` says: - `en` moved EARLIER than the last edit: `object.fields.isSystem.helpText`, `object.fields.fields.reference.helpText`, `object.fields.fields.trackHistory.helpText`, `view.fields.filter.helpText`, `page.fields.type.helpText`, `dashboard.fields.gap.helpText`, `action.sections.advanced.description`, `action.fields.body.helpText`, `report.sections.basics.description`, `report.fields.columns.helpText` and `dataset.fields.measures.helpText` (all three locales); plus, zh-CN only, `app.fields.defaultAgent.helpText`, `report.sections.joined_blocks.description`, `email_template.sections.identity.description` and `email_template.fields.variables.helpText`. - `en` moved IN the last-edit commit: `object.fields.indexes.fields.helpText`, `field.fields.precision.helpText`, `field.fields.maskingRule.helpText`, `page.fields.variables.source.label`, `app.sections.content.description`, `app.sections.access_and_sharing.description` and `permission.sections.identity.description` (all three locales). ## Dispatch hypotheses, measured - **H1 held, and widened.** The targets are exactly the three translated bundles against `en.metadata-forms.generated.ts`. Both known leaves are candidates in all three locales, and the raw counts reproduce as 9 / 9 / 6 at `a8acee28d`. Judged, 4 / 4 / 3 contradict. Two more paths besides the known two contradict: `report.fields.blocks.helpText` (all three) and `email_template.sections.identity.description` (ja-JP, es-ES). zh-CN's permission leaf contradicts for a different reason than the known one (sharing rules). The retired-concept scan added one zh-CN label. - **H2 held: values only.** The companions work as they do for the object bundles. `collectFilledFromHashes` records a `metadataForms` leaf only while it is a byte copy of the current `en` or of a recorded digest. Today that is 0 rows in all three locales, and a hand translation records nothing. At `aaee46778`, `node scripts/check-i18n-bundles.mjs --write --filter=platform-objects` printed `regenerated` and rewrote all eleven bundle and companion files on disk (every mtime moved), and `git status --porcelain` stayed empty. The control that shows the tool really reads these leaves is in "Known-positive controls" above. No echo-decision ledger row and no test names any of the 12 paths (`git grep` over the ledgers). `metadata-forms-vocabulary.test.ts` asserts group keys only. - **H3 held: both checks ran, and their hits are reported separately.** Renamed key: 1 / 1 / 1 hit (`dashboard.fields.refreshIntervalSeconds.label`), stale but not contradicting. Penultimate edit: 18 / 18 / 22 flags, 0 contradicting. ## Verification (all at `aaee46778`) - Build: `turbo run build --filter=@objectstack/cli... --filter=@objectstack/platform-objects... --concurrency=2`: 59/59 tasks, `VERDICT command-exit 0`. - `pnpm --filter @objectstack/platform-objects test`: 58 files, 942 tests passed. `pnpm --filter @objectstack/platform-objects typecheck`: exit 0, `check:test-typecheck: OK`. - The new values ship: the built `dist/metadata-translations/index.mjs` and `index.js` were imported, and `MetadataFormsTranslations` was read by path after `withSourceFallback`. In both, 12 of 12 re-translated rows equal the HEAD source and differ from the base, and 9 of 9 unchanged-leaf controls equal both. - Gates: `node scripts/pm/dispatch-gates.mjs --commands` (no paths) derived 55 commands from the real diff, and all 55 exit 0. `--ran` printed "55 derived famil(ies) accounted for — 55 run, 0 NOT-MEASURED (a DERIVED zero — all 55 recorded an exit code and none of them is 3)". `pnpm check:dual-build-cjs-loads` first refused with exit 3 (`PREREQUISITE NOT MET`: nine packages outside this diff had no `dist/`). Those were built and the gate re-ran: exit 0. - Named in the verdicts: `check:i18n` "OK (9 package(s) — all bundles in sync, no undeclared authoring keys)"; `check:i18n-stale-fill` "OK (10 bundle set(s) — no new stale fills, 0 baselined)"; `check:nul-bytes` exit 0. - Lint, narrowed to the three edited bundles: `eslint --no-inline-config --format json` read 3 files, 0 errors, 0 warnings. `ESLint#isPathIgnored` answers `false` for all three, read from the repo's own config. The config enables no type-aware linting (no `parserOptions.project` or `projectService`, `eslint.config.mjs` states it at line 328), so a change to string values in these files cannot move the verdict on any other file. The full `pnpm lint` is left to CI. - `origin/main` moved two commits past the base (`defc7f7b5`). Neither touches a form declaration or a translation bundle, so the measurement stands on it. ## Acceptance notes - `dataset-panel-echo-decisions.test.ts`, the reason on the `include` label row, cites "report.sections.joined_blocks.label is 关联对象" as a precedent for the word 关联. After this PR that label reads joined 报表分块, so the citation is out of date. It is prose: no assertion reads it, and the `include` decision stands on its own, since 关联 keeps authored precedents such as `sys_email.fields.related_object.label` 关联对象 in the objects bundle. The ledger is outside this card's file surface and is not edited here. - zh-CN `page.sections.data_context.description` reads 关联对象与变量 ("related objects and variables") against `en` "Record binding and page-local state." It is loose but does not contradict: the bound record's object is the related object, and the variables are the page-local state. No instrument flags it, and it is left as written. - The since-last-edit instrument cannot see a leaf that was wrong when it was authored while `en` never moved. Instruments 5 and 6 cover only the vocabularies they name. Instruments were run from the session scratchpad. --- _Generated by [Claude Code](https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY)_ Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20653
Clause-②: no
What changed
A translated leaf in a plugin's
src/translations/{ja-JP,es-ES,zh-CN}.objects.generated.tsthat a translator wrote by hand keeps its value when itsensource changes later. This PR measures that set in the four plugin bundles the card names (plugin-webhooks,plugin-audit,plugin-approvals,plugin-security), then re-translates the leaves whose meaning now contradicts the currenten: 18 leaves, six paths in all three locales (ja-JP 6, es-ES 6, zh-CN 6). The triage-ordered leaf,sys_webhook.fields.definition_json.help, is the first commit.enfile is edited, and no provenance companion (*.source-hashes.generated.ts) changes: the repo's own tooling was run and writes nothing (measured below)..changeset/20653-stale-authored-plugin-bundle-leaves.md:@objectstack/plugin-webhooks,@objectstack/plugin-audit,@objectstack/plugin-security, eachpatch.@objectstack/plugin-approvalshas no change and is not named.platform-objectsmetadata-forms leaves are the engine lane's card, i18n(platform-objects): authored ja-JP / es-ES / zh-CN metadata-forms leaves contradict their moved en source (the metadata-forms half of #20653) #20666, which remains open and is not touched here.The measurement (base
6bff748bb, before any edit)Instrument
PR #20652's condensed instrument, ported with one change: the bundle directory comes from argv instead of the
platform-objectsconstant. Population = every string leaf of the locale's objects bundle, minus echoes of the currentenand paths recorded in*.source-hashes.generated.ts; last edit = the first-parent commit where the parsed value last changed; a leaf is a candidate whenenat that commit differs fromentoday. Meaning is then judged by hand. Full history (the clone is not shallow:git rev-parse --is-shallow-repositoryanswersfalse).Four widening checks ran beside it:
packages/platform-objects/src/apps/translations/at44045721c(ADR-0029 D8: webhooks, approvals, security) andbe1b9165f(K2: audit). The condensed walk starts at the carve-out, so it dates every carried leaf there and reads theenof that day. The widened walk replays the platform-objects file's first-parent history (from its birth at6bacbced2, the horizon fix(platform-objects): re-translate the object leaves that contradict their current en source #20652 covered) before the plugin file's own. It re-dated every candidate the condensed walk had dated at a carve-out commit (for exampledefinition_json.help, to7bb92056e) and changed no candidate set.enis also read at the second parent. 0 hits.enat its previous edit, and whetherenmoved in the last-edit commit itself. Every flag whereenmoved EARLIER than the last edit was read by hand:sys_webhook.fields.method.help,sys_position.fields.name.helpandsys_position._actions.clone_position.params.name.helpTextalready say whatensays; es-ESsys_position._actions.deactivate_position.confirmTextdiffers by rewording only; es-ESsys_position.descriptioncontradicts (it was edited at4ea921ca8, afterenmoved, and kept the old framing), so it is re-translated.projectordepartmentwhereenat the same path does not carry the English term; the locale's word forroleorRBACanywhere in the four bundles; and a leaf whose path was born at its last edit while the same value sat under a sibling path of the same object that disappeared in that commit (a renamed key). One path, found by both the term scan and the renamed-key check:sys_activity.fields.environment_id.help. The key was renamed fromproject_idat944f18758with the value carried, so the since-last-edit instrument dates it at the rename, whereenalready said Environment, and cannot see it.Known-positive control
plugin-webhookssys_webhook.fields.definition_json.help, line 72 of all four bundles. The source has said "Credentials are NOT stored here" since160294963(the custom headers moved onto the encrypted channel, aftere3a6f6e7emoved the signing secret), and theenbundle since8af76aebf. The three translations were last edited at7bb92056e, whenensaid "full headers/auth/retry/payload config". The ported instrument flags it in all three locales. A1 held: the port is live before anything is counted.Counts per bundle (ja-JP / es-ES / zh-CN)
environment_id.help)sys_position.description)The before candidate counts equal the #20539 dev's raw counts exactly (A2). After = at
5444bb130, both instruments: each after-candidate set is exactly the before set minus the re-translated paths (0 added), the echo counts are unchanged (no new value equals itsenleaf), and the term scan and renamed-key check return 0. The penultimate-edit flags after are the before set minus es-ESsys_position.description, plus the 14 re-translated leaves that were candidates (their last edit is now this PR, afterenmoved), which is the expected shape.The judgment rule
As in PR #20652. A leaf contradicts the current
enwhen a reader who acts on it would believe something about the current field or object thatennow says is false:ennow denies what the leaf asserts; the object was re-modelled, so the leaf describes a different thing; or the leaf names the record's entity by a term a rename retired from it. Added detail, narrowing, rewording, punctuation and title-casing are not contradictions.Re-translated (18): before and after, with the
eneach now matches(ja-JP / es-ES / zh-CN, in each bundle's existing terms: 署名シークレット / カスタムヘッダー / アウトボックス / ケイパビリティ; secreto de firma / cabeceras personalizadas / outbox / capacidades / entorno; 签名密钥 / 自定义请求头 / 发件箱 / 授权能力 / 环境.)
plugin-webhooks·sys_webhook.fields.definition_json.help: said the JSON carries the full headers / auth / retry / payload config.ensays the opposite, on a security field.en: 「Serialised Webhook JSON (see @objectstack/spec/automation/webhook) — timeout and the rest of the authored envelope. Credentials are NOT stored here: the signing secret lives in the encryptedsigning_secretfield and the custom headers in the encryptedheaders_secretfield.」signing_secretフィールドに、カスタムヘッダーは暗号化されたheaders_secretフィールドに保存されます。」signing_secrety las cabeceras personalizadas, en el campo cifradoheaders_secret.」signing_secret字段中,自定义请求头保存在加密的headers_secret字段中。」plugin-webhooks·sys_webhook.description: said an HTTP connector plugin executes the webhook.69f1dfd5creplaced that executor in the source with the webhook auto-enqueuer and the shared HTTP outbox (service-messaging), and no HTTP connector plugin exists in the tree (packages/plugins,packages/services). The leaf also omitted thedefineStack({ webhooks })door, which is additive.en:Outbound HTTP webhook subscription. Declared in code via defineStack({ webhooks }) / defineWebhook() (materialized into rows on boot) or authored directly in the Studio editor; dispatched by the webhook auto-enqueuer onto the shared HTTP outbox.plugin-audit·sys_activity.fields.environment_id.label: the v5.0 rename project to environment ships no alias.en:Environmentplugin-audit·sys_activity.fields.environment_id.help: same rename; found by the term scan and the renamed-key check, invisible to the since-last-edit instrument.en:Environment context (multi-environment deployments)plugin-audit·sys_audit_log.fields.user_id.label:7fe7e85d6gavesys_audit_logits ownactorprincipal field (label Actor; ADR-0014 D2) and relabelled the strictsys_userlookupuser_idfrom Actor to User, because a service-token action leavesuser_idnull and records the principal inactor. The leaves still calleduser_idthe actor: es-ES showed two fields both labelled 「Actor」, and ja-JP (操作者 beside 実行者) and zh-CN (执行人 beside 操作者) showed two synonyms.en:Userplugin-security·sys_position.description: the ADR-0090 P1 commit (6d83431cf) changedenfrom "Role definitions for RBAC access control" to capability distribution; the translations were find-replaced role to position and kept "for RBAC access control" (ADR-0090: capability =permission_set, distribution =position, and the word role is retired from UI copy).en:Position definitions for capability distribution (ADR-0090)The two closest calls on this side are
sys_webhook.descriptionandsys_audit_log.fields.user_id.label; the reasons are above.Stale but not contradicting (listed, left as written)
endidsys_webhook.fields.triggers.helpenbundle began tracking its source (8af76aebf). The leaf's longer "bulk_update / bulk_delete fire on predicate writes and deliver a count" is still true (the field's own source comment says the same).sys_audit_log.fields.user_id.helpsys_approval_request.fields.status.options.*(5),sys_approval_action.fields.action.options.*(12)pendingtoPending,request_infotoRequest Info,ooo_substitutetoOut-of-Office Substitution). Every leaf already carried the meaning. Triage: not a contradiction.sys_position.fields.managed_by.help,sys_capability.fields.managed_by.help,sys_permission_set.fields.managed_by.helpsys_capability.descriptionsys_position._actions.deactivate_position.confirmTextsys_permission_set.fields.name.helpDispatch hypotheses, measured
6bff748bbare 3/3/3, 3/3/3, 17/17/16 and 7/5/7.sys_activity.fields.environment_id.label) is a positive in all three locales (es-ES 「Proyecto」 too).node scripts/check-i18n-bundles.mjs --write --filter=plugin-NAMEfor webhooks, audit, security and approvals printedregeneratedfor each; the files were rewritten on disk andgit status --porcelainstayed empty, so the tooling owes no companion orenchange. Control (a commit-first ablation throughscripts/ablation-replace.mjs): with ja-JPsys_webhook.fields.definition_json.helpset to theenstring,check-i18n-bundles --filter=plugin-webhooksgoes red,plugins/plugin-webhooks DRIFTED (1); the tool restored it (blob88583c2ea5f9== HEAD,git diff HEADempty).dist(built at5444bb130).plugin-webhooks: the built chunkdist/translations-KQ72WOMS.js, the module the plugin'skernel:readyhook imports, was imported and its served value (afterwithSourceFallback) read at each path: 6 of 6 equal the HEAD source and differ from the base, and 3 unchanged-leaf controls equal.plugin-auditandplugin-securityinline their bundles: each locale's object literal was cut out ofdist/index.mjsanddist/index.jsand read by path: 24 of 24 rows equal the HEAD source and differ from the base, and 12 controls equal. A plain byte grep was not a usable reading here: esbuild escapes non-ASCII text, and the old short labels are shared by other fields.Verification (all at
5444bb130, after mergingorigin/mainat9a4b2bb38)The merge brought PR #20658, a comment-only edit that includes
plugin-security/src/translations/index.ts, a file this PR does not edit.turbo run build --filter=@objectstack/cli... --filter=@objectstack/plugin-webhooks... --filter=@objectstack/plugin-audit... --filter=@objectstack/plugin-security..., 59/59 tasks,VERDICT command-exit 0.pnpm --filtertest, for the three changed plugins: plugin-webhooks 13 files / 160 tests passed; plugin-audit 25 files / 363 tests passed; plugin-security 147 files / 3202 passed, 23 skipped (the translation testsbundle-ownership.test.tsandposition-rename-consistency.test.tsincluded).typecheckfor the three: exit 0, eachcheck:test-typecheck: OK.node scripts/pm/dispatch-gates.mjs --commands(no paths) derived 57 commands against the real diff; the dispatch-time list had 56, and the one added ispnpm check:logger-receiver-detach. All 57 exit 0.--ranprinted "57 derived famil(ies) accounted for — 57 run, 0 NOT-MEASURED".pnpm check:dual-build-cjs-loadsfirst refused with exit 3 (nine packages outside this diff had nodist/). Those were built and the gate re-ran: exit 0.node scripts/check-changeset-fixed.mjs,pnpm check:authz-resolver,pnpm check:error-code-casing,pnpm check:filter-alias-parity, all exit 0.check:i18n"OK (9 package(s) — all bundles in sync, no undeclared authoring keys)";check:i18n-stale-fill"OK (10 bundle set(s) — no new stale fills, 0 baselined)";check:nul-bytesOK.eslint --no-inline-config --format jsonread 9 files, 0 errors, 0 warnings.ESLint#isPathIgnoredanswersfalsefor all nine, read from the repo's own config. The config enables no type-aware linting (noparserOptions.project;eslint.config.mjsstates it at lines 327-328), so a change to string values in these files cannot move the verdict on any other file. The fullpnpm lintis left to CI.Acceptance notes
position-rename-consistency.test.tsmatches the retired term as\brole?s?\b/\brol(es)?\b/ 角色 / ロール. None of those matches the acronym RBAC, which is howsys_position.descriptionkept "for RBAC access control" in all three locales under a green ADR-0090 guard. This PR fixes the values; the test's blind spot remains. It is outside this card's file surface and is not changed here.project_idtoenvironment_id). The renamed-key check above covers that shape. i18n(platform-objects): authored ja-JP / es-ES / zh-CN metadata-forms leaves contradict their moved en source (the metadata-forms half of #20653) #20666 runs the same instrument on the metadata-forms bundles and may want the same check.Generated by Claude Code