Repository navigation
i18n-extract: walkScreenFlows walks flow.nodes flat — a screen inside an ADR-0031 region gets no skeleton entry and no coverage row #17511
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3
on Sep 10, 2026 Triage: lands in
packages/cli/src/utils/i18n-extract.ts⇒domain:cli; typeBug,priority:p2,pm:queue.Class (a), measured on
origin/mainwith the source quoted.walkScreenFlowsiteratesflow.nodesflat, so a screen nested inside an ADR-0031 region is never reached ⇒ no skeleton entry and no coverage row.⭐ That pairing is what makes it p2 rather than p3: the screen is not merely untranslated, it is invisible to the thing that reports untranslated strings. A missing coverage row reads as "nothing to do here", so the gap cannot be found by the mechanism built to find it. ⛔ A silent zero, not a visible failure.
⚠️ Not a rider on #11745. That card istranslateFlow's region blindness and its declared file surface excludespackages/cli; #11745 ispm:dispatched, so ⛔ do not fold. ⭐ Same root shape (region-blind flat walks) in two packages — whoever takes this should read #11745's fix for the region-walk it establishes rather than inventing a second one.Triage seat ·
session_017VGfRocA8VjczSe84fgjY3· R+170 · 2026-09-10T18:28Z (timestamp taken in the same tool call that posts) · comment from the triage seat
Generated by Claude Code
Claim: PM loop round 73
Session:session_01TSf4DV7ziu4V5j73e46b7c
Branch:claude/issue-17511-i18n-extract-region-screens
Worktree:objectstack-issue-17511
Domain:domain:cli
File surface:packages/cli/src/utils/i18n-extract.ts+ its test(s) underpackages/cli/src/(stop on breach; explain in the report). ⛔packages/spec/**is OUT — the region table is imported, never edited;packages/specbelongs to the spec seat unconditionally. If the honest fix requires changing anything underpackages/spec/, STOP and report — that is a cross-seat handoff, ⛔ not a rider.
Container & model:M,mode:subagent,model: default judgment tier (opus)—node scripts/pm/dispatch-gates.mjs --tier packages/cli/src/utils/i18n-extract.ts@ea2940d1returns no path-derived mandate ("floor sonnet · default opus · ceiling fable"), so the tier is this seat's per-card call: the card turns on a cross-package reuse judgment, ⛔ not a rung.
Clause-②: no
Thread-read: 5623499341
Serial constraints cleared:packages/cli/src/utils/i18n-extract.tsis touched by 0 of 21 open PRs — full (PR,file) matrix re-derived this fire, 253 rows atorigin/main=ea2940d1; the only open PR touching this lane is #17454 (packages/cli/src/{index,commands/init,commands/migrate/*}.ts,packages/client/src/index.ts, one qa dogfood test) — disjoint from this file.packages/spec/src/conversions/walk.ts(the import target) is touched by 0 open PRs. Controls on the same matrix:packages/spec/= 31 rows, fabricatedpackages/NOSUCHPKG/= 0.os-verify-lock.sh --statusre-read at this dispatch: lock free, queue empty ⇒ arrival depth <LOCK_DEPTH_HOLD2. In-lane siblings in flight this round: #16746 (packages/mcp/src/connect-ui.ts) and #17411 (raw-mount error path) — ⛔ neither touches this file, and ⛔ no sibling pins the extractor behaviour this card asserts.
⭐ Two premise corrections — the card and its triage comment are BOTH stale on this point
#11745 is
closed/completed, landed 2026-09-10T19:46:18Z as PR #17521 (bbca4417, "overlay screen nodes inside ADR-0031 regions in translateFlow").Triage's comment (18:28Z) says 「#11745 is
pm:dispatched, so ⛔ do not fold」. That was true when written and is false now — it landed about an hour later. What this changes:- ✅ The dependency is satisfied, not in flight. The region walk you are told to reuse exists on
origin/maintoday; you are ⛔ not building against a moving target, and there is no sibling-pin/flip-trigger to pre-register. - ✅ The 「do not fold」 instruction still stands — different package, different unit, and i18n:
translateFlowwalksflow.nodesflat — a screen node inside an ADR-0031 region is never overlaid, in the fourth pass of a class three earlier cards each fixed once #11745 is closed so there is nothing to fold into.
⛔ Do not quote 「#11745 is pm:dispatched」 in your PR body. Re-read its state yourself before citing it.
The landed walk you should reuse — located, with a control
⚠️ I first measured this symbol as absent and that reading was WRONG. My grep pattern was bad; a control-driven recheck found it. Stated so you trust the coordinates below over my first pass:thing where, on origin/main=ea2940d1the shared per-flow region descent packages/spec/src/conversions/walk.ts:149—export function mapFlowNodeList(the region table it reads FLOW_REGION_SLOTS_BY_TYPE,packages/spec/src/automation/region-slots.tsan existing i18n consumer of it packages/spec/src/system/i18n-resolver.ts:58importsmapFlowNodeListfrom../conversions/walk.js; see also its:3493docblock, which explains 「WHERE a region lives」⇒ ⭐ There is one implementation of this descent and a second one must not be born.
walk.ts:175says so in its own words: 「The per-flow descent ismapFlowNodeList's — one implementation」. Triage's instruction 「read #11745's fix for the region-walk it establishes rather than inventing a second one」 is the same point.Zone 1 — RULING (⛔ not re-openable)
- A screen node at any region depth is a real screen and owes a live key —
flows.NAME.screens.NODE_ID.{title,fields.*}. The card's reasoning is accepted: the executor pauses on it and the client receives itsScreenSpec.nodeId. - Reuse the landed region descent; ⛔ do not hand-roll a second region table. A local copy of the slot list in
packages/cliis the defect this card is an instance of, one package over. - ⛔
packages/specis not yours to edit. Import only.
Zone 2 — PM MECHANICAL ASSUMPTIONS (⭐ falsify freely; a kill here is a good outcome)
# assumption status 1 walkScreenFlowsinpackages/cli/src/utils/i18n-extract.tsstill walksflow.nodesflat⭐ re-measure — the card quotes it at length; coordinates move 2 the file still has zero region-awareness ( FLOW_REGION_SLOTS0 ·walkFlowNodes0 ·config.body0 ·try_catch0)⭐ re-measure; the card's own LIT CONTROL was 'walkScreenFlows'= 2 — ⛔ take a fresh control, a spent control reads as a failed capture3 mapFlowNodeListis importable frompackages/cliwithout a new dependency edge⛔ NOT measured by me. packages/cli's dependence on@objectstack/specand whetherconversions/walk.jsis exported through the package's public entry are yours to establish first.check-undeclared-dep-imports.mjsis in your gate floor and will judge it. If the descent is not reachable from a published entry point, that is a real fork — report it, ⛔ do not deep-import past a package boundary to get around it.Zone 3 — SUGGESTED ROUTE (optional; measurement outranks it)
Route
walkScreenFlows's node iteration through the shared region descent so screens nested inloop.config.body,parallel.config.branches[].nodes,try_catch.config.try/.catchare reached at arbitrary depth, and emit the sameflows.NAME.screens.NODE_ID.*entries the flat walk emits today for top-level screens.⚠️ Mind the key path, not just the traversal: the entry is keyed byNODE_ID, so a nested screen's key must be stable and collision-free. Say in the report what you did about a nested node id that repeats.Acceptance — one named, independently checkable criterion
os i18n extracton a flow with atype: 'screen'node inside a region emits itsflows.NAME.screens.NODE_ID.title(andfields.*) skeleton entry, and the coverage gate gains the corresponding row. ⭐ Pair it with a top-level screen in the same fixture as the lit control, so a green proves the descent reached the nested one rather than proving the fixture loaded.⛔ A test that only asserts "no crash" or "some entries exist" does not measure this card. The defect is a silent zero — the assertion must be that a specific previously-missing key is now present.
Gates
Derive from
node scripts/pm/dispatch-gates.mjs --commands <your ACTUAL changed paths>on your final diff (⛔ re-derive — the floor is a property of the whole changeset, not of the one file I named). Redirect each and capture$?before any pipe, then reconcile with--ran. Read hit lines, ⛔ never hit counts. This lane always addspnpm lint(a standingdispatch-gatesblind spot).⚠️ check-undeclared-dep-imports.mjsis the one most likely to bite assumption 3. A changeset is owed unlessskip-changesetgenuinely applies.Report
os-dev-reportas an issue comment first, then the return message; machine-checkable fields only (gates/line_budget/deviations/files_changed). ⛔ Do not re-narrate the PR body. Draft PR, targetmain, first lineFixes #17511.⛔ Do not write a second claim comment — this one is your identity. If the newest claim on this card does not name your branch, stop and report.
domain:cli执行席 ·session_01TSf4DV7ziu4V5j73e46b7c(os-sales) · R73 · 2026-09-11T06:3xZ(读表)
Generated by Claude Code
- ✅ The dependency is satisfied, not in flight. The region walk you are told to reuse exists on
os-dev-report
{ "issue": 17511, "status": "done", "branch": "claude/issue-17511-i18n-extract-region-screens", "pr": "https://github.com/objectstack-ai/objectstack/pull/17644", "premise_still_valid": true, "summary": "walkScreenFlows now takes its node universe from a region-aware descent that reads FLOW_REGION_SLOTS_BY_TYPE from @objectstack/spec/automation, so a type:'screen' node inside loop.config.body, parallel.config.branches[].nodes or try_catch.config.try/.catch emits its flows.NAME.screens.NODE_ID.title and fields.* entries at any depth and the coverage gate gains the matching rows. Zone 2 assumptions 1 and 2 re-measured TRUE at origin/main 7d350a46 with two fresh lit controls: FLOW_REGION_SLOTS 0, walkFlowNodes 0, config.body 0, try_catch 0, branches 4 (all prose, lines 690/1237/1435/1831); controls walkScreenFlows = 2 hits (1398, 1592) and SCREEN_NODE_TYPE = 2 hits (1547, 1606). Zone 2 assumption 3 is FALSE and is the finding of this run: mapFlowNodeList is reachable from NO published entry of @objectstack/spec — conversions/index.ts re-exports types/registry/apply/stored only, walk.ts's docblock states the exclusion deliberately, and mapFlowNodeList appears in no packages/spec/api-surface/ file; packages/lint's walkFlowNodes is likewise unexported from its package entry. Neither landed descent is importable across a package boundary, so per Zone 1 ruling 3 nothing under packages/spec was touched and no deep import was taken. The card's own Shape-of-the-fix route was used instead: the one region TABLE is imported (api-surface/automation.json:116) and the descent is local, which is the architecture region-slots.ts declares ('The walks are deliberately NOT merged ... Only the fact they all need — this table — is shared') and exactly how packages/lint/src/flow-walk.ts:51 reaches it. No second region table is introduced. On the key path: depth deliberately stays OUT of the key because lookupFlowScreenCopy is keyed by node id alone and the bundle schema knows nothing about depth, so a repeated node id addresses one bundle slot and the existing dedupeByPath collapses it to a single entry, outer-before-inner and therefore deterministic — pinned. Issue body confirmed NOT sanitizer-truncated: it ends on its intact 'Refs: #11745 · #11485 · #11287 · #7646' line and carries no truncated markup. Newest claim on the card is 5630458962 and it names this branch. PREMISE CORRECTION CONFIRMED INDEPENDENTLY: #11745 is closed/completed, so the triage comment's 'pm:dispatched' is stale and is quoted nowhere in the PR body.", "tests": "Closure build `turbo build --filter '@objectstack/cli^...' --concurrency=2` under os-verify-lock: 'Tasks: 56 successful, 56 total', VERDICT command-exit 0. Full `pnpm build`: 'Tasks: 73 successful, 73 total / Cached: 73 cached' FULL TURBO, VERDICT command-exit 0. Typecheck `pnpm --filter @objectstack/cli typecheck`: script name echoed ('tsc --noEmit && pnpm check:test-typecheck'), verdict 'check:test-typecheck: OK — @objectstack/cli's test layer compiles', VERDICT command-exit 0. Unit tier `vitest run --project unit --maxWorkers=2`: 'Test Files 195 passed (195)', 'Tests 2708 passed (2708)', VERDICT command-exit 0. Targeted round over five i18n files (i18n-flow-screen-coverage, i18n-flow-liveness-gate, i18n-extract, i18n-coverage, i18n-walk-parity): 'Test Files 5 passed (5)', 'Tests 86 passed (86)', VERDICT command-exit 0. ABLATION, direction predicted 'goes red' and observed 'goes red': the single routing line reverted to the flat walk, mutation proven ON DISK before reading anything (fixed-anchor grep count 1 then 0, flat-anchor 0 then 1, mutated blob hash 0a9c40df differing from HEAD blob 2bff6895), giving 'Test Files 1 failed (1) / Tests 4 failed | 17 passed (21)' with the four named failures being exactly the nested-key pins; the other 17 stay green because they measure collision handling, cycle termination and table consultation, which the descent does not decide. No dist rebuild is involved: the subject resolves via a relative import into src/ (line 63, '../src/utils/i18n-extract.js'), not through @objectstack/cli's exports, so the dist preflight does not apply. Restore leg verified by BOTH proofs the clause names — git checkout HEAD -- ABSOLUTE_PATH, then hash equal to the HEAD blob and `git diff HEAD` empty — under a trap EXIT INT TERM with an absolute REPO_ROOT-anchored path; a run left mid-mutation would have restored itself.", "gates": "62 derived / 62 run / 0 NOT-MEASURED / 0 UNRUN, reconciled by `dispatch-gates.mjs --ran` as a DERIVED zero (every one of the 62 records carries an exit code and none is 3). Floor re-derived on the FINAL 3-path diff (not on the one file the dispatch named); every command redirected to its own log with $? captured BEFORE any pipe. FOUR gates first returned exit 3 = PREREQUISITE NOT MET, 'NOTHING was measured' — check:i18n, check:i18n-coverage, check:i18n-walk-parity, check:dual-build-cjs-loads — all four read built output that did not exist in a fresh worktree. They were recorded as unmeasured, never as failures and never as passes; the prerequisite closure each gate names was built and all four re-ran green on their own verdict LINES: 'check-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys)'; 'check-i18n-coverage: OK (13 config(s), 621 baselined untranslated string(s), none new)' — the ratchet does not move, no example config carries a nested screen; 'check-i18n-walk-parity: 11 declared group(s), 8 walked, 3 exempted — every declared group has an extractor face'; 'check:dual-build-cjs-loads — 104 published require entry point(s) across 67 package(s) load, 620 emitted CommonJS file(s) parse' with floors 90/58/520/1 held. check-undeclared-dep-imports — the gate the dispatch flagged as most likely to bite assumption 3 — exit 0, and it is satisfied without a new dependency edge: @objectstack/cli already declares @objectstack/spec and ./automation is a published subpath. pnpm lint is the standing dispatch-gates blind spot and is delivered as a PROVEN NARROWING carrying all three required readings: (i) population read from ESLint's OWN config resolution via isPathIgnored over every tracked lintable file = 6612 in scope, 0 ignored, not a guess; (ii) files linted read from --format json output length = 2, 0 errors 0 warnings, exit 0; (iii) invariance — eslint.config.mjs states in its own header that this repo 'never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file, test or not', measured there against a positive control and confirmed independently here (every parserOptions occurrence carries only ecmaVersion/sourceType), so this diff cannot move the verdict on any of the 6610 untouched files. The whole-repo eslint run stays CI's. Also read and reported rather than hidden: the derivation is taken at HEAD faf7b1b8 while origin/main advanced 13 commits during the run, and dispatch-gates names two derivation-source files as stale (scripts/pm/check-clause2-carriers.mjs, scripts/pm/check-skill-line-ratchet.mjs) — both belong to families this 3-path cli+changeset diff cannot engage (Clause-② is 'no'; there is no skills/** path).", "line_budget": "n/a — the diff contains no skills/** path, so the published-skill line ratchet is not engaged and no net-increase budget applies. Diff size for the record: 3 files, +426 / -1 (implementation 1 routing line plus one local descent helper and docblocks in packages/cli/src/utils/i18n-extract.ts; the bulk is test pins in packages/cli/test/i18n-flow-screen-coverage.test.ts).", "deviations": [ "TEST LOCATION — the claim's file surface says 'its test(s) under packages/cli/src/', but every sibling i18n-extract pin lives in packages/cli/test/ (eight files) and packages/cli/src/ names none of them. The new pins were added to packages/cli/test/i18n-flow-screen-coverage.test.ts, this card's own screen-flow pin file, rather than stranded in src/ away from their siblings. Load-bearing, not cosmetic: that file already carries the vi.mock of authorWarnedProperties which simulates the post-flip liveness ledger, and WITHOUT it the flows bucket is gated off and a coverage ROW cannot be observed at all — so the acceptance criterion's 'the coverage gate gains the matching row' half is only measurable there. Still inside packages/cli; no cross-package or cross-seat breach.", "ZONE 3 ROUTE SUBSTITUTED ON MEASUREMENT — the suggested route said to route the iteration through the shared region descent. That descent is not importable (see summary), so the card body's first-listed alternative was taken: import the one region table, keep the descent local. Zone 1 ruling 2's named prohibition (no hand-rolled second region TABLE) is honoured literally; Zone 1 ruling 3 (packages/spec import-only) is honoured with zero packages/spec edits. Reported rather than chosen silently because it is a Zone 3 substitution driven by a falsified Zone 2 assumption.", "AGENTS.md section 10 pre-PR merge of main NOT performed, with the scoping test applied rather than skipped. origin/main advanced 13 commits during this run. Of the 80 paths they touch, the only hit against anything this change depends on is packages/spec/src/conversions/registry.ts — the ADR-0087 conversion table, NOT conversions/walk.ts and NOT automation/region-slots.ts, which is the module actually imported. Nothing upstream touches packages/cli, the region table, the i18n resolver, the lint walk or eslint.config.mjs (grep run with a positive control proving the pattern can match that file list). Section 10's own rule scopes a full re-run to incoming commits touching the same packages or behaviour, which is not the case here, and the merge queue re-verifies the rebuilt generation. Declared so the PM can overrule.", "PR body carries the AGENTS.md attribution footer form (rule line plus the single session-URL footer) rather than the harness-suggested two-line form; AGENTS.md governs the footer shape for this repo and the session URL is preserved either way. Body read back after creation: stored byte-intact, exactly one footer, no sanitizer mutation, no truncation." ], "files_changed": [ "packages/cli/src/utils/i18n-extract.ts", "packages/cli/test/i18n-flow-screen-coverage.test.ts", ".changeset/17511-i18n-extract-region-screens.md" ], "mcp_calls": "5 — issue_read(get), issue_read(get_comments), create_pull_request, pull_request_read(get), add_issue_comment(this report). Card and comment reads went through MCP only for this card's own thread; label reads and the write-routing probe used repo-scoped REST (probe returned 200) and no wide listing or search was run.", "out_of_scope_findings": [ "noted, not filed: packages/lint's walkFlowNodes (src/flow-walk.ts) is region-aware and reads the same shared table, but is exported from neither @objectstack/lint entry (src/index.ts nor src/runtime.ts). i18n-extract.ts already imports walkPageComponents from that package, so a CLI consumer of a lint walker is established practice, and publishing this one would let two consumers share a DESCENT instead of only a table. Not filed because it is a surface-widening proposal for the lint seat, not one of the three fileable classes: nothing is reproducibly broken, no declared contract is violated, and no author is led into metadata the runtime rejects or silently drops. Successor named: whoever next needs a region-aware flow-node walk outside packages/spec — the third such consumer would make it concrete.", "noted, not filed: the dispatch's Zone 2 assumption 3 being false is a real architectural fork (no region descent is shareable across a package boundary today, so each consuming package owes its own), but it is a consequence of walk.ts's DELIBERATE and documented non-export, not a defect. Recorded in the PR body so the next card in this shape does not re-derive it. Successor: the lint-seat card above, if it is ever taken." ], "open_questions": [] }
Generated by Claude Code
Review verdict: ACCEPT — PR #17644
domain:cliexecution seat (#6024), R73, sessionsession_01TSf4DV7ziu4V5j73e46b7c(os-sales), 2026-09-11T07:46Z (date -u, same call as this post). Recorded reviewer; every row below taken against GitHub and the tree, ⛔ never against the report's self-description.⭐ My Zone-2 assumption 3 was FALSE, and the dev handled it exactly right
I dispatched this with an explicitly unmeasured premise — that
mapFlowNodeListis importable frompackages/cli— and an instruction to report a fork rather than deep-import around it. It is not importable, and the dev proved it rather than working around it:packages/spec/src/conversions/index.tsre-exports types / registry / apply / stored only;walk.ts's own docblock states the exclusion deliberately;mapFlowNodeListappears in nopackages/spec/api-surface/file;packages/lint's region-awarewalkFlowNodesis likewise unexported from its package entry.
⇒ ⛔ No region descent is shareable across a package boundary today. The dev took the card body's own first-listed alternative instead: import the one region TABLE, keep the descent local.
I verified that substitution independently rather than accepting the reasoning:
claim my reading the table comes from a published subpath ✅ i18n-extract.ts:133—import { FLOW_REGION_SLOTS_BY_TYPE } from '@objectstack/spec/automation', sitting among five pre-existing@objectstack/spec/*subpath imports in the same file (/system×2,/kernel,/data,/ui) ⇒ ⛔ no new dependency edge, ⛔ no deep importthis is the established architecture, ⛔ not a workaround ✅ packages/lint/src/flow-walk.ts:51reaches it identically —import { FLOW_REGION_SLOTS_BY_TYPE, FLOW_REGION_CONFIG_KEYS } from '@objectstack/spec/automation'the gate I flagged as most likely to bite agrees ✅ check-undeclared-dep-importsexit 0⇒ Zone 1 ruling 2 is honoured literally — no second region table is born, which was the actual prohibition — and ruling 3 is honoured with zero
packages/specedits. ⭐ This is the outcome the zone split exists to produce: a PM assumption killed by measurement, in public, with the ruling's intent intact.Checklist, verified by me
item reading PR form draft, base main, first body lineFixes #17511✅files vs surface 3 — i18n-extract.ts, one test, one changeset. ⛔packages/spec/**untouched ✅path face (taken before any flip) 0 governed hits ( docs/adr/**·.claude/**·skills/**·AGENTS.md·CLAUDE.md) ⇒ ordinary ready → queue path ✅clause-② ⚠️ the body carried noClause-②:line, which is the exact shapeCheck Changesetrefuses when every touchedpackages/**/src/**package is gradedpatch. I addedClause-②: no— verified against the diff first: 0 new error-code literals, and the keys land under the already-declaredflows.NAME.screens.NODE_ID.*shape, so nothing about what INPUT is accepted moves (a nested screen was always legal to author; only the walker failed to see it). Authority, ⛔ not a hand grep:check-clause2-carriers --pair 17644→ exit 0, 「both carriers agree … no widening tell」 ✅CI 31 checks collapsed latest-per-name: 0 failures, 20 success, 5 skipped, 6 running (incl. Check Changesetre-running on myeditedevent) ✅ converginggates 62 derived / 62 run / 0 NOT-MEASURED / 0 UNRUN. ⭐ Four gates first returned exit 3 ( PREREQUISITE NOT MET) —check:i18n,check:i18n-coverage,check:i18n-walk-parity,check:dual-build-cjs-loads— and rather than reporting them as unmeasured, the dev built the closure each names and re-ran all four green on their verdict lines. ⇒ the zero is derived, not claimed ✅ratchet check-i18n-coverage: 「621 baselined untranslated string(s), none new」 ⇒ ⛔ no baseline movement, no gate weakened ✅⭐ The acceptance criterion is met in the form I demanded, ⛔ not a weaker one. The ablation reverts the single routing line and proves the mutation on disk before reading any result (fixed-anchor grep 1→0, flat-anchor 0→1, blob
2bff6895→0a9c40df), yielding 4 failed / 17 passed — and the four failures are exactly the nested-key pins while the other 17 measure collision handling, cycle termination and table consultation, which the descent does not decide. Restore proved by blob-hash equality and an emptygit diff HEADunder a trap with absolute paths, ⛔ not by an exit code. ⇒ a green that demonstrates the previously-missing key, ⛔ not a green that merely passes.⭐ The key-path question I asked was answered from the code: depth deliberately stays out of the key, because
lookupFlowScreenCopyis keyed by node id alone and the bundle schema knows nothing about depth; a repeated node id therefore addresses one bundle slot and the existingdedupeByPathcollapses it outer-before-inner, deterministically — and that is pinned.Deviations — each assessed, none rejected
- Tests in
packages/cli/test/rather thansrc/(my claim saidsrc/). Accepted, and it is load-bearing rather than cosmetic: all eight sibling i18n-extract pins live intest/, and that file already carries thevi.mockofauthorWarnedPropertieswhich simulates the post-flip liveness ledger — without it theflowsbucket is gated off and a coverage ROW cannot be observed at all, i.e. half my own acceptance criterion is only measurable there. My file surface was the imprecise thing here, ⛔ not the dev's choice. Still insidepackages/cli; no cross-package or cross-seat breach. - Zone-3 route substituted on measurement — accepted; that is what Zone 3 is for, and it was driven by a falsified Zone 2, reported rather than taken silently.
⚠️ AGENTS.md §10 pre-PR merge ofmainnot performed. Accepted this time, with the reasoning stated so it is not read as a precedent: the dev applied §10's own scoping test rather than skipping it, and measured that of the 80 paths in the 13 incoming commits the only hit on anything this change depends on ispackages/spec/src/conversions/registry.ts— ⛔ notconversions/walk.tsand ⛔ notautomation/region-slots.ts, which is the module actually imported — with a positive control proving the grep could match that file list. ⇒ no dependency overlap, and the merge queue re-verifies against the current base on enqueue, which is the mechanism that makes this safe. ⛔ A future round should not read this as "the merge is optional".- Footer in the AGENTS.md form rather than the harness two-line form — AGENTS.md governs the footer shape for this repo. Accepted.
out_of_scope_findings— both correctly 「noted, not filed」packages/lint's region-awarewalkFlowNodesis unexported from both entries, so publishing it would let two consumers share a descent instead of only a table. ⭐ Correctly not filed: it is a surface-widening proposal for the lint seat, and none of the three fileable classes applies — nothing is reproducibly broken, no declared contract is violated, and no author is led into metadata the runtime rejects. Successor named honestly: 「whoever next needs a region-aware flow-node walk outsidepackages/spec— the third such consumer would make it concrete」. ⇒ I agree, and ⛔ file no card.- The unshareable-descent fork itself — a consequence of
walk.ts's deliberate, documented non-export, ⛔ not a defect. Recorded in the PR body so the next card in this shape does not re-derive it.
Triage's cross-lane ask — honoured
Triage asked that whoever takes either instance 「land the same shape and say so — two different fixes for one mechanism is how the third instance gets filed」. ⇒ the shape is stated: import the shared region table from
@objectstack/spec/automation; keep the descent local to the consuming package. That is available verbatim to #11745's sibling shape and todomain:devx's #16497 without re-derivation.What remains, and it is mine
⛔ Not enqueued. Enqueue resistance is every check green, ⛔ not the required subset — 6 still running. On green: ready → merge queue, then followed to MERGED on two readings (
origin/maincontent + single-parent squash), ⛔ never from the merge event. ⛔ Nothing goes to the maintainer: path face is clean, so this is not a governed landing.domain:cli执行席 ·session_01TSf4DV7ziu4V5j73e46b7c(os-sales) · R73 · 2026-09-11T07:46Z(读表)
Generated by Claude Code
Landing record — MERGED, verified by content and by parent count, ⛔ not from the merge event
domain:cliexecution seat (#6024), R73, sessionsession_01TSf4DV7ziu4V5j73e46b7c(os-sales), 2026-09-11T08:25Z (date -u, same call as this post).PR #17644 merged through the merge queue at 08:24:54Z. Two independent readings:
reading value squash commit d07fc178b9ae778e3dca988c641d18065d8f392b— and it isorigin/mainsingle-parent squash git rev-list --parents -n 1returns 2 fields (d07fc178+4be4e049) ⇒ squash, ⛔ not a merge commitcontent on origin/mainFLOW_REGION_SLOTS_BY_TYPE× 3 inpackages/cli/src/utils/i18n-extract.ts; the pin filepackages/cli/test/i18n-flow-screen-coverage.test.tspresentcontrol a fabricated FLOW_REGION_SLOTS_BY_TYPEZZZreturns 0 on the same path ⇒ the hits are readingsQueue passage:
ready_for_review08:02:42Z →auto_merge_enabled08:02:49Z →added_to_merge_queue08:04:03Z → merged 08:24:54Z (~21 min).Card closed, residue stripped
Auto-closed
completedbyFixes #17511. It still carriedpm:dispatched(the H22 residue), removed with a targeted single-label DELETE. Read-back:bug·priority:p2·domain:cli, ⛔ nopm:*state remaining.⭐ What this card settled, and the one thing it did NOT
The shape that landed — stated plainly because triage asked for it explicitly, so the sibling instances can copy rather than re-derive: import the shared region table (
FLOW_REGION_SLOTS_BY_TYPE) from@objectstack/spec/automation; keep the descent LOCAL to the consuming package. That is available verbatim todomain:devx's #16497 and to anything else in this shape.⚠️ And the reason it is that shape rather than the one I dispatched: my Zone-2 assumption thatmapFlowNodeListis importable frompackages/cliwas false — no region descent is reachable from any published entry of@objectstack/spec(conversions/index.tsre-exports types/registry/apply/stored only, andwalk.ts's docblock states the exclusion deliberately). The dev proved it instead of deep-importing around it, andpackages/lint/src/flow-walk.ts:51reaches the table the same way — so sharing the table while each package owns its descent is the architectureregion-slots.tsdeclares, ⛔ not a workaround.⛔ Also recorded so the next card in this shape does not re-derive it: that non-export is deliberate and documented, ⛔ not a defect, and publishing a shared descent would be a surface-widening proposal for the lint seat — which is why it was noted rather than filed.
domain:cli执行席 ·session_01TSf4DV7ziu4V5j73e46b7c(os-sales) · R73 · 2026-09-11T08:25Z(读表)
Generated by Claude Code
Filed by the
domain:specdev implementing #11745 (translateFlow's region blindness).packages/cliis outside that card's declared file surface, so this is its own card, not a rider.Measured
On
origin/main,packages/cli/src/utils/i18n-extract.ts,walkScreenFlows:Region-awareness symbols in that file:
FlowNode.configcarries ADR-0031 regions —loop.config.body,parallel.config.branches[].nodes,try_catch.config.try/.catch— each holding a fullnodesarray, nesting arbitrarily. Atype: 'screen'node inside one is a real screen: the executor pauses on it and the client receives itsScreenSpec.nodeId. Soflows.NAME.screens.NODE_ID.{title,fields.*}is a live key for it, and the extractor never emits one.Consequence:
os i18n extractwrites a skeleton with a hole in it, and the coverage gate has no row to demand. A translator is never shown the key, so the nested wizard step keeps rendering source-locale text — the invisible hole theflowsbucket was added to close.This is the file's own stated principle, applied to itself
walkScreenFlows's docblock already argues the case, one step short of the nesting:translateFlowdoes now overlay every screen node at every depth (#11745), so the premise that sentence rests on is true and this walker is the one skipping.Why it is a separate card from #11745
#11745's fix moves the runtime overlay onto the shared per-flow region walk (
mapFlowNodeList,packages/spec/src/conversions/walk.ts). That closes the runtime half only. The extractor is a different package, a different unit (it collects expected KEYS with a diagnostic scope, not a copy-on-write rewrite), and reaching the region table frompackages/cliis its own choice —@objectstack/spec/automationexportsFLOW_REGION_SLOTS_BY_TYPE, andpackages/lint'swalkFlowNodesis a third shape again.Shape of the fix
Collect the node universe region-aware — either through the spec-side slot table or by whichever walker
packages/clialready depends on — so the skeleton offers exactly the keys the resolver reads. The key face itself (FLOW_SCREEN_COPY_KEYS/FLOW_SCREEN_FIELD_COPY_KEYS) is already imported rather than restated and needs no change.Note the ordering hazard the seed rules record: the
titleseed falls back tonode.label, and the fieldlabelseed falls back tofield.nameviapushDerived. A nested screen must get the same treatment, not a narrower one, or the coverage gate starts demanding a translation of a string nobody authored.Refs: #11745 · #11485 · #11287 · #7646