Skip to content

Commit e2a4b6a

Browse files
fix(spec): liveness ledger rows cite the seam modules that now hold the moved engine bodies (#25796)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6Jao9NFKhq5MmwptNuMX9ewhqz)_ Fixes #25790 Part of #25677 (cutover prep I: `main` must read green before the cutover PR). Clause-②: no Metadata-protocol: none ## What was red, measured on `main` 5612dd9 `check-liveness.test.ts` fails 23 tests, but they are **23 tests with one cause, not 23 rows**. Every one of them runs the gate over a verbatim copy of the shipped ledgers and asserts exit 0; each fails `expected 1 to be +0` on the same single finding: ``` key-mention anchoring: 762 (entry, cited file) pair(s) asked, 760 anchored, 1 exempt, 1 UNANCHORED. ✗ translation/flows.screens → packages/services/service-automation/src/engine.ts ``` `engine.ts` no longer names `screens` at all. Its one mention was an unrelated comment ("states two screens down") inside `resumeInternal`'s body, which 0189524 moved to `run-resume.ts:1462`. So the row was anchored by accident before, and the move exposed a citation whose cited body had already left the file. The 23 `engine.ts#` lines the card counts are 22 citation rows plus one historical `note`. All 22 still resolve and anchor except the `screens` row, but 7 of them cite a method whose body now lives in a seam module, so the code their prose quotes is no longer in the cited body. This PR re-points six of them; the seventh (row 21) keeps its citation for the reason in the table. The other 15 cite bodies still in `engine.ts` and are unchanged. ## The rows `SA` = `packages/services/service-automation/src/`. Bodies located by reading each cited method at 5612dd9 (a wrapper says `The body lives in ./X.ts`). | # | Ledger row | Old citation | New citation | Measured | |---|---|---|---|---| | 1–5 | connector `name`, `label`, `type`, `description`, `icon` | `SA/engine.ts#getConnectorDescriptors` | unchanged | body still in `engine.ts` (`name: def.name` …) | | 6 | connector `actions.children.key` | `SA/engine.ts#registerConnector` | `SA/plugin-extension-api.ts#registerConnector` | the quoted `if (typeof handlers[action.key] !== 'function')` throw is at `plugin-extension-api.ts:349`. Prose also named `getConnectorActionHandler`, which does not exist; the method is `resolveConnectorAction` (`engine.ts:3820`). `resolveConnectorActionEffect` / `getConnectorDescriptors` pointers unchanged (bodies in `engine.ts`) | | 7–10 | connector `actions.children.label`, `description`, `inputSchema`, `outputSchema` | `SA/engine.ts#getConnectorDescriptors` | unchanged | body in `engine.ts` | | 11 | connector `actions.children.effect` | `SA/engine.ts#resolveConnectorActionEffect`, `#getConnectorDescriptors` | unchanged | the quoted `find((a) => a.key === actionId)?.effect` is at `engine.ts:3838` | | 12 | flow `successMessage` | `SA/engine.ts#executeWithoutRetry`, `SA/engine.ts#resumeInternal` | `SA/dag-traversal.ts#executeWithoutRetry`, `SA/run-resume.ts#resumeInternal` | quoted `successMessage: flow.successMessage` at `dag-traversal.ts:1278`; resumed run at `run-resume.ts:1552` | | 13 | flow `errorMessage` | `SA/engine.ts#retryExecution`, `SA/engine.ts#resumeInternal` | `retryExecution` unchanged, `SA/run-resume.ts#resumeInternal` | `retryExecution`'s body is in `engine.ts` (`errorMessage: flowErrorMessage`); quoted `errorMessage: flow.errorMessage` at `run-resume.ts:1809` | | 14 | flow `status` | `SA/engine.ts#registerFlow`, `#isFlowEnabled` | unchanged | both bodies in `engine.ts`; quote verbatim at `:4171` | | 15 | flow `type` | `SA/engine.ts#resolveTriggerBinding` | `SA/plugin-extension-api.ts#deriveTriggerBinding`, `packages/spec/src/automation/flow-trigger-kind.ts#resolveFlowTriggerKind` | `resolveTriggerBinding` is a lookup that hands the flow to `deriveTriggerBinding`, whose body moved. The quoted `flow.type === 'schedule'` exists nowhere now; the read is `f.type === 'schedule'` / `f.type === 'api'` in the shared resolver. Prose rewritten to match | | 16 | job `pull.children.mapping` | `SA/engine.ts#AutomationEngine` (`pullConnectorSource`) | unchanged | body in `engine.ts:4554` | | 17–19 | translation `flows.label`, `flows.successMessage`, `flows.errorMessage` (`producer`) | `SA/engine.ts#AutomationEngine` | unchanged | `execute()` in `engine.ts` copies `flow.label` / `flow.successMessage` / `flow.errorMessage` (`:5786`, `:5782`, `:6054`); `producer` is not key-mention-scanned | | 20 | translation `flows.screens` **(the red row)** | `SA/engine.ts#renderFlowTextSlot` | `packages/spec/src/system/i18n-resolver.ts#flowScreenCopyKey`; the renderer is named in the `screen-nodes.ts` clause, which calls it | the renderer's body (`node-text-rendering.ts#renderFlowTextSlot`) never names `screens` either (it reads `slot.key`), so citing it would stay red. The gate's own remedy is to cite the file that names the key: `flowScreenCopyKey` spells the address. Not exempted | | 21 | translation `flows.refusals` | `SA/engine.ts#renderRefusalMessage` | citation unchanged; prose points at the drilled child | `node-text-rendering.ts` never names `refusals` (it is spelled by `flowRefusalMessageKey`), so re-pointing would red the row. `engine.ts` still names the key (`setI18nServiceSource` docblock, `:4527`) and the method is still the group's entry point | | 22 | translation `flows.refusals.children.message` | `SA/engine.ts#translatedFlowTemplate`, `SA/engine.ts#renderRefusalMessage` | `SA/node-text-rendering.ts#translatedFlowTemplate`, `SA/node-text-rendering.ts#renderRefusalMessage` | both bodies moved; the file names `message` | | note | connector retired `triggers` row, `note` | names `SA/engine.ts#registerConnector` | unchanged | a dated record of the retirement, not a citation; the gate does not read `note` | 7 rows edited (6, 12, 13, 15, 20, 21, 22), 0 rows deleted, no status changed. `undrilled-containers.baseline.json` and `key-mention.baseline.json` are untouched, and so are `engine.ts` and every seam module. ## Verification (head be47c5b) - `pnpm --filter @objectstack/spec check:liveness`: exit 1 before, exit 0 after. `evidence paths: 766 … 766 resolved` (was 762), `symbol anchors: 992 … 992 naming a symbol the cited file contains` (was 991), `key-mention anchoring: 766 … 765 anchored, 1 exempt` (was 760 anchored, 1 UNANCHORED). - `vitest run scripts/liveness/` in `packages/spec`: before 23 failed / 48 passed in `check-liveness.test.ts`; after 12/12 files, 350/350 tests. - Derived gates (`node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`): 65 families, all exit 0 on be47c5b after a full workspace build; `--ran` reconciles 65 run, 0 NOT-MEASURED, 0 UNRUN. - Changeset: `@objectstack/spec` patch, because `liveness` is in the package's `files` and ships. ## The 8/8 red on the same `main` run Run 40187126172's `Test Core (8/8)` annotation is `plugin-pinyin-search … pnpm run test exited (1)`. The job log body could not be read from this container, so which test failed is NOT MEASURED. The package passes locally at this head, 4/4 files and 39/39 tests. That matches cutover bill item 26 (the `boot-reconcile.integration.test.ts` 5 s timing class); it is reported here, not fixed. ## Acceptance notes - The card's mechanism assumption "23 rows ↔ 23 failures" does not hold: 23 tests fail on one row. The 22 citation rows were audited anyway, as the card asks. - The card's `pnpm check:liveness` (root) is not a root script; the command is `pnpm --filter @objectstack/spec check:liveness`. - Not audited here: 17 `flow.json` rows cite bare `SA/engine.ts` with no symbol. They anchor at this head. Carrier: none. --- _Generated by [Claude Code](https://claude.ai/code/session_011mK1nJznx2PNFgCuCwwCjM)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5f5f0b4 commit e2a4b6a

4 files changed

Lines changed: 19 additions & 7 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): the shipped liveness ledger cites the automation seam modules that now hold the moved engine bodies
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Seven rows in `liveness/connector.json`, `liveness/flow.json` and `liveness/translation.json` cited `AutomationEngine` methods in `service-automation/src/engine.ts` whose bodies now live in the service's seam modules, leaving one-hop wrappers behind. They now cite the body that holds the quoted code: `plugin-extension-api.ts#registerConnector`, `dag-traversal.ts#executeWithoutRetry`, `run-resume.ts#resumeInternal`, `plugin-extension-api.ts#deriveTriggerBinding` with the shared `flow-trigger-kind.ts#resolveFlowTriggerKind` (which is where `flow.type` is read now), and `node-text-rendering.ts#translatedFlowTemplate` / `#renderRefusalMessage`. The `translation` `flows.screens` row cites `i18n-resolver.ts#flowScreenCopyKey`, the file that spells its address, because the generic renderer it hands each slot to never names the key. That row is what turned `check:liveness` red.
11+
12+
What to do: nothing. No status, key or verdict changes; every row keeps its classification. Only the evidence pointers and the prose around them change.

‎packages/spec/liveness/connector.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
"status": "live",
7373
"verifiedAt": "2026-09-17",
7474
"evidenceScope": "cross-repo",
75-
"evidence": "packages/services/service-automation/src/engine.ts#registerConnector — `if (typeof handlers[action.key] !== 'function') throw` refuses a def whose declared action has no handler, so the key is the contract between declaration and implementation; packages/services/service-automation/src/engine.ts#resolveConnectorActionEffect looks the action up by `a.key === actionId`, and `getConnectorActionHandler` dispatches `handlers[actionId]`; packages/services/service-automation/src/engine.ts#getConnectorDescriptors publishes it; objectui @dda8f3815 packages/app-shell/src/views/metadata-admin/inspectors/FlowReferenceField.tsx (`connectorActionsToOptions`) and .../connector-input-fields.ts#connectorActionInputSchema both resolve an action by `a.key === actionKey`.",
75+
"evidence": "packages/services/service-automation/src/plugin-extension-api.ts#registerConnector — `if (typeof handlers[action.key] !== 'function') throw` refuses a def whose declared action has no handler, so the key is the contract between declaration and implementation; packages/services/service-automation/src/engine.ts#resolveConnectorActionEffect looks the action up by `a.key === actionId`, and `resolveConnectorAction` returns `handlers[actionId]` for the node to dispatch; packages/services/service-automation/src/engine.ts#getConnectorDescriptors publishes it; objectui @dda8f3815 packages/app-shell/src/views/metadata-admin/inspectors/FlowReferenceField.tsx (`connectorActionsToOptions`) and .../connector-input-fields.ts#connectorActionInputSchema both resolve an action by `a.key === actionKey`.",
7676
"producer": "packages/connectors/connector-mcp/src/mcp-connector.ts — `actions: selected.map((tool) => ({ key: tool.name, … }))` derives the action set from the upstream `tools/list`; packages/connectors/connector-openapi/src/openapi-connector.ts derives one action per OpenAPI operation. ⚠️ THE METADATA AUTHOR IS NOT A PRODUCER HERE: packages/spec/src/integration/connector.zod.ts#DeclarativeConnectorEntrySchema REFUSES `actions` on a provider-bound instance (ADR-0097 §5 — the provider derives them), and on a descriptor (no `provider`) the entry never reaches the registry at all.",
7777
"note": "Live on the PLUGIN/provider door, inert-and-audited on the authoring door — the two-door fact in the file note, at its sharpest. What an authored `actions[]` on a descriptor DOES do is trigger a boot warning: packages/services/service-automation/src/plugin.ts#findInertDeclaredConnectors returns every declared connector that has at least one action, no runtime registration and no `enabled: false` opt-out, and `auditDeclaredConnectors` warns once per boot naming them and the remedy. That warning is the #2612 contract made audible; it is not dispatch."
7878
},

‎packages/spec/liveness/flow.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
"successMessage": {
1010
"status": "live",
1111
"verifiedAt": "2026-08-28",
12-
"evidence": "packages/services/service-automation/src/engine.ts#executeWithoutRetry (`successMessage: flow.successMessage` on the terminal success envelope — deliberately not a per-attempt value); packages/services/service-automation/src/engine.ts#resumeInternal (the resumed run returns the same authored string)",
12+
"evidence": "packages/services/service-automation/src/dag-traversal.ts#executeWithoutRetry (`successMessage: flow.successMessage` on the terminal success envelope — deliberately not a per-attempt value); packages/services/service-automation/src/run-resume.ts#resumeInternal (the resumed run returns the same authored string)",
1313
"note": "2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:1292` had rotted onto a docblock sentence about `'runtime'` package provenance, ~2,450 lines above the readers. The wrapping `execute` path returns the same field and is named here as prose rather than anchored, because `execute` is too common a word in this file for an anchor to falsify anything. Re-closed by hand against 8cb96ec41."
1414
},
1515
"errorMessage": {
1616
"status": "live",
1717
"verifiedAt": "2026-08-28",
18-
"evidence": "packages/services/service-automation/src/engine.ts#retryExecution (carries the authored text onto the last attempt's failure); packages/services/service-automation/src/engine.ts#resumeInternal (`errorMessage: flow.errorMessage` on the resumed run's failure envelope)",
18+
"evidence": "packages/services/service-automation/src/engine.ts#retryExecution (carries the authored text onto the last attempt's failure); packages/services/service-automation/src/run-resume.ts#resumeInternal (`errorMessage: flow.errorMessage` on the resumed run's failure envelope)",
1919
"note": "2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:1348` had rotted onto a docblock heading (\"⛔ This is NOT the retired `flowEnabled` map under a new name\"), ~2,480 lines above the readers. As with `successMessage`, the top-level `execute` path reads the same field and is left as prose for the same reason. Re-closed by hand against 8cb96ec41."
2020
},
2121
"label": {
@@ -56,7 +56,7 @@
5656
"type": {
5757
"status": "live",
5858
"verifiedAt": "2026-08-28",
59-
"evidence": "packages/services/service-automation/src/engine.ts#resolveTriggerBinding (`flow.type === 'schedule'` and `flow.type === 'api'` decide which trigger the flow is bound to)",
59+
"evidence": "packages/services/service-automation/src/plugin-extension-api.ts#deriveTriggerBinding (the binding behind `AutomationEngine.resolveTriggerBinding`: the kind `resolveFlowTriggerKind` answers decides which trigger the flow is bound to); packages/spec/src/automation/flow-trigger-kind.ts#resolveFlowTriggerKind (`f.type === 'schedule'` and `f.type === 'api'`, beside the start node's own config, answer the `schedule` and `api` kinds)",
6060
"note": "flow.type==='schedule'/'api' drives trigger dispatch. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:637` had rotted onto a docblock about the unknown-node-type audit being structured rather than logged, ~1,430 lines above the dispatch. Re-closed by hand against 8cb96ec41."
6161
},
6262
"variables": {

‎packages/spec/liveness/translation.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,21 +146,21 @@
146146
"status": "live",
147147
"verifiedAt": "2026-10-10",
148148
"evidenceScope": "cross-repo",
149-
"evidence": "packages/services/service-automation/src/builtin/screen-nodes.ts#registerScreenNodes (the `screen` executor asks for `flows.<flow>.screens.<node_id>.title` and, where the screen authors body text, `.description`, spelled by `flowScreenCopyKey`, before it renders either template); packages/services/service-automation/src/engine.ts#renderFlowTextSlot (picks the translated template through the `i18n` service in the run's `AutomationContext.locale`, negotiated by `resolveBundleLocale`, and renders it through `renderTextSlot` in place of the authored one; a miss echoes the key and the authored template renders); objectui @31971ff1e: packages/app-shell/src/views/FlowRunner.tsx#localizeScreen (the heading through `resolveFlowScreenTitle`, and each field's copy from `bundle[language]?.flows?.[flowName]?.screens?.[screen.nodeId]?.fields`); objectui @31971ff1e: packages/app-shell/src/views/FlowRunner.tsx#overlayFieldCopy (walks the spec's imported `FLOW_SCREEN_FIELD_COPY_KEYS`, which are `label` / `placeholder` / `inlineHelpText`, and overlays each key on its own, falling back to the authored string); objectui @31971ff1e: packages/app-shell/src/views/FlowRunner.tsx#activeFlowsBundle (reads the active language's `flows` group out of the i18next `translation` resource tree); objectui @31971ff1e: packages/app-shell/src/views/FlowRunner.tsx#FlowRunner (draws `shown.title` as the dialog title and hands the localized screen to `ScreenView`); objectui @31971ff1e: packages/app-shell/src/views/ScreenView.tsx#ScreenView (draws each field's `label`, hands `placeholder` to the control, and draws `inlineHelpText` under the control, which names it in `aria-describedby`)",
149+
"evidence": "packages/services/service-automation/src/builtin/screen-nodes.ts#registerScreenNodes (the `screen` executor asks for `flows.<flow>.screens.<node_id>.title` and, where the screen authors body text, `.description`, spelled by `flowScreenCopyKey`, before it renders either template, and hands each slot to `AutomationEngine.renderFlowTextSlot`, which picks the translated template through the `i18n` service in the run's `AutomationContext.locale`, negotiated by `resolveBundleLocale`, and renders it through `renderTextSlot` in place of the authored one; a miss echoes the key and the authored template renders); packages/spec/src/system/i18n-resolver.ts#flowScreenCopyKey (spells the address the executor asks for, `flows.<flow>.screens.<node_id>.<key>`); objectui @31971ff1e: packages/app-shell/src/views/FlowRunner.tsx#localizeScreen (the heading through `resolveFlowScreenTitle`, and each field's copy from `bundle[language]?.flows?.[flowName]?.screens?.[screen.nodeId]?.fields`); objectui @31971ff1e: packages/app-shell/src/views/FlowRunner.tsx#overlayFieldCopy (walks the spec's imported `FLOW_SCREEN_FIELD_COPY_KEYS`, which are `label` / `placeholder` / `inlineHelpText`, and overlays each key on its own, falling back to the authored string); objectui @31971ff1e: packages/app-shell/src/views/FlowRunner.tsx#activeFlowsBundle (reads the active language's `flows` group out of the i18next `translation` resource tree); objectui @31971ff1e: packages/app-shell/src/views/FlowRunner.tsx#FlowRunner (draws `shown.title` as the dialog title and hands the localized screen to `ScreenView`); objectui @31971ff1e: packages/app-shell/src/views/ScreenView.tsx#ScreenView (draws each field's `label`, hands `placeholder` to the control, and draws `inlineHelpText` under the control, which names it in `aria-describedby`)",
150150
"producer": "packages/runtime/src/app-plugin.ts#loadTranslations (hands each bundle's locale data to the i18n service whole, `flows` included); packages/runtime/src/domains/i18n.ts#handleI18nRequest (the translations route answers `getTranslations(locale)` with no group filter); objectui @31971ff1e: apps/console/src/loadLanguage.ts#loadLanguage (fetches the locale's translations and runs `transformSpecTranslations`); objectui @31971ff1e: packages/i18n/src/utils/spec-translations.ts#transformSpecTranslations (forwards every group it does not flatten, `flows` among them, verbatim under the `app` namespace); objectui @31971ff1e: packages/i18n/src/provider.tsx#I18nProvider (adds the loaded payload to the `translation` resource bundle that `activeFlowsBundle` reads)",
151151
"note": "FLIPPED planned → live 2026-09-27 (#20296). It was PLANNED with its container under the #7646 contract-first split: the spec declared the vocabulary and the screen-flow runner half was a downstream objectui card. That half has landed client-side, the side #11287 picked (objectui#5920, FlowRunner's `localizeScreen`), and the `.objectui-sha` pin f8a9d0fb carries it, so every objectui pointer above was read at the pin this repo builds against. Each read is unchanged at objectui main 256b4c9e. Per-screen heading + per-field copy, keyed by `FlowNode.id` / `ScreenFieldConfig.name`, the identifiers the client already holds as `ScreenSpec.nodeId` / `ScreenFieldSpec.name`. Each key falls back to the authored string on its own. The screen `description` and the runner chrome stay untranslated here by ruling. Deeper conventions (`screens.<id>.title`, `screens.<id>.fields.<name>.{label,placeholder,inlineHelpText}`) are governed by the runner and the spec's `FLOW_SCREEN_COPY_KEYS` / `FLOW_SCREEN_FIELD_COPY_KEYS`, not by ledger rows: the one-drill-level boundary this ledger's type note states. MOUNT CHAIN, closed by hand at the pin: `FlowRunner` is mounted by `useConsoleActionRuntime`'s dialogs (the console root `ConsoleShell` renders them, and ObjectView / DeclaredActionsBar each run their own runtime), by `RecordDetailView`, and by the console's `developer/flow-runs` route (`FlowRunsPage`). A `type: 'flow'` action whose run pauses at a screen node opens it with `{ flowName, runId, screen }`. The bundle reaches it through the console root's `I18nProvider loadLanguage` (apps/console main.tsx). ⚠️ NOT flipped with it: the container `flows` keeps `planned` + `authorWarn` for its `label` child, which nothing reads yet (#20318), and its `authorHint` now says that `screens` is read and the flow label is not. That bit is group-level and has two readers: @objectstack/lint's warn map, and the CLI i18n coverage gate (`authorWarnedTranslationGroups` in packages/cli i18n-extract.ts), which holds back the whole `flows.*` demand while it is set. Dropping it before the label has a reader would switch on demand for `flows.<flow>.label` too, so it drops when #20318 lands (seat ruling on #20296). RE-VERIFIED 2026-10-02 (#17306) at the `.objectui-sha` pin 31971ff1e, which carries objectui 81778b955 (REST compare 81778b955...31971ff1e answers `ahead`, `behind_by 0`): the per-field face gained `inlineHelpText` beside `label` and `placeholder`, the screen field's own help-text key. Every objectui pointer in `evidence` and `producer` was re-read at that pin and repinned from f8a9d0fb. `overlayFieldCopy` imports the spec's key list rather than retyping it, so the new key needed no objectui edit, and `ScreenView` draws the help text under the control (objectui#9248). The status is unchanged: this row was already `live`. 2026-10-05: the `⚠️ NOT flipped with it` clause above is superseded. The `label` child is `live` now (objectui's FlowRunner reads it at the pin 0abd4f9f8), so the container flipped to `live` and dropped `authorWarn` and `authorHint`; see the container note. 2026-10-09 (#22507): the per-field face gained `options.<value>` — a screen field's option labels, keyed by each option's value read as text (`flowScreenFieldOptionKey`) and resolved by `resolveFlowScreenFieldOptions`. It is NOT in `FLOW_SCREEN_FIELD_COPY_KEYS` (a map over `options[]`, not one string), so `overlayFieldCopy` does not reach it, and no objectui code reads it at main 20c6d351a: the option labels are declared ahead of their reader, the downstream runner change. The row stays `live` for the heading and the three string keys, which are unchanged; the one-drill-level boundary keeps the option map off a row of its own. 2026-10-10 (#22507, maintainer ruling A): the screen face gained `description`, and the screen's heading and body text moved to the ENGINE. Both are `{{ }}` templates the executor renders per run, so the `screen` executor picks each translated template in the run's locale through the one `i18n` channel the refusal message is read through, before it fills the holes; the served `ScreenSpec` arrives translated, and the authored template renders when the bundle has none. The body text is translated only where the screen authors one. objectui's `localizeScreen` still overlays the heading through `resolveFlowScreenTitle` at the `.objectui-sha` pin 20c6d351a; that overlay is retired by the ruling's consumer half (filed into objectui#12075). Until it lands the runner replaces a served heading with the translated template, which draws a hole literally exactly where it did before this change, so the window adds no regression. The per-field copy stays client-side, read by `overlayFieldCopy` as cited. The row stays `live`: the engine reads both screen keys in-repo, and objectui reads the field keys. objectui's `FlowRunner` overlay of `flows.<flow>.screens` on the screen it draws was read at objectui @f8a9d0fb (the citation moved out of packages/spec/src/system/i18n-resolver.ts, #23434)."
152152
},
153153
"refusals": {
154154
"status": "live",
155155
"verifiedAt": "2026-10-09",
156156
"evidenceScope": "in-repo",
157-
"evidence": "packages/services/service-automation/src/engine.ts#renderRefusalMessage (the one reader of the group: each `refusals.<node_id>` entry is read for its `message`, the drilled child below)",
157+
"evidence": "packages/services/service-automation/src/engine.ts#renderRefusalMessage (the one reader of the group: each `refusals.<node_id>` entry is read for its `message`, the drilled child below, whose evidence cites the body this method delegates to)",
158158
"children": {
159159
"message": {
160160
"status": "live",
161161
"verifiedAt": "2026-10-09",
162162
"evidenceScope": "in-repo",
163-
"evidence": "packages/services/service-automation/src/engine.ts#translatedFlowTemplate (reads `flows.<flow>.refusals.<node_id>.message` through the `i18n` service, spelled by `flowRefusalMessageKey` and handed over by `renderRefusalMessage`, in the run's `AutomationContext.locale` negotiated by `resolveBundleLocale`; a miss echoes the key and picks nothing); packages/services/service-automation/src/engine.ts#renderRefusalMessage (renders the picked translation in place of the authored `end` `message` through `renderTextSlot`, the one text renderer, so the refusal the run stores and returns is in the run's language; the authored template renders when nothing is picked)",
163+
"evidence": "packages/services/service-automation/src/node-text-rendering.ts#translatedFlowTemplate (reads `flows.<flow>.refusals.<node_id>.message` through the `i18n` service, spelled by `flowRefusalMessageKey` and handed over by `renderRefusalMessage`, in the run's `AutomationContext.locale` negotiated by `resolveBundleLocale`; a miss echoes the key and picks nothing); packages/services/service-automation/src/node-text-rendering.ts#renderRefusalMessage (renders the picked translation in place of the authored `end` `message` through `renderTextSlot`, the one text renderer, so the refusal the run stores and returns is in the run's language; the authored template renders when nothing is picked)",
164164
"producer": "packages/runtime/src/app-plugin.ts#loadTranslations (hands each bundle's locale data to the i18n service whole, `flows` included); packages/core/src/fallbacks/authored-translation-sync.ts#wireAuthoredTranslationSync (runtime-authored translation items reach the same service); packages/services/service-automation/src/plugin.ts#i18nServiceReader (the engine's lazy reader of that service); packages/runtime/src/domains/automation.ts#buildAutomationContext and packages/runtime/src/action-execution.ts#dispatchFlowAction (the trigger and action doors forward the request's already-resolved `ExecutionContext.locale` as `AutomationContext.locale`, which persists with a suspended run)"
165165
}
166166
},

0 commit comments

Comments
 (0)