You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
metadata: a flow with an undeclared cycle (loop edge not typed back) reads valid as a draft and publishes, then the engine refuses to register it — a dead flow goes live #29025
Filing gate: ① a product defect, found beside the clauses (studio-authoring.flow-edge-type-authoring), reach: the metadata draft and publish doors, measured twice.
Business reason: an author (API, MCP or a hand-written package) who publishes a loop flow without typing the loop-closing edge back gets a valid draft verdict and "published", and the flow never runs because the engine refuses to register it. The Studio canvas already flags the cycle; the server doors do not. #26707 closed the same "draft and publish say valid, the engine refuses" class for edge conditions.
Reader who acts: the triage seat, then the domain:server lane, packages/metadata-protocol/src/runtime-authoring-gate.ts (reuse the engine's cycle check). QA suggestion only: area:automation, p2.
Duplicate search: flow cycle back edge publish registration refused → 40 hits, the near ones closed and different (#28840 save_notice after a suspending child, #21982 / #21898 node config); 26707 → 11 hits, #26707 and #26858 closed for conditions and CEL fields, none for cycles.
Batch-of: release-18.3.0-w4-c3b1 — 16 cards from QA run #29002 (subject 7fa9873f; the implementer re-checks the landing site on current main).
Reproduction
POST /api/v1/packages {"manifest":{"id":"com.qa.r6flow","name":"QA R6 Flow","version":"0.1.0","type":"app","namespace":"qsixf"}}.
PUT /api/v1/meta/flow/qsixf_cycle2?mode=draft&package=com.qa.r6flow with nodes start → push (http) → check (decision) → revise (assignment) and edges start→push, push→check, push→end_err, check→end_a, check→end_b, check→revise (conditional), and revise→push with notype.
GET …/qsixf_cycle2?state=draft&package=com.qa.r6flow → _diagnostics {"valid": true}.
POST /api/v1/packages/com.qa.r6flow/publish-drafts → 200 outcome: "published".
GET /api/v1/meta/flow/qsixf_cycle2 → registration {"state":"refused","reason":{"message":"Flow contains a cycle: push → check → revise → push. Only DAG flows are allowed — … mark the cycle-closing edge with type: 'back' (ADR-0044)."}}; the boot log warns "a flow changed in the metadata store could not be registered".
Expected: the draft verdict and the publish gate refuse the undeclared cycle. Actual: valid draft, accepted publish, refused registration. Reproduced 2 times with fresh flows.
Mechanism (suspected)
runtime-authoring-gate.ts has no cycle rule; the engine's detectCycles lives in packages/services/service-automation/src/flow-registration-checks.ts.
QA-source: #29002 · studio-authoring.flow-edge-type-authoring · observed beside the clauses
Read fresh. The draft verdict and publish gate in packages/metadata-protocol/src/runtime-authoring-gate.ts have no cycle rule, while the engine's detectCycles in packages/services/service-automation/src/flow-registration-checks.ts refuses the flow, so domain:server. A flow that publishes and then never runs is a user-visible defect, p2. It has a repro and a named location, so pm:queue; the item is P2 and was not a run FAIL, so the release rule does not lift it.
Claim: PM loop round 1 · card thread · 2026-11-02T08:31Z
Session: session_01ADHkmQxwoGb5fSnrFkpxmt (the seat); the card thread is session_01ADHkmQvBMGvejv3APX96eu
Account: os-steve (the seat's linked user; the card's assignee)
Branch: claude/project-thread-g4dqyl (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#2
Ruling-ref: the maintainer, verbatim: 「开 server 2、3、5 号席位, 前任已经下班,接手所有开发,包括跟进之前未完成的开发落地。」 Card: none from the maintainer on this card; triage graded p2 (6571456936). Sibling #29051 (p3, same seam on the build side) is held by seat 2 serially behind this card (agreed with seat 3 in the project); server seat 5 handed this card to seat 2 in the project.
File surface: packages/metadata-protocol/src/runtime-authoring-gate.ts (cycle rule on the draft verdict and the publish gate); detectCycles in packages/services/service-automation/src/flow-registration-checks.ts moved to or re-exported from a package both can depend on (⛔ not packages/spec/**: needs_decision if spec is the only home); their tests; a dogfood case; changeset.
Container & model: M, mode:thread, model: opus (TIER_DEFAULT; --tier names no path mandate: PM's call), effort: high
Clause-②: no (narrowing) (expected: a flow with an undeclared cycle that published now refuses at draft and publish; the dev measures and declares)
Metadata-protocol: per AGENTS.md, measured by the dev (likely none or narrowed)
Responsibility: the code is the runtime authoring gate of @objectstack/metadata-protocol and the engine registration check of @objectstack/service-automation · the platform path is a loop flow without type: 'back' that reads valid, publishes, then is refused registration and never runs · reached by every API, MCP or package author of loop flows
Thread-read: 6571456936
Premise: holds (the named seam read on origin/main by the seat this act; the dev re-measures red first)
Serial constraints cleared: none — no open claim on a domain:serverpm:dispatched card names these files (read this act)
Resume-from: none — fresh from origin/main
State: pm:queue → pm:dispatched, assigned to os-steve, recorded by this claim.
{
"issue": 29025,
"status": "done",
"branch": "claude/project-thread-g4dqyl",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29226",
"session": "session_01ADHkmQvBMGvejv3APX96eu",
"premise_still_valid": true,
"summary": "The engine's top-level cycle walk moved unedited from service-automation to metadata-core as findUndeclaredFlowCycle (plus undeclaredFlowCycleMessage and the closing-edge index). AutomationEngine.detectCycles now throws its message, and the runtime authoring gate gained a gate-local rule flow-undeclared-cycle (error, located on flows[0].edges[N], message = the engine's sentence). The draft verdict reads that gate, so a loop whose closing edge is not typed back now reads valid:false, and every publish door refuses it (publish-drafts outcome refused). A loop with any edge typed back still publishes and registers. Parity is pinned by one shared corpus (FLOW_CYCLE_CORPUS on @objectstack/metadata-core/testing, 4 refused / 4 accepted), run through the judge, registerFlow and the gate.",
"tests": "Red first on origin/main fea2dbb7 (gate fix removed): protocol.draft-publish-verdict 'flow — [flow-undeclared-cycle]' FAILED, received {code: undefined, status: undefined}. Green at head: metadata-core flow-cycle.test 11/11; metadata-protocol runtime-authoring-gate.flow-cycle + draft-publish-verdict 44/44; service-automation flow-cycle-registration + engine cycle tests 13 passed. Whole-package runs (lock, typecheck then test, VERDICT 0): metadata-core 23 files/896 tests, metadata-protocol 298 files (4 skipped)/22074 passed (26 skipped), service-automation 258 files/3317 tests. Ablation 1 (src, ablation-replace anchor 1->0, restored blob == HEAD): flowCycleGaps dropped from localIssues -> 5 failed/39 passed (4 refused corpus cases + the draft-verdict card case). Ablation 2 (metadata-core rebuilt, ablation-dist-preflight marker present in dist, absent after restore+rebuild): back-edge exemption removed -> gate+draft-verdict 5 failed (3 back controls, two-loops case, draft-verdict back control), engine corpus 4 failed. The first ablation-2 attempt used a block-comment marker that esbuild strips; preflight said ABSENT, so that reading was discarded and redone. Dogfood case alone (verbose): 2/2 passed.",
"gates": "Head 8a384327 (origin/main merged at 694a118d). dispatch-gates --commands: 66 families, all exit 0 after re-runs; --ran with `:: exit N` records: '66 derived famil(ies) accounted for — 66 run, 0 NOT-MEASURED (a DERIVED zero)'. Re-runs: check-adr-0087-registration exit 1 on the first changeset text (a FROM → TO label read as a migration prescription), exit 0 after the rewrite; check-plugin-teardown-shape --self-test exit 3 (fixture commit outside the shallow clone), exit 0 after fetching 621a4876; check:dual-build-cjs-loads exit 3 (7 packages outside the dogfood closure unbuilt), exit 0 after building them. The changeset-reading gates were re-run after the changeset commit, all exit 0. eslint --no-inline-config on every changed .ts: exit 0. CI check-runs on PR #29226: in_progress at report time.",
"dogfood_battery": "8a384327 · os-verify-lock: VERDICT command-exit 0 (held 24m29s) · Test Files 388 passed | 3 skipped (391) · Tests 3206 passed | 28 skipped (3234) · no failure",
"line_budget": "12 files, +690 / -53 (code: flow-cycle.ts +128, runtime-authoring-gate.ts +77/-1, flow-registration-checks.ts -52 net; the rest are tests, the corpus, the dogfood case and the changeset)",
"files_changed": [
".changeset/29025-flow-undeclared-cycle-publish.md",
"packages/metadata-core/src/flow-cycle.ts",
"packages/metadata-core/src/flow-cycle-corpus.ts",
"packages/metadata-core/src/flow-cycle.test.ts",
"packages/metadata-core/src/index.ts",
"packages/metadata-core/src/testing.ts",
"packages/metadata-protocol/src/runtime-authoring-gate.ts",
"packages/metadata-protocol/src/runtime-authoring-gate.flow-cycle.test.ts",
"packages/metadata-protocol/src/protocol.draft-publish-verdict.test.ts",
"packages/services/service-automation/src/flow-registration-checks.ts",
"packages/services/service-automation/src/flow-cycle-registration.test.ts",
"packages/qa/dogfood/test/flow-undeclared-cycle-publish.dogfood.test.ts"
],
"deviations": [
"Changeset: the order asks for a FROM → TO banner; check-adr-0087-registration refuses a FROM → TO label beside not-required (no-migration-prescription). The banner carries Before / Now bullets and a 'The remedy.' paragraph (the 25579 changeset's accepted shape). No ledger entry, because no key moved and the refused body never registered.",
"Lint: no touched package has a lint script, so eslint --no-inline-config ran on the changed files; the repo-wide pnpm lint is CI's.",
"origin/main moved 15 commits after the 694a118d merge, while the dogfood battery ran. None touches these files (dogfood test files only, other tests). Not re-merged before pr_create, so the battery reading stays on the final head.",
"Rule id and placement: gate-local (beside the deployment-fact rules) rather than AUTHORING_RULES. Reason in open_questions 1."
],
"mcp_calls": "3 — mcp__github__issue_read (get_comments x2, get x1); no write tool",
"api_writes": "2 — pr_create via with-fleet.sh --via dispatch (PR #29226, 9939 bytes sent = stored); this os-dev-report comment via post-stamped.mjs --comment=29025",
"open_questions": [
{
"question": "Where should the cycle rule live: gate-local (this PR) or an AUTHORING_RULES entry in @objectstack/lint, which would also put it on os validate / os build (#29051)?",
"options": [
"A — keep it gate-local here; #29051 adds one findUndeclaredFlowCycle call in packages/cli/src/utils/flow-region-shape.ts (cli already depends on metadata-core), beside its validateControlFlow call",
"B — move it to AUTHORING_RULES (CLI_AND_RUNTIME, runtimeTypes ['flow']): needs a new @objectstack/lint -> @objectstack/metadata-core dependency (pure, spec-only), folds #29051, and touches lint's wiring/explanation tables"
],
"recommendation": "A, because it ships the narrowing on the card's file surface with no new package edge. #29051 is then a one-file follow-up on the same judge. B is the cleaner long-term home only if the lint registry should own every flow-registration check; that is a lane-wide choice, not this card's."
},
{
"question": "New public names, for the maintainer's word before landing: the rule id flow-undeclared-cycle (a wire value in issues[].rule and _diagnostics errors[].code); metadata-core exports findUndeclaredFlowCycle, undeclaredFlowCycleMessage and the types FlowCycleNode, FlowCycleEdge, FlowUndeclaredCycle; metadata-protocol exports FLOW_UNDECLARED_CYCLE and findFlowCycleGaps; @objectstack/metadata-core/testing exports FLOW_CYCLE_CORPUS and FlowCycleCorpusCase. No metadata key is added.",
"options": ["A — land as named", "B — rename before landing (name the spelling)"],
"recommendation": "A, because the rule id follows the gate's kebab-case ids and the exports follow metadata-core's shared-judge pattern (extension-field-patch-targets)."
}
],
"out_of_scope_findings": [
"class: a · reach: the publish door's verdict function, measured on the pure gate at 8a384327: evaluateRuntimeAuthoringGate({type:'flow', state:'active'}) on a flow whose loop body is a → b → a returns error null, while the engine's validateControlFlow (run by canonicalizeFlow at registration) throws \"loop 'lp' body: region has no entry node … region contains a cycle\". So a malformed or cyclic loop / parallel / try_catch region reads valid and publishes, then never registers. The HTTP door was not measured. This is the gate-side twin of #28893 (the CLI side) and the same family as this card and #29051. · dedupe: flow region cycle publish gate validateControlFlow; loop body cycle draft valid; parallel one branch publish registration refused",
"carrier: seat 2 (#29051 serial card, or a sibling of it) · noted in the PR's Acceptance notes"
]
}
防 AI 犯错:A 下 AI 写错回环会在保存读取时得到响亮的 valid:false 与定位到边的提示,而不是静默不跑。
创业阶段不扩散:A 新增一个规则 id 与两个导出,无新配置面;B 多一条包依赖。
os-decision-facets
① 项目长远合理性: A 引擎与发布门共用唯一判定;B 再统一 CLI 但加依赖边。
② 实际业务拉动: 卡上实测流程发布成功却永不运行。
③ 防 AI 犯错: A 保存即响亮 valid:false 并定位到边。
④ 创业阶段不扩散: A 一个 id 两个导出,无新配置面。
Prior rulings read: "flow-undeclared-cycle", "findUndeclaredFlowCycle", "AUTHORING_RULES runtime gate" → 0 rulings; thread: none
The new public names land as filed: rule id flow-undeclared-cycle (the gate's kebab-case family); @objectstack/metadata-core exports findUndeclaredFlowCycle, undeclaredFlowCycleMessage, FlowCycleNode, FlowCycleEdge, FlowUndeclaredCycle; @objectstack/metadata-protocol exports FLOW_UNDECLARED_CYCLE, findFlowCycleGaps; @objectstack/metadata-core/testing exports FLOW_CYCLE_CORPUS, FlowCycleCorpusCase.
Ruling 6580265584: letter A, the maintainer's 「同意」 at 2026-11-02T14:54Z. The names land as filed, with zero rework. The seat ACCEPTs PR #29226 and enqueues it in this act, and lands it.
State: needs-user-decision → pm:dispatched, assigned to os-steve, recorded by this comment (the ruling's exit stroke).
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: studio-authoring.flow-edge-type-authoring
Triage:
domain:server· p2 ·pm:queue· 2026-11-02T07:20Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt
Claim: PM loop round 1 · card thread · 2026-11-02T08:31Z
Session:
session_01ADHkmQxwoGb5fSnrFkpxmt(the seat); the card thread issession_01ADHkmQvBMGvejv3APX96euAccount:
os-steve(the seat's linked user; the card's assignee)Branch:
claude/project-thread-g4dqyl(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#2Ruling-ref: the maintainer, verbatim: 「开 server 2、3、5 号席位, 前任已经下班,接手所有开发,包括跟进之前未完成的开发落地。」 Card: none from the maintainer on this card; triage graded p2 (6571456936). Sibling #29051 (p3, same seam on the build side) is held by seat 2 serially behind this card (agreed with seat 3 in the project); server seat 5 handed this card to seat 2 in the project.
File surface:
packages/metadata-protocol/src/runtime-authoring-gate.ts(cycle rule on the draft verdict and the publish gate);detectCyclesinpackages/services/service-automation/src/flow-registration-checks.tsmoved to or re-exported from a package both can depend on (⛔ notpackages/spec/**: needs_decision if spec is the only home); their tests; a dogfood case; changeset.Container & model:
M,mode:thread,model: opus(TIER_DEFAULT;--tiernames no path mandate: PM's call),effort: highClause-②: no (narrowing) (expected: a flow with an undeclared cycle that published now refuses at draft and publish; the dev measures and declares)
Metadata-protocol: per AGENTS.md, measured by the dev (likely none or narrowed)
Responsibility: the code is the runtime authoring gate of
@objectstack/metadata-protocoland the engine registration check of@objectstack/service-automation· the platform path is a loop flow withouttype: 'back'that reads valid, publishes, then is refused registration and never runs · reached by every API, MCP or package author of loop flowsThread-read: 6571456936
Premise: holds (the named seam read on
origin/mainby the seat this act; the dev re-measures red first)Serial constraints cleared: none — no open claim on a
domain:serverpm:dispatchedcard names these files (read this act)Resume-from: none — fresh from
origin/mainState:
pm:queue→pm:dispatched, assigned toos-steve, recorded by this claim.objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-steve · session_01ADHkmQvBMGvejv3APX96eu
os-dev-report
{ "issue": 29025, "status": "done", "branch": "claude/project-thread-g4dqyl", "pr": "https://github.com/objectstack-ai/objectstack/pull/29226", "session": "session_01ADHkmQvBMGvejv3APX96eu", "premise_still_valid": true, "summary": "The engine's top-level cycle walk moved unedited from service-automation to metadata-core as findUndeclaredFlowCycle (plus undeclaredFlowCycleMessage and the closing-edge index). AutomationEngine.detectCycles now throws its message, and the runtime authoring gate gained a gate-local rule flow-undeclared-cycle (error, located on flows[0].edges[N], message = the engine's sentence). The draft verdict reads that gate, so a loop whose closing edge is not typed back now reads valid:false, and every publish door refuses it (publish-drafts outcome refused). A loop with any edge typed back still publishes and registers. Parity is pinned by one shared corpus (FLOW_CYCLE_CORPUS on @objectstack/metadata-core/testing, 4 refused / 4 accepted), run through the judge, registerFlow and the gate.", "tests": "Red first on origin/main fea2dbb7 (gate fix removed): protocol.draft-publish-verdict 'flow — [flow-undeclared-cycle]' FAILED, received {code: undefined, status: undefined}. Green at head: metadata-core flow-cycle.test 11/11; metadata-protocol runtime-authoring-gate.flow-cycle + draft-publish-verdict 44/44; service-automation flow-cycle-registration + engine cycle tests 13 passed. Whole-package runs (lock, typecheck then test, VERDICT 0): metadata-core 23 files/896 tests, metadata-protocol 298 files (4 skipped)/22074 passed (26 skipped), service-automation 258 files/3317 tests. Ablation 1 (src, ablation-replace anchor 1->0, restored blob == HEAD): flowCycleGaps dropped from localIssues -> 5 failed/39 passed (4 refused corpus cases + the draft-verdict card case). Ablation 2 (metadata-core rebuilt, ablation-dist-preflight marker present in dist, absent after restore+rebuild): back-edge exemption removed -> gate+draft-verdict 5 failed (3 back controls, two-loops case, draft-verdict back control), engine corpus 4 failed. The first ablation-2 attempt used a block-comment marker that esbuild strips; preflight said ABSENT, so that reading was discarded and redone. Dogfood case alone (verbose): 2/2 passed.", "gates": "Head 8a384327 (origin/main merged at 694a118d). dispatch-gates --commands: 66 families, all exit 0 after re-runs; --ran with `:: exit N` records: '66 derived famil(ies) accounted for — 66 run, 0 NOT-MEASURED (a DERIVED zero)'. Re-runs: check-adr-0087-registration exit 1 on the first changeset text (a FROM → TO label read as a migration prescription), exit 0 after the rewrite; check-plugin-teardown-shape --self-test exit 3 (fixture commit outside the shallow clone), exit 0 after fetching 621a4876; check:dual-build-cjs-loads exit 3 (7 packages outside the dogfood closure unbuilt), exit 0 after building them. The changeset-reading gates were re-run after the changeset commit, all exit 0. eslint --no-inline-config on every changed .ts: exit 0. CI check-runs on PR #29226: in_progress at report time.", "dogfood_battery": "8a384327 · os-verify-lock: VERDICT command-exit 0 (held 24m29s) · Test Files 388 passed | 3 skipped (391) · Tests 3206 passed | 28 skipped (3234) · no failure", "line_budget": "12 files, +690 / -53 (code: flow-cycle.ts +128, runtime-authoring-gate.ts +77/-1, flow-registration-checks.ts -52 net; the rest are tests, the corpus, the dogfood case and the changeset)", "files_changed": [ ".changeset/29025-flow-undeclared-cycle-publish.md", "packages/metadata-core/src/flow-cycle.ts", "packages/metadata-core/src/flow-cycle-corpus.ts", "packages/metadata-core/src/flow-cycle.test.ts", "packages/metadata-core/src/index.ts", "packages/metadata-core/src/testing.ts", "packages/metadata-protocol/src/runtime-authoring-gate.ts", "packages/metadata-protocol/src/runtime-authoring-gate.flow-cycle.test.ts", "packages/metadata-protocol/src/protocol.draft-publish-verdict.test.ts", "packages/services/service-automation/src/flow-registration-checks.ts", "packages/services/service-automation/src/flow-cycle-registration.test.ts", "packages/qa/dogfood/test/flow-undeclared-cycle-publish.dogfood.test.ts" ], "deviations": [ "Changeset: the order asks for a FROM → TO banner; check-adr-0087-registration refuses a FROM → TO label beside not-required (no-migration-prescription). The banner carries Before / Now bullets and a 'The remedy.' paragraph (the 25579 changeset's accepted shape). No ledger entry, because no key moved and the refused body never registered.", "Lint: no touched package has a lint script, so eslint --no-inline-config ran on the changed files; the repo-wide pnpm lint is CI's.", "origin/main moved 15 commits after the 694a118d merge, while the dogfood battery ran. None touches these files (dogfood test files only, other tests). Not re-merged before pr_create, so the battery reading stays on the final head.", "Rule id and placement: gate-local (beside the deployment-fact rules) rather than AUTHORING_RULES. Reason in open_questions 1." ], "mcp_calls": "3 — mcp__github__issue_read (get_comments x2, get x1); no write tool", "api_writes": "2 — pr_create via with-fleet.sh --via dispatch (PR #29226, 9939 bytes sent = stored); this os-dev-report comment via post-stamped.mjs --comment=29025", "open_questions": [ { "question": "Where should the cycle rule live: gate-local (this PR) or an AUTHORING_RULES entry in @objectstack/lint, which would also put it on os validate / os build (#29051)?", "options": [ "A — keep it gate-local here; #29051 adds one findUndeclaredFlowCycle call in packages/cli/src/utils/flow-region-shape.ts (cli already depends on metadata-core), beside its validateControlFlow call", "B — move it to AUTHORING_RULES (CLI_AND_RUNTIME, runtimeTypes ['flow']): needs a new @objectstack/lint -> @objectstack/metadata-core dependency (pure, spec-only), folds #29051, and touches lint's wiring/explanation tables" ], "recommendation": "A, because it ships the narrowing on the card's file surface with no new package edge. #29051 is then a one-file follow-up on the same judge. B is the cleaner long-term home only if the lint registry should own every flow-registration check; that is a lane-wide choice, not this card's." }, { "question": "New public names, for the maintainer's word before landing: the rule id flow-undeclared-cycle (a wire value in issues[].rule and _diagnostics errors[].code); metadata-core exports findUndeclaredFlowCycle, undeclaredFlowCycleMessage and the types FlowCycleNode, FlowCycleEdge, FlowUndeclaredCycle; metadata-protocol exports FLOW_UNDECLARED_CYCLE and findFlowCycleGaps; @objectstack/metadata-core/testing exports FLOW_CYCLE_CORPUS and FlowCycleCorpusCase. No metadata key is added.", "options": ["A — land as named", "B — rename before landing (name the spelling)"], "recommendation": "A, because the rule id follows the gate's kebab-case ids and the exports follow metadata-core's shared-judge pattern (extension-field-patch-targets)." } ], "out_of_scope_findings": [ "class: a · reach: the publish door's verdict function, measured on the pure gate at 8a384327: evaluateRuntimeAuthoringGate({type:'flow', state:'active'}) on a flow whose loop body is a → b → a returns error null, while the engine's validateControlFlow (run by canonicalizeFlow at registration) throws \"loop 'lp' body: region has no entry node … region contains a cycle\". So a malformed or cyclic loop / parallel / try_catch region reads valid and publishes, then never registers. The HTTP door was not measured. This is the gate-side twin of #28893 (the CLI side) and the same family as this card and #29051. · dedupe: flow region cycle publish gate validateControlFlow; loop body cycle draft valid; parallel one branch publish registration refused", "carrier: seat 2 (#29051 serial card, or a sibling of it) · noted in the PR's Acceptance notes" ] }Stamped 2026-11-02T09:50Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt
决策分析(席位 domain:server#2)· 2026-11-02T14:37Z
一句话问题: 流程里有一条回环没标成「回边」时,发布照样成功、引擎却不注册,流程永远不跑。修复 PR #29226 已就绪(复审 PASS),但它给平台新增两个公开名字,需维护者批准名字和规则放哪。
背景: 卡 #29025 由席 2 派发,PR #29226(草稿,head
8a384327)交付;合约复审 PASS。PR 新增:① 发布门规则 idflow-undeclared-cycle(出现在_diagnostics与 422 答复里);②@objectstack/metadata-core新根导出findUndeclaredFlowCycle/undeclaredFlowCycleMessage(引擎与发布门共用的唯一判定)。席位此前只在项目线程里放了决策卡,未在卡上落分析、未打needs-user-decision,总监收件箱因此看不到——本评论补上。Governing text: 新公开名字须维护者批准(AGENTS.md 公开面规则;PR 正文「New public names」)。门内规则命名惯例:
runtime-authoring-gate.ts现有 id 全为短横线小写(hierarchy-scope-resolver、public-form-intake-unavailable、validation-rule-copy-changed…)。检索:grep -oE "'[a-z]+(-[a-z]+){2,}'" packages/metadata-protocol/src/runtime-authoring-gate.ts。协议声明:
Clause-②: no (narrowing),Metadata-protocol: narrowed;metadata-protocol / metadata-core minor,service-automation patch,BREAKING 横幅。不改packages/spec。前提(带复查命令): PR 仍为草稿且干净 —
gh api repos/objectstack-ai/objectstack/pulls/29226 --jq '.draft,.mergeable_state';两名字只出现在本 PR —git grep -n "flow-undeclared-cycle\|findUndeclaredFlowCycle" origin/main为 0 行。flow-undeclared-cycle,判定放 metadata-core 由发布门调用@objectstack/lint的AUTHORING_RULES表业务含义: A = 按现有门内规则的命名和放置惯例上线;B = 把它变成作者工具链里的通用规则,代价是一次架构依赖调整;C = 只换拼写。
四维(业务立场):
valid:false与定位到边的提示,而不是静默不跑。os-decision-facets
① 项目长远合理性: A 引擎与发布门共用唯一判定;B 再统一 CLI 但加依赖边。
② 实际业务拉动: 卡上实测流程发布成功却永不运行。
③ 防 AI 犯错: A 保存即响亮 valid:false 并定位到边。
④ 创业阶段不扩散: A 一个 id 两个导出,无新配置面。
Prior rulings read: "flow-undeclared-cycle", "findUndeclaredFlowCycle", "AUTHORING_RULES runtime gate" → 0 rulings; thread: none
推荐:A。 只看①选 A;②③④ 是否翻转:否。置信缺口:未比较其他低代码平台对该类诊断 id 的拼写;B 的 CLI 统一由 #29051 以一次调用补齐,不需本 PR 返工。
裁后执行: A ⇒ 席 2 ACCEPT 并入队落地,随后派 #29051 与 #29233(同文件串行)。B ⇒ 退回开发返工。C ⇒ 按给出拼写改名重跑门禁。
相关: PR #29226 · #29051 · #29233 · 席 2 项目线程决策卡(同一问题,裁决以本卡为准)。
State:
pm:dispatched→needs-user-decision, recorded by this comment.Generated by Claude Code
zhuangjianguo commentedon Nov 2, 2026
Brief-claimed-by: session_01QsR2jV5sBSzCYNEDgpYLJw · batch #458 · 2026-11-02T14:50Z
Generated by Claude Code
zhuangjianguo commentedon Nov 2, 2026
Ruling: batch #458 item 3 · letter A · maintainer 「同意」 2026-11-02T14:54Z
director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw
What the ruling binds
runtime-authoring-gate.ts(PR fix(metadata)!: refuse a flow with an undeclared cycle at publish, as the engine refuses it at registration #29226 as it stands); it does not move to@objectstack/lint'sAUTHORING_RULES, which would add a lint → metadata-core dependency edge for a check automation: a flow with a top-level cycle passes os validate and build; detectCycles runs only at registration and has no author-time home #29051 can add with one call.flow-undeclared-cycle(the gate's kebab-case family);@objectstack/metadata-coreexportsfindUndeclaredFlowCycle,undeclaredFlowCycleMessage,FlowCycleNode,FlowCycleEdge,FlowUndeclaredCycle;@objectstack/metadata-protocolexportsFLOW_UNDECLARED_CYCLE,findFlowCycleGaps;@objectstack/metadata-core/testingexportsFLOW_CYCLE_CORPUS,FlowCycleCorpusCase.Workload of the letter
8a384327, contract review PASS 6574280356, 37 green, clean). BREAKING banner,Clause-②: no (narrowing),Metadata-protocol: narrowed, as the seat's analysis states.findUndeclaredFlowCyclecall inflow-region-shape.ts), then metadata: a flow whose loop / parallel / try_catch region is malformed or cyclic reads valid as a draft and publishes, then the engine refuses to register it #29233, both serial on the same files.Execution
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt
Ruling received · 2026-11-02T15:00Z
Ruling
6580265584: letter A, the maintainer's 「同意」 at 2026-11-02T14:54Z. The names land as filed, with zero rework. The seat ACCEPTs PR #29226 and enqueues it in this act, and lands it.State:
needs-user-decision→pm:dispatched, assigned toos-steve, recorded by this comment (the ruling's exit stroke).Generated by Claude Code