Repository navigation
fix(cli)!: os validate and os build refuse a flow whose region the automation engine refuses to register - #29043
Conversation
…ine refuses to register Both commands now hand each flow to the automation engine's registration verdict (FlowSchema, then the spec's validateControlFlow) through utils/flow-region-shape.ts, and refuse it with the engine's message behind the flow's name. Before, they passed a flow whose try_catch, loop or parallel region was not single-entry/single-exit (or a single-branch parallel), and boot logged a warning and ran without it. os lint stays blind to the check; the parity test pins that gap. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 36 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 8 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 85acf69a38b53e04f8f8f09114c2c69e3fc453d5 && git checkout 85acf69a38b53e04f8f8f09114c2c69e3fc453d5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ab085a048241b0bf1fde06696fc6cbf659a6707f d246cd7e24aeae540a86b3f09a72207a35e33d0f && git checkout -B drift-repro ab085a048241b0bf1fde06696fc6cbf659a6707f && git merge --no-ff d246cd7e24aeae540a86b3f09a72207a35e33d0f
node scripts/docs-audit/affected-docs.mjs --json ab085a048241b0bf1fde06696fc6cbf659a6707f
|
|
domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt Contract-review-claim: |
|
domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentThe narrowing, read against the published spec on
No must-fix. Four should-fix items above (③ carries the escalation); none changes the verdict. Implemented-by: VERDICT: PASS |
|
domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt 席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」. 席位意见:与 diff 相符。
|
domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt
Requested by Leehom · project thread
Fixes #28893
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none — no file under
skills/**teaches whatos validate/os buildrefuse about flow regions; the region rules themselves are unchanged.维护者速读
os validate和os build放行一个try_catch/loop/parallel区域不是单入口单出口(或单分支 parallel)的 flow,build 还照常产出 artifact;引擎在registerFlow里用validateControlFlow拒绝它,启动只记一条[Automation] failed to register flow警告,然后不带这个 flow 继续运行,触发器从不触发。FlowSchema,再 spec 导出的validateControlFlow),任意深度;拒绝信息就是引擎原文(<where>: <errors>)前面加 flow 名,退出码 1,build 不写dist/。规则没有第二份拷贝,未改packages/spec/**。Clause-②: no (narrowing),标题带!):新拒绝的每个 flow 本来就注册失败、从未运行,运行中的 flow 不受影响。os lint仍看不见这个检查(parity 测试里以blindTo: ['lint']钉住);顶层图的环(detectCycles在 service-automation 内且未导出)validate 仍放行;启动横幅不计"注册失败"数,只在 Boot diagnostics 里逐条出现。What changes
packages/cli/src/utils/flow-region-shape.ts(new):findFlowRegionRefusals(parsed)walks eachpackages[]entry's flows, or the stack's own flows when it declares no packages (the walkfindExtensionFieldSuffixRefusalstakes), and hands each one to the engine's registration verdict:FlowSchema.safeParse, thenvalidateControlFlowfrom@objectstack/spec/automation. A refusal is a row{ path, code: 'VALIDATION_ERROR', flow, message }whose message isFlow '<name>' would be refused at registration:followed by the engine's message verbatim. Like the engine it stops at a flow's first malformed region; every flow is judged. A flow the schema refuses is left to the schema step.commands/validate.tsstep 3a-vi andcommands/compile.ts(os build) step 3b-iii-ter call it beside the__creservation gate, ahead of every artifact write, with the same two faces as the gates around them: the command's--jsonfailure envelope (errorsrows), and the text face (printError+ one bullet per flow, exit 1).test/validate-build-gate-parity.test.ts:findFlowRegionRefusalsjoinsSHARED_NON_REGISTRY_GATES, the ledger of gates both commands run.Placement (A), in lane, following #28727: a
cli/src/utilsjudge over the spec's exported function. Nopackages/spec/**,packages/lint/**orservice-automationedit.Tests
test/validate-flow-region-shape.test.ts(new, in-processValidate/Compile, 4 cases):try_catch 'guard'whose catch region isd → x, y, zwith no join): validate--jsonexits 1 with a row carryingtry_catch 'guard' catch: region must be single-exit but has 3: x, y, zand the flow name; build--jsonexits 1,success: false, the same row, and nodist/;loopwhose body is that fan-out, inside atry_catchtry region): the same, withtry_catch 'outer' try → loop 'sweep' body: …;parallel:parallel 'fan': a parallel block needs at least 2 branches, both commands;x, y, z → j) validates and builds.origin/main(ff881050): the three refused cases red (validate exited 0, build succeeded), the control green.validate.tsreplaced by[]→ 3 red; the call incompile.tsreplaced by[]→ 3 red (expected +0 to be 1).test/authoring-rule-command-parity.test.ts: per the seat's ruling,os lint's blindness is recorded asblindTo: ['lint']. It sits in a second table,OUTSIDE_THE_TABLE, not inCASES: everyCASESrow asserts the rule gates on all three commands throughrunAuthoringRules, and this check is not an authoring rule, so aCASESrow would be red by construction. The new row ASSERTS the blindness (runAuthoringRules('lint', …)carries nosingle-exitfinding), so closing the gap reds it and the case moves intoCASES.os validaterun (real CLI, text face) over the duly fixture:Acceptance notes
yes (narrowing)if the gates read it so.clause2-line.mjsreadsyes (narrowing)as "widens one surface and narrows another"; nothing here widens (no new export, schema key or accepted input;flowon the error row is a new member of an existing row list, likerule/packageIdon its neighbours). So the line isno (narrowing), the spelling cli:os builddoes not run the__cextension-field judge thatos validateruns (#28564 follow-up) #28727 used for the same kind of change; the changeset carries the**BREAKING**banner and an ADR-0087not-requireddisposition.Flows:line (utils/format.tsprintAutomationSummary, ~1718) counts registered flows (flowCount=getFlowRuntimeStates().length), bound flows and drafts.declaredFlowCount(commands/serve.ts~5708,resolveStackCollection(config, 'flows')) is read only for the engine-disabled line. A refused flow is in neither count; it surfaces only as its[Automation] failed to register flowline in Boot diagnostics. A refused count would be one moreparts.pushbeside the draft count, but no one-line, same-file version is exact:declaredFlowCount - flowCountis moved by runtime overlay rows and same-named contenders too, and the engine exposes refusals only per name (getFlowRegistration(name)), so an exact count needs a new summary field collected inserve.ts. Left out, reported. Whether boot continues is untouched.os lintblind, B, domain:devx), and a cycle in the flow's TOP-LEVEL graph, whichos validatestill passes (start → a → b → a → end, exit 0): the engine refuses it withdetectCycles(packages/services/service-automation/src/flow-registration-checks.ts), which is exported from that file but not from the package index. Region bodies are cycle-checked, byvalidateControlFlow.subflow-guards.ts,region-suspension-refusal.ts, the arming gate) are untouched.Verification
Gates:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 62 commands on the diff; all 62 exit 0, and--ranreconciles 62 derived, 62 run, 0 NOT-MEASURED.pnpm --filter @objectstack/cli typecheck(runscheck:test-typecheck): exit 0.pnpm --filter @objectstack/spec check:error-code-provenance: exit 0. Scoped eslint over the six changed.tsfiles: exit 0.pnpm --filter @objectstack/cli exec vitest run --project unit, after mergingmain(00d0668d, nothing underpackages/cli,packages/specorpackages/lint): first run 368/369 files, 1 red:validate-build-gate-parity.test.ts"every call site … is classified" namedfindFlowRegionRefusals. Fixed by adding it toSHARED_NON_REGISTRY_GATES(a gate both commands run); re-run on the final head: 369/369 files, 5201/5201 tests, VERDICT command-exit 0. Theintegrationtier (whereauthoring-rule-command-parity.test.tslives, for its spawn case) is declared to CI; its in-process rule cases, the new pin included, ran locally green (10 passed).Dogfood battery (owed for the
(narrowing)arm): after building the@objectstack/dogfood...closure (68/68 tasks),pnpm --filter @objectstack/dogfood testunder the lock, VERDICT command-exit 0 · Test Files 383 passed | 3 skipped (386) · Tests 3163 passed | 28 skipped (3191). It ran on832bd06d; the final head adds the ledger row in apackages/clitest file and one more merge ofmain(ab085a04: underpackages/speconlyshared/region-currency.*and a save-notice placement change with aFlowSchemacomment, nothing the region check reads).Generated by Claude Code