Repository navigation
Commit de2b7e0
fix(service-automation): a flow write node's refusal is worded in the run's locale, as the record doors word it (#25929)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XFgtJRPVDMB5VczqNK3rf7s)_
Fixes #25809
Clause-②: no
Metadata-protocol: none
## What changes
A refusal a flow's write node meets is now worded in the language of the
person who started the run, as their own record save is.
The run already carries that language (`AutomationContext.locale`,
seeded by the `/actions` door at
`packages/runtime/src/action-execution.ts` and by the trigger door).
`resolveRunDataContext`
(`packages/services/service-automation/src/runtime-identity.ts`), the
one place that maps a run to the ObjectQL `context` its data nodes pass,
dropped it. It now carries `locale` on both `runAs` arms
(`RunDataContext.locale`). The engine's existing translation hook
(`ObjectQL#validationMessageContext`, bridged to the i18n service) does
the rest: an authored rule's `objects.OBJECT._validations.RULE.message`,
the built-in catalog, a `DELETE_RESTRICTED` sentence's labels. The
translated text reaches the caller through the path the inner-refusal
ruling added: the crud node's catch arm puts it on
`innerRefusal.message`, and the four doors emit it in the `FLOW_FAILED`
details.
No second translation path: nothing in service-automation translates
anything. A run no person started (record-change, schedule, webhook) has
no locale and keeps the authored text, as before.
## Measurement — red first
`packages/services/service-automation/src/flow-write-refusal-locale.integration.test.ts`:
a real kernel (ObjectQL + better-sqlite3), an object with an authored
`script` rule, and a zh-CN bundle on the engine's i18n service. Each
case first asserts the record door's own answer for the same write
(`ql.insert` with `locale: 'zh-CN'` answers `金额不能为负数。`).
- At `cb0a2fcf4` (origin/main) plus the test alone: **6 failed / 2
passed (8)**. `create_record` and `update_record` returned
`innerRefusal.message: "Amount must not be negative."` under both
`runAs` arms; `delete_record` returned `This Account is still referenced
by 1 Deal record(s) through “Account”. Delete or reassign them first.`
(English labels). The two passing cases are the no-locale controls.
- With the fix (`447414c4`): **8 passed (8)**.
## Every flow write node, with a verdict
| Node | Verdict |
|:--|:--|
| `create_record` | Gap (measured red: authored rule message in
English). Fixed. |
| `update_record` | Same gap (measured red). Fixed. |
| `delete_record` | Same gap on `DELETE_RESTRICTED`: the sentence named
objects by their English labels (measured red). Fixed. |
| `get_record` | Read. ObjectQL reads `context.locale` only for refusal
wording (validation messages, delete restriction), so a read has nothing
to word. Carries the locale; no behaviour change. |
| lookup expander (`plugin.ts`, `setRecordExpander`) | Read through
`resolveRunDataContext`; same as `get_record`. |
| `approval` / `approval_revise` (`plugin-approvals`) | Not a
flow-generic data write: they write through the approval service under
its own context, not through `resolveRunDataContext`. Out of this card's
file surface; not measured. |
| `connector_action`, `http_*`, `notify`, `screen` | No ObjectQL write.
|
All four data nodes take their context from `resolveRunDataContext`
(`DATA_NODE_TYPES`), so the fix is one place and cannot drift between
node types.
## Tests
- New: `flow-write-refusal-locale.integration.test.ts` — 8 cases: create
/ update / delete under `runAs: 'user'` and `'system'` in zh-CN, plus
the no-locale control per arm.
- Ablation, one per arm, with the fix committed (`447414c4`), via
`scripts/ablation-replace.mjs` (anchor 1 → 0, blob moved; restore: blob
== HEAD `d5d8492ba58e`, `git diff HEAD` empty). The subject is imported
from source (`./plugin.js`), so no `dist/` leg applies.
- delete `...(locale ? { locale } : {}),` (system arm): 3 failed / 5
passed — the three `runAs: 'system'` zh-CN cases.
- delete `if (locale) out.locale = locale;` (user arm): 3 failed / 5
passed — the three `runAs: 'user'` zh-CN cases.
- `pnpm --filter @objectstack/service-automation test`: Test Files 203
passed (203), Tests 2667 passed (2667).
- `pnpm --filter @objectstack/service-automation typecheck`: exit 0
(incl. `check:test-typecheck`).
- `eslint --no-inline-config packages/services/service-automation`: exit
0.
## Gates (local subset)
`node scripts/pm/dispatch-gates.mjs --commands` at `ba44680f4` derives
68 families for this diff (3 paths). All 68 ran with exit 0; `--ran`
reconciliation: `68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN` (exit 0).
- Two families answered exit 3 on the first pass for shallow-clone
prerequisites, the known pair: `check-plugin-teardown-shape --self-test`
(re-run after fetching its fixture commit) and
`check:dual-build-cjs-loads` (re-run after a full `pnpm build`, lock
VERDICT exit 0). Both re-ran to exit 0.
- The first 56 families ran at `447414c4` (the code at its final bytes,
before the changeset commit); the 12 the changeset added to the
derivation ran at `ba44680f4`.
- Every family CI runs is CI's verdict on this PR's check-runs; none is
re-run here. `test:repo` and the repo-wide `pnpm lint` are CI's.
## Acceptance notes
- `AutomationContext.locale`'s docblock
(`packages/spec/src/contracts/automation-service.ts`) lists its readers
as the refusing `end` node and the `screen` executor; the data nodes are
now a third reader. Not edited here: `packages/spec/**` is outside this
claim's file surface. Carrier: none.
- Hooks fired by a flow's write now see the starter's `locale` on their
session, as the same user's record-door write already gives them.
Presentation only: no security middleware keys on it.
- The `/actions` door's half (`ec.locale` → `AutomationContext.locale`)
and the details carriage (`flowFailureDetails`) were already on main and
are unchanged; this PR pins the engine leg, where the gap was.
---
_Generated by [Claude
Code](https://claude.ai/code/session_011W59GGh1a3YnDbRLcBJYxe)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_011W59GGh1a3YnDbRLcBJYxe
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent dd5e355 commit de2b7e0
3 files changed
Lines changed: 231 additions & 2 deletions
File tree
- .changeset
- packages/services/service-automation/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
144 | 144 | | |
145 | 145 | | |
146 | 146 | | |
147 | | - | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
148 | 162 | | |
149 | 163 | | |
150 | 164 | | |
| |||
262 | 276 | | |
263 | 277 | | |
264 | 278 | | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
265 | 283 | | |
266 | 284 | | |
267 | 285 | | |
| |||
271 | 289 | | |
272 | 290 | | |
273 | 291 | | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
274 | 295 | | |
275 | 296 | | |
276 | 297 | | |
| |||
320 | 341 | | |
321 | 342 | | |
322 | 343 | | |
| 344 | + | |
323 | 345 | | |
324 | 346 | | |
325 | 347 | | |
| |||
340 | 362 | | |
341 | 363 | | |
342 | 364 | | |
343 | | - | |
| 365 | + | |
344 | 366 | | |
345 | 367 | | |
346 | 368 | | |
| |||
351 | 373 | | |
352 | 374 | | |
353 | 375 | | |
| 376 | + | |
354 | 377 | | |
355 | 378 | | |
356 | 379 | | |
| |||
0 commit comments