Repository navigation
fix(cli)!: os validate and os build refuse a flow with an undeclared top-level cycle, as the engine refuses it at registration - #29595
Conversation
… the fix) Claude-Session: https://claude.ai/code/session_01ADHkmSGEsjUPQp4S968p2X Co-authored-by: Claude <noreply@anthropic.com>
…ow cycle, with the engine's sentence The CLI's flow walk asks findUndeclaredFlowCycle (@objectstack/metadata-core), the judge the engine's registration and the runtime publish gate call, before validateControlFlow, in the engine's order. Claude-Session: https://claude.ai/code/session_01ADHkmSGEsjUPQp4S968p2X Co-authored-by: Claude <noreply@anthropic.com>
… flow-cycle corpus check:test-source-alias names the new subpath import; the corpus the CLI tests read is the checkout's, not a stale dist/testing.js. Claude-Session: https://claude.ai/code/session_01ADHkmSGEsjUPQp4S968p2X Co-authored-by: Claude <noreply@anthropic.com>
The CLI region walk (flow-region-shape.ts) moved into the lint rule table (flow-region-shape-invalid). The undeclared-cycle check, which lint cannot reach (no metadata-core edge), moves to its own CLI walk, utils/flow-undeclared-cycle.ts, run as os validate step 3a-vii and os build step 3b-iii-quater. Claude-Session: https://claude.ai/code/session_01ADHkmSGEsjUPQp4S968p2X Co-authored-by: Claude <noreply@anthropic.com>
…gate The gate-parity ledger names every call site in compile.ts and validate.ts; the cycle walk runs in both and is not a registry rule because lint has no metadata-core edge. Claude-Session: https://claude.ai/code/session_01ADHkmSGEsjUPQp4S968p2X 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 ebecfa080db61d27bee06ac2876d69613c29e921 && git checkout ebecfa080db61d27bee06ac2876d69613c29e921
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 50e35a280994dcecb714b6fa1f2d44840900f5e7 5b16eb349b8d570ea22c453f279e62ae6e94db7d && git checkout -B drift-repro 50e35a280994dcecb714b6fa1f2d44840900f5e7 && git merge --no-ff 5b16eb349b8d570ea22c453f279e62ae6e94db7d
node scripts/docs-audit/affected-docs.mjs --json 50e35a280994dcecb714b6fa1f2d44840900f5e7
|
|
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt Contract reviewServed-tier: Face: narrowing ( ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
VERDICT: PASS Must-fix: none.
Generated by Claude Code |
…test file and the bare specifier's own alias entry Comment-only. Claude-Session: https://claude.ai/code/session_01ADHkmSGEsjUPQp4S968p2X Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt 席位核对ACCEPT ·
Generated by Claude Code |
Requested by Steve · project thread
Fixes #29051
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none —
skills/objectstack-automation/references/state-machines-and-approvals.mdalready teaches that a cycle-closing edge MUST be typed'back'and that an unmarked cycle is refused; that stays true, and the CLI now refuses the same flow before it ships.维护者速读
os validate和os build现在拒绝顶层图里有环、且环上没有一条type: 'back'边的流程(ADR-0044),退出码 1,build 不写产物;报错定位到该流程,正文是引擎注册时那句 "Flow contains a cycle: …"。@objectstack/metadata-core的findUndeclaredFlowCycle,与引擎注册、发布门是同一个函数,没有第二份环检测。回滚即还原本 PR,无数据迁移。packages/cli/src/utils/flow-undeclared-cycle.ts(新的遍历,一次调用),packages/cli/src/commands/validate.ts第 3a-vii 步与compile.ts第 3b-iii-quater 步(新的门禁步骤),packages/cli/test/validate-flow-region-shape.test.ts末尾(用同一份FLOW_CYCLE_CORPUS驱动 validate 与 build)。pnpm --filter @objectstack/cli exec vitest run src/utils/flow-undeclared-cycle.test.ts test/validate-flow-region-shape.test.ts,预期 16 个用例全绿;在 main 上环相关用例全红(validate 答valid: true)。What changes
packages/cli/src/utils/flow-undeclared-cycle.ts):findFlowCycleRefusalswalks eachpackages[]entry's flows (or the stack's own flows when it declares no packages), parses each withFlowSchema, and asksfindUndeclaredFlowCyclefrom@objectstack/metadata-core(exported by fix(metadata)!: refuse a flow with an undeclared cycle at publish, as the engine refuses it at registration #29226), the judge the engine's registration and the runtime publish gate call. No copy of the DFS. A refused flow is oneVALIDATION_ERRORrow located at the flow (flows[N]orpackages[K].manifest.flows[N]), messageFlow 'NAME' would be refused at registration:followed by the judge's sentence verbatim.@objectstack/clialready depended on@objectstack/metadata-core.packages/cli/src/commands/validate.tsstep 3a-vii,compile.tsstep 3b-iii-quater): one gate step each, the same shape as the CLI's other walks (--json:errorsrows and exit 1; text: a heading naming ADR-0044 and the rows). The build writes no artifact.@objectstack/lint(flow-region-shape-invalid).@objectstack/lintdoes not depend on@objectstack/metadata-core, so the rule table cannot reach this judge without a new package edge, and the ruling forbids a copy (seat claim amendment 6584349353 declares this file surface). Soos lintdoes not give this verdict;os validateandos builddo..changeset/29051-cli-flow-undeclared-cycle.md:@objectstack/clipatch, BREAKING banner with before/now and the remedy,adr-0087: not-required (no-migration-prescription).packages/cli/vitest.config.tsgains one anchored alias,@objectstack/metadata-core/testing→../metadata-core/src/testing.ts, whichcheck:test-source-aliasnames for the new corpus import. The bare@objectstack/metadata-corespecifier is not aliased; its ledger entry is unchanged.Not touched:
packages/metadata-protocol/**,packages/spec/**,packages/lint/**,ui/**.Tests
packages/cli/src/utils/flow-undeclared-cycle.test.ts(new): one case perFLOW_CYCLE_CORPUSentry (@objectstack/metadata-core/testing, the corpus the judge, the engine's registration and the publish gate are driven from). A refused case gives exactly one row atflows[0]whose message isFlow 'NAME' would be refused at registration:+undeclaredFlowCycleMessage(cycle). A back-typed or acyclic case gives none. Plus an assembled artifact: the refused flow in the secondpackages[]entry is located atpackages[1].manifest.flows[1], and the stack's top level is not walked.packages/cli/test/validate-flow-region-shape.test.ts(new block at the end, in-processCommand.run, unit tier): the whole corpus in one stack.os validate --jsonandos build --jsoneach exit 1, with exactly one row per refused corpus flow at its ownflows[N]carrying the judge's sentence; the build writes nodist/. CONTROL: the corpus's back-typed and acyclic flows alone validate and build (exit 0).packages/cli/test/validate-build-gate-parity.test.ts. Its ledger classifies every call site incompile.tsandvalidate.ts, sofindFlowCycleRefusalsjoinsSHARED_NON_REGISTRY_GATESwith the reason it is not a registry rule.Acceptance notes
flow-region-shape-invalid) first, so a flow with both a malformed region and an undeclared cycle is refused for its region first, then for the cycle once the region is fixed. Both are refusals; only which one an author reads first differs.os lintdoes not give this verdict (see What changes). Closing that would need a lint → metadata-core edge, or the judge moving to a package lint already depends on; a separate decision, not taken here.edges.N). The judge returnsclosingEdgeIndex, so the CLI row could carry the edge later; not done here, to keep the row shape the CLI's other flow walks use.FlowSchemarefuses is not judged by this walk:flow-region-shape-invalidrefuses it at the rule-table step, which exits before this one.Verification
Head
5b16eb34(origin/main merged ata5b8e9bf, after #29495).5b16eb34is a comment-only commit over1f72080c(the@objectstack/metadata-core/testingalias comment inpackages/cli/vitest.config.ts); the suites and dogfood below were measured ona5b8e9bf/1f72080c, and the gate list was re-run on5b16eb34.Red first on main, green with the fix, ablation
aca58cdb, the tests then in their pre-feat(lint)!: os lint refuses a flow region the engine refuses to register (flow-region-shape-invalid) #29495 home):Tests 6 failed | 13 passed (19).os validate --jsonon the corpus stack answered"valid": truewith exit 0. Every refused corpus case wasexpected [] to deeply equal [ { path: 'flows[0]', … } ].a5b8e9bf:pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 src/utils/flow-undeclared-cycle.test.ts test/validate-flow-region-shape.test.ts→Test Files 2 passed (2) · Tests 16 passed (16).a5b8e9bf, throughscripts/ablation-replace.mjs: anchorconst cycle = findUndeclaredFlowCycle(result.data);replaced with a null verdict. Output:anchor x1 -> x0,ok mutation landed: anchor 1 -> 0, blob 5f733ce3ccf2 -> 9d51706e9b08. Run: 6 red (the 4 refused corpus cases, the assembled-artifact case, the validate/build door case). Thenok restored: blob == HEAD … git diff HEAD empty. The CLI tests import source, so no dist leg.Suites
pnpm --filter @objectstack/cli typecheck: VERDICT command-exit 0 (ona5b8e9bf;1f72080cadds one ledger row to a test file).pnpm --filter @objectstack/cli exec vitest run --project unitona5b8e9bf:Tests 1 failed | 5281 passed (5282). The one failure wasvalidate-build-gate-paritynaming the unclassifiedfindFlowCycleRefusals, the forced edit above (1f72080c). On1f72080c:validate-build-gate-parity,authoring-rule-command-parity,flow-undeclared-cycle,validate-flow-region-shape→Test Files 4 passed (4) · Tests 61 passed (61).eslint --no-inline-configover the 7 touched source and test files: exit 0.a5b8e9bf(pnpm --filter @objectstack/dogfood test, afterpnpm install --frozen-lockfile && pnpm build): VERDICT command-exit 0 ·Test Files 397 passed | 3 skipped (400)·Tests 3277 passed | 28 skipped (3305).1f72080cchanges one CLI test file only.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon1f72080c, and again on5b16eb34(62 families, all exit 0,--ranthe same derived zero): 62 families, each run with its exit code captured before any pipe, all exit 0.--ranreconciliation:62 derived famil(ies) accounted for — 62 run, 0 NOT-MEASURED (a DERIVED zero — all 62 recorded an exit code and none of them is 3).check:plugin-teardown-shape --self-testneeded the shallow-clone deepen (git fetch --depth=1 origin 621a4876…) first.