Repository navigation
fix(cli): the flow-region gate never passes a flow it could not judge, and the boot banner counts refused flows - #29243
Conversation
…, and the boot banner counts refused flows - findFlowRegionRefusals: a FlowSchema parse failure is a located VALIDATION_ERROR row instead of a silent return. - authoring-rule-command-parity: the OUTSIDE_THE_TABLE pin also asserts the doors' own gate refuses its fixture with the marker; docblock says it measures the registry; test-budget header counts ten rule cases. - validate-flow-region-shape: an assembled-artifact packages[] fixture, asserting exit code, path and that build writes no dist/. - os serve banner: declared flows the engine refused at registration are counted beside the declared count, from getFlowRegistration. Claude-Session: https://claude.ai/code/session_01ADHkmQBbn1k9FtQeh2nJSs Co-authored-by: Claude <noreply@anthropic.com>
…ount line Claude-Session: https://claude.ai/code/session_01ADHkmQBbn1k9FtQeh2nJSs Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 19 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 35 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 8d42804642ca0314952a5aae364d3b9e8905228b && git checkout 8d42804642ca0314952a5aae364d3b9e8905228b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 45e9b2f14c91f76683296cd33bb1b477cbaef1bb fc2494112839c156ca8b947f1458a4e0e0ac506c && git checkout -B drift-repro 45e9b2f14c91f76683296cd33bb1b477cbaef1bb && git merge --no-ff fc2494112839c156ca8b947f1458a4e0e0ac506c
node scripts/docs-audit/affected-docs.mjs --json 45e9b2f14c91f76683296cd33bb1b477cbaef1bb
|
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 席位核对 · ACCEPT · PR #29243 · 2026-11-02T10:16ZHead-sha: 维护者速读(席位终稿)
核对六项
|
Requested by Steve · project thread
Fixes #29059
Clause-②: no
Metadata-protocol: none
domain:server#3 · os-steve · session_01ADHkmQBbn1k9FtQeh2nJSs
维护者速读
os validate/os build的 flow 区域门禁在 FlowSchema 解析失败时不再静默放行,改为报一条带路径的 VALIDATION_ERROR;os serve/os dev启动横幅新增一行「N flow(s) declared, M refused at registration」。objectstack validatepasses a flow whose try_catch catch region has several sinks; the engine refuses it at registration ("region must be single-exit") with only a WARN, so the flow never runs #28893 的启动半边:门禁的静默跳过、空转的盲区断言、未测的 packages[] 主体、过期计数,以及启动时被拒的 flow 只在 Boot diagnostics 里逐条出现。packages/cli/src/utils/flow-region-shape.ts(解析失败分支)、packages/cli/src/commands/serve.ts的collectAutomationSummary(拒载计数读 engine 自己的getFlowRegistration)、packages/cli/src/utils/format.ts的printAutomationSummary(行序与全拒时的早返回)。pnpm --filter @objectstack/cli exec vitest run src/commands/serve-automation-refused.test.ts src/utils/flow-region-shape.test.ts,预期 7 个测试全绿。What changes
All five Wanted items of #29059.
utils/flow-region-shape.ts).findFlowRegionRefusalsused toreturnwith no row whenFlowSchema.safeParserefused a flow. It now pushes aVALIDATION_ERRORrow at the flow'spath, named by the flow'sname(or its path when it has no string name), carrying the schema's issues. The door refuses on it like any other row. New unit testsrc/utils/flow-region-shape.test.tsforces the branch (own flows, nameless body,packages[]entry).test/authoring-rule-command-parity.test.ts). EachOUTSIDE_THE_TABLEentry now names itsgate, and the case first asserts thatgate(stack)is non-empty and carries the marker, then that the registry run asos lintruns it reports nothing with the marker. The docblock now says it measures the registry, not theos lintcommand.packages[]subject tested end to end (test/validate-flow-region-shape.test.ts). A two-entry assembled artifact with the malformed flow in the second entry:os validate --jsonandos build --jsonboth exit 1 with exactly one region row{ path: 'packages[1].manifest.flows[0]', code: 'VALIDATION_ERROR', flow: 'rgn_ops_lead_time' }, and build writes nodist/.CASESplus oneOUTSIDE_THE_TABLE).commands/serve.ts,utils/format.ts).collectAutomationSummarytakes the declared flows' names and counts those for which the engine'sgetFlowRegistration(name).stateisrefused. That read already exists on the engine and onIAutomationService(optional, the metadata door uses it), and the record behind it is written by the plugin'skernel:readycold-boot bind (recordFlowRefusal). So no new engine or plugin surface. The banner prints⚠ Flows: N flow(s) declared, M refused at registration — …after the Flows count line, and alone when every declared flow was refused (the old early return onflowCount === 0would have printed nothing). No line when nothing is refused. Whether boot continues is unchanged.AutomationReadySummarygainsrefusedCount. It is not on any published entry of@objectstack/cli(exportsis.,./console,./hook-body;index.tsre-exports only theServeCommanddefault), and the banner is stderr text, so no JSON or published member changes.Forced test edits:
src/utils/format.boot-warning-classes.test.tsandsrc/utils/format.flow-contender-agreement.test.tsbuildAutomationReadySummaryliterals by hand; each gainsrefusedCount: 0because the member is required.Tests
Red first, by ablation (
scripts/ablation-replace.mjs, each leg restored and proven by blob hash equal toHEADand an emptygit diff HEAD):(1) parse failure: put the silent return back
On
03cc2f40,flow-region-shape.tsanchorif (!result.success) {replaced byif (result.success === false) { return; } if (false) {:src/utils/flow-region-shape.test.ts3 failed / 3, eachexpected [] to deeply equal [ { path: 'flows[0]', … } ]. (A first attempt whose replacement contained the anchor was refused by the tool as a no-op and restored; it is not counted.)(2) vacuous pin: a fixture typo
Fixture edge
dyrewiredd → ytox → y(the catch region becomes single-exit): with this PR's assertion,known gapcase 1 failed,[]: expected [] to not deeply equal []. Control leg, same typo with the new assertion deleted (the pin as onmain): 1 passed. The old pin stayed green on a fixture nothing refuses.(3) packages[] subject
const subjects = entries.length > 0→false && …(walk the empty top level only): the assembled case failed,os validateexit+0, expected 1. Path spellingpackages[N].manifest.→packages[N].: failed withpath: "packages[1].flows[0]"against the expectedpackages[1].manifest.flows[0].(5) boot banner count
serve.tsrefusedCount++→void 0:serve-automation-refused.test.ts2 failed / 4 (refusedCount0). On48c3a64c, the all-refused branch'sprintRefused();→void printRefused;: 1 failed / 4,expected [] to have a length of 1. (On03cc2f40, the earlier line placement, gating the line onflowCount > 0failed the same case.)The test drives the REAL
AutomationEngine(dist):registerFlowrefuses a one-branchparallel, the test records it the way the host's sync does, and the collector and the realprintServerReadyread it.Real boot, one-off measurement (not kept as a test):
os serveon a two-flow config with one one-branchparallel,OS_DATABASE_URL=:memory:, printedwith both
[Automation] failed to register flowand[Automation] cold-boot flow bind: failed to register flowforbt_fanunder Boot diagnostics, and boot continued toPress Ctrl+C to stop. (That run had the line before the count line;48c3a64cmoved it after.)Verification
All on the final head
fc24941128(this branch merged withorigin/maindedc350519; clean merge), after rebuilding@objectstack/cliand its dependency closure:pnpm --filter @objectstack/cli typecheck(tsc pluscheck:test-typecheck): exit 0.pnpm --filter @objectstack/cli exec vitest run --project unit: 374 files, 5252 tests passed.test/authoring-rule-command-parity.test.ts(integration tier, edited here): 12 / 12 passed.node scripts/pm/dispatch-gates.mjs --commandsderived 63 commands for this change set; all 63 exit 0;--ranreconciliation: 63 run, 0 NOT-MEASURED, 0 UNRUN.pnpm lint: exit 0.On the pre-merge head
48c3a64cthe same set was green too. Five gates first exited 3 (prerequisite not met: no full build, shallow clone) and were rerun green afterturbo run buildand an unshallow fetch:check-plugin-teardown-shape --self-test,check:dual-build-cjs-loads,check:i18n,check:i18n-coverage,check:i18n-walk-parity.Dogfood battery: not owed (no
!in the title,Clause-②: nowith no narrowing arm).The 63 derived commands, each with its exit code
Acceptance notes
no. The parse-failure branch cannot be reached by an authored stack: every flow the walk reads was parsed by the stack schema'sflows: z.array(FlowSchema)(and, underpackages[], byAssembledPackageBodySchema, whose collections come from the same stack shape), andFlowSchemaaccepts its own output. All existing validate/build/parity cases and the CONTROL case stay green, so nothing that passes today is refused.sys_metadataflow the engine refuses is not declared, so it is not counted; its per-flow line under Boot diagnostics is unchanged. And the record is written by thekernel:readybind; the boot pull's own refusals are deliberately not recorded (AGENTS.md "Startup registry reads"). A host with noprotocolservice runs no such bind, so its count reads 0 there while the warn still prints. Everyos serveboot measured here has the bind.test/authoring-rule-command-parity.test.tsis integration (it spawns the CLI) and was run locally in full because it is edited; the rest of the integration tier is declared to CI.Generated by Claude Code