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
FlowSchema accepts a flow whose edges[] declares the same id twice — measured with a control, and it let a duplicate id ship on green CI #14964
Filed by the repo:hotcrm execution seat (session session_019hUuCQStzXGMFSX4dzww5t). ⛔ Observation-level, unassigned, no domain:* / type — routing and grading belong to central triage. Filed here rather than pinned locally, per the hotcrm rulebook's Scope rule 3: "Lint, validation, gates and diagnostics belong to the platform, uniformly. A drift-class or validation-class gap you find is a platform problem and goes upstream."
Surfaced by hotcrm#1558 / PR #1571, which fixed the instance. This card is the mechanism that allowed it.
The gap
FlowSchema.safeParse()accepts a flow whose edges[] contains two edges carrying the same id.
Probed directly against @objectstack/spec 17.2.0 with a minimal two-edge flow, both edges id: 'dup':
success: true
parsed edge ids: ['dup', 'dup']
⭐ With a control, so the pass is not vacuous: the same schema instance rejects a flow whose edge carries an invalid type. ⇒ the schema was live and validating at the moment it accepted the duplicate — this is a missing rule, not a disabled validator.
Why it matters beyond tidiness
⚠️It let a real duplicate ship and survive two releases of green CI.hotcrm's src/flows/lead-conversion.flow.ts declared id: 'e20' on two different edges. Nothing caught it — not pnpm validate, not the 3,300-test suite, not review — because nothing checks the property.
The instance was inert in that app only by luck of implementation: traversal filters out-edges by source and never by id. ⇒ the collision is invisible until something keys on edge ids, and then it is silently wrong rather than loudly broken. Candidates: a BPMN export, a flow designer, a flow-diff, or any future traversal that dedupes by id.
⚠️ And the id space is authored by hand. hotcrm's own file carries a comment explaining that surviving edges keep the ids they have always had — a convention that only means anything if ids are unique — and the duplicate arose from an edge reorder that reassigned one. The next author picking a "free" id from the sequence has no way to know it is taken.
Suggested shape
A uniqueness refinement on edges[] in the flow schema, so this fails at parse time for every ObjectStack app rather than per-app. ⛔ The seat is not proposing an implementation.
⚠️Worth deciding deliberately: whether this is an error or a warning. An error is correct on the merits but would reject flows that parse today, so it wants a release note at minimum — hotcrm had one such flow and may not be alone.
Reproduction
Minimal, against @objectstack/spec 17.2.0: build a flow with two edges differing only in source/target, both id: 'dup', and call FlowSchema.safeParse(). It returns success: true. Swap one edge's type for an invalid value and the same call returns success: false — that is the control.
⚠️ One adjacent measurement, in case it shares a cause
The hotcrm dev's sweep across that app's flows found 168 edges and exactly one flow with a duplicate id — so this is a latent trap rather than a widespread one, at least in that app. ⇒ if the refinement lands as an error, the blast radius may be small; ⛔ but one app is not a survey, and the seat has not measured any other app.
Triage (R+146): needs-user-decision · domain:spec · priority:p2 · Bug. Lands in the flow schema under packages/spec/src/automation/**.
Escalated because the repair narrows the accept set of a published schema: a uniqueness refinement at error level rejects flows that parse today. That is a protocol change with a migration cost, ⛔ not a declared-to-enforced restoration — nothing currently declares edge ids unique, so there is no contract being restored.
⭐ The probe carries its own control, which is why this is a measured gap rather than a guess: the sameFlowSchema instance rejects a flow whose edge carries an invalid type while accepting two edges with id: 'dup'. ⇒ The schema was live and validating at the moment it accepted the duplicate — a missing rule, not a disabled validator.
① 项目长远合理性(权重 ≥50%,领起推荐) —— edge id 空间是手写的,而「id 唯一」是所有读它的东西的隐含前提。hotcrm 自己的文件里那句注释 —— 幸存的边保留它们一直以来的 id —— 只有在 id 唯一时才有意义,而重排边时重新分配了一个,前提就破了。⇒ 一个被普遍假设、却无人强制的不变量,正是「声明即强制」要关掉的形状。①指向加约束。
② 实际业务拉动 —— 具名且已发生。 重复 id 真的落地并活过两次绿色 CI:pnpm validate 没发现、3,300 条测试没发现、review 没发现,因为没有任何东西检查这条属性。⇒ 有实测拉动。
③ 防 AI 犯错 —— 决定性,而且是最坏的失败形态。 该实例在那个 app 里只是碰巧无害 —— 遍历按 source 过滤出边,从不按 id。⇒ 冲突在有东西开始按 id 索引之前是不可见的,那之后是静默错误而不是响亮失败。 候选场景卡里都点了:BPMN 导出、流程设计器、flow-diff,或任何按 id 去重的遍历。而下一个从序列里挑一个「空闲」id 的作者,⛔ 没有任何办法知道它已被占用。
Maintainer ruling recorded — A: FlowSchema gains a uniqueness refinement on edges[].id at error level, preceded by a repo-wide duplicate-id census that decides whether the changeset carries a release note or a migration line
Director seat, summon #14, session session_01LsEjuNMPitCHwEfYftZ1um (GitHub os-warren), 2026-09-05. Provenance: maintainer, live PM chat, decision batch #40 (item 1, presented with the recommendation A), verbatim reply 「同意」. Premise: the card's probe on @objectstack/spec 17.2.0 with its control (the same schema instance rejects an invalid edge type while accepting two edges with id: 'dup') and triage's facets 5542853873; the real instance hotcrm#1558 / PR #1571 survived two releases of green CI.
Ruled: A. A superRefine on the flow's edges[] refuses a duplicate id at parse time with a message naming the id and both edge positions; error, not warning. Not taken: B (warning with a dated escalation — the card's whole evidence is a warning-level reality that nobody checked; a non-blocking hint swaps "nobody checks" for "nobody reads"), C (leave it).
Why (① ≥50%): the edge-id space is hand-authored and every reader assumes uniqueness while nothing enforces it — hotcrm's own comment ("surviving edges keep their ids") only means anything if ids are unique. ② already happened; ③ the collision is invisible until something keys on ids (BPMN export, designer, flow-diff) and then silently wrong; ④ a narrowing, no capability added.
Execution:domain:spec lane, S. Zone 2 first, before the refinement: a duplicate-edge-id census over every flow in this repo (packages/**, examples/**, qa/**) and, where the seat can read them, the pinned objectui / hotcrm trees — reported with a lit control; the count decides the changeset's remedy paragraph (a release note when zero in-repo instances remain; a migration sentence naming the files when any do). Reproduce the card's probe and control on today's origin/main before editing (the probe ran on 17.2.0). Pins: the duplicate refused, the control still refused, a unique-id flow still accepted, and the message shape. Clause-②: yes (a published schema's accept set narrows) ⇒ needs:contract-review on the PR. Changeset: @objectstack/specminor with a BREAKING banner (flows with duplicate edge ids are now refused at parse) and an adr-0087 disposition (no rename/retirement; the remedy is renumbering the duplicate, which is authoring intent — no-migration-prescription unless the census finds an in-repo instance to name).
State transition, same stroke: needs-user-decision → pm:queue. priority:p2 · domain:spec · type Bug unchanged. Ledger: director seat post #12708, batch #40.
Claim: PM loop round R1
Session: session_01M59rPZZFzqhfMUPFqqZTkf
Branch: claude/issue-14964-flow-edge-id-uniqueness
Worktree: objectstack-issue-14964
Domain: domain:spec
File surface: packages/spec/src/automation/flow.zod.ts (FlowSchema at :586, FlowEdgeSchema at :503 with id at :521, the existing superRefine precedent at :849, all on origin/main7bf96cfd0) + flow.test.ts · the generated content/docs/references/automation/flow.mdx only if a describe moves (regenerated, never hand-edited) · one changeset with the BREAKING banner and the ADR-0087 disposition · the Zone-2 duplicate-edge-id census (read-only) over packages/**, examples/**, qa/** and the pinned objectui / hotcrm trees where readable. Stop on breach; explain in the report.
Container & model: S, mode:subagent, model: fable (claude-fable-5-1) — node scripts/pm/dispatch-gates.mjs --tier packages/spec/src/automation/flow.zod.ts run 2026-09-05T02:48Z against the tree at b4b37e59: "no path-derived mandate", "Clause ② SUSPECT surface" hint; tier set from card CONTENT — the ruling narrows a published schema's accept set (flows with duplicate edge ids are refused at parse) ⇒ clause ② ⇒ CONTRACT_REVIEW_TIER (claude-fable-5-1, re-derived from origin/main:scripts/pm/dispatch-gates.mjs at 01:44Z). Maintainer ruling A recorded by the director (5548575136, batch #40, 「同意」).
Clause-②: yes
Serial constraints cleared: packages/spec/src/automation/flow.zod.ts has no in-flight claim at 02:48Z; queued sibling #14945 (ruled 2′ — the end node gains outcome: 'refused') also writes this file ⇒ serial, not folded (different defect, different fix): #14964 goes first (type Bug, older), #14945 is dispatched only after this PR lands. #15617 lives in automation/execution.zod.ts (different file). packages/spec/src/migrations/registry.ts is HELD by draft PR #15626 — a parse-time refinement retires nothing, so no registry entry is expected; the ADR-0087 disposition rides in the changeset (no-migration-prescription unless the census names an in-repo instance); if a gate demands a registry entry the dev stops and reports. Same-round in-flight #14079 (data/filter-text-conformance.ts + drivers) and #14882 (system/i18n-resolver.ts) are source-disjoint; the generated corpus lands serially via scripts/pm/os-regen-merge.sh. Landing-window PR #15672 (#14954, automation/control-flow.zod.ts + authorable-surface/automation.json) shares the automation/ generated shard only — if this card regenerates authorable-surface/automation.json, it merges main after #15672 lands before enqueueing.
Claim (os-dev): implementing on the PM-dispatched branch, round R1.
Session: session_01M59rPZZFzqhfMUPFqqZTkf
Branch: claude/issue-14964-flow-edge-id-uniqueness (pushed empty as the landing marker; base a55efc6c1)
Worktree: objectstack-issue-14964
Scope: ruling A from 5548575136 — a superRefine on FlowSchema.edges[] refusing a duplicate edge id at parse time (error level), preceded by the Zone-2 duplicate-edge-id census with a lit control; probe + control reproduced on today's origin/main before editing. File surface: packages/spec/src/automation/flow.zod.ts + flow.test.ts + one @objectstack/spec minor changeset with the BREAKING banner and the ADR-0087 disposition. Assignee untouched.
{
"issue": 14964,
"status": "done",
"branch": "claude/issue-14964-flow-edge-id-uniqueness",
"pr": "https://github.com/objectstack-ai/objectstack/pull/15716",
"premise_still_valid": true,
"head": "260c4d986",
"base": "a55efc6c1 (origin/main at worktree creation; the PM claim cited 7bf96cfd0 — landmarks FlowEdgeSchema :503, id :521, FlowSchema :586, superRefine precedent :849 unchanged on a55efc6c1)",
"summary": "Ruling A implemented: FlowSchema is strictObject(...).superRefine(...) in packages/spec/src/automation/flow.zod.ts — each later occurrence of an already-declared edges[].id raises one custom issue at [edges, j, id] naming the id and both positions (Zod 4.4.3 superRefine returns the same ZodObject, so Flow/FlowParsed/defineFlow and every importer keep their types; no describe moved, no generated follower). Card probe + control reproduced on origin/main a55efc6c1 before editing (duplicate accepted with parsed ids [dup,dup]; invalid edge type refused at edges.1.type; unique accepted). Zone-2 census with a lit control: 0 in-repo instances, so the changeset (@objectstack/spec minor, BREAKING banner, adr-0087 not-required (no-migration-prescription)) carries the release-note remedy; the ADR-0087 gate first refused my FROM-to-TO heading as a prescription — dropped, gate green. Draft PR #15716 with Fixes #14964; needs:contract-review hung on PR and card (REST additive POST, compare read-back green on both). No registry entry written; execution.zod.ts and the end-node config untouched.",
"files_changed": [
"packages/spec/src/automation/flow.zod.ts (+27)",
"packages/spec/src/automation/flow.test.ts (+105, 6 pins)",
".changeset/flow-edge-id-uniqueness.md (new)"
],
"census": {
"method": "AST scanner (TypeScript compiler API for TS/JS; structural walk for YAML/JSON) over every edges: [...] array literal — property assignment, edges variable, inside defineFlow(...), as-const/parenthesised unwrapped; undecidable elements COUNTED (computed id, spread, non-object)",
"control": "synthetic root: 3 TS duplicate shapes + YAML + JSON duplicates all reported with positions (5/5); negative file 0 with its spread and computed id counted undecided",
"objectstack_a55efc6c1": "5703 code + 366 data files; 776 edges arrays; 1098 edges; 1052 literal ids; 8 computed; 8 spread; 30 non-object; 24 arrays with an undecidable element (all test fixtures or bpmn-mapping computed spreads, none under examples/**); 0 duplicate arrays; root qa/ does not exist (packages/qa/** covered under packages/**)",
"dynamic": "every example flow file (app-crm, app-showcase, app-todo: 7 files, 50 distinct flow exports / 73 rows with re-exports) parsed through the built FlowSchema from dist/automation: 0 refused; control refused at edges.1.id",
"objectui_pin_a472b07": "read-only, byte-equal to .objectui-sha: 89 arrays / 104 edges, 17 literal, 75 computed (React-Flow state), 0 duplicate literal ids; computed 75 not statically decidable",
"hotcrm": "NOT MEASURED (not checked out; its one instance fixed in hotcrm#1571 per the card)"
},
"tests": "All heavy runs via scripts/pm/os-verify-lock.sh (slot names set before first attempt; 99 never returned). spec build: VERDICT command-exit 0 (133s). flow.test.ts: Test Files 1 passed, Tests 102 passed; spec typecheck (tsc + check:scripts-typecheck + check:test-typecheck) OK — VERDICT command-exit 0 (54s). Whole spec suite: Test Files 472 passed, Tests 12692 passed — VERDICT command-exit 0 (342s). Consumers (prefix direction ...@objectstack/spec = downstream, closures built with turbo --concurrency=2): lint Test Files 96 passed / Tests 3006 passed 5 skipped + core Test Files 49 passed / Tests 1190 passed — VERDICT command-exit 0 (257s); service-automation Test Files 108 passed / Tests 1294 passed — VERDICT command-exit 0 (183s). Ablation (fix committed first; BASE text of flow.zod.ts restored under an EXIT/INT/TERM trap by absolute path; the test imports ./flow.zod from src so no dist leg): mutation proven on disk — marker Duplicate edge id 1 to 0, superRefine 3 to 2, tree blob abc5dc7 vs HEAD a0d5cf2; expected direction four refusal pins red, control + unique-id green: observed Tests 4 failed | 98 passed (102), exactly those four; restore exit 0, git diff HEAD empty, status empty, blob back to a0d5cf2, marker 1. Gates: family derived with node scripts/pm/dispatch-gates.mjs (no paths) at a4da73eec — 121 commands, 94 exit 0; union re-run at final commit 260c4d986: check:generated all 15 artifacts up to date (api-surface, authorable-surface, docs, strictness-ledger, liveness incl.), check:empty-state, check:yaml-examples, check:skill-examples (257 prose examples, 3 surfaces), check:error-code-provenance, check:exported-any, check:dual-source-exports, check:entry-nameability, check:llms-txt, check:variant-docs, check:browser-reachable-entries, lint check:doc-formula-expressions, check:nul-bytes (7584 files, no control bytes), check:cross-package-test-inputs, check:doc-authoring, check:docs-audit-scope, check:changeset-gate-self-tests, check-empty-changeset --base a55efc6c1, check-changeset-no-major --base a55efc6c1, check-adr-0087-registration --base a55efc6c1 (1 declared-breaking changeset, each carrying an ADR-0087 disposition), check:pm-half-states, check:objectui-changeset, check-keyed-text-bounds, check:spec-parsed-alias, check:test-source-alias, check:merge-driver, reference-carrier-shape — all exit 0 with their own verdict lines (per-gate table in PR comment 5549213320). Six dist-reading gates first answered STALE at 260c4d986 (ablation restore gave flow.zod.ts a newer mtime than dist) — prerequisite refusal, not a reading; spec rebuilt (same .build-input-hash 7c08850e) and re-run green; check:skill-examples additionally needed the client-react closure built (34 tasks). check:partof-closing-keyword on the PR body: no contradiction.",
"gates_not_measured": [
"check:react-declaration-parity — external input (objectui sdui.manifest.json), exits 1 by design locally",
"check:published-readme-exports — needs a full-repo build; reads READMEs this diff does not touch",
"24 CI-step spellings in the derivation (RUNNER_TEMP / matrix / job labels: ESLint, workspace type-check, shard attestations, claim-path, label steps) — CI owns them",
"example apps validate/test scripts — declared narrowing (59-63-package closures); their flows parsed directly instead"
],
"deviations": [
"REST /search/issues answered 403 while every other repo-scoped REST read/write passed (issue, comments, labels, issues POST, pulls POST all 200/201) — dedup switched to one targeted MCC search_issues plus one control query that hit #14964",
"the node-id finding carries the finding label because the dispatch named it as such; the general rule would leave a concrete defect unlabelled — PM may re-triage",
"PR body footer: both the repo session-URL form and the harness trailer were written; the platform stored the body byte-equal except for moving the rule+footer block to the tail",
"origin/main moved during the run (dispatch-gates STALE TREE notice, 3 derivation inputs changed); branch left unmerged for the PM landing path — no generated artifact regenerated, so the authorable-surface/automation.json shard named in the claim is untouched",
"the ruling root qa/** does not exist at the repo root; packages/qa/** was scanned under packages/**"
],
"labels": {
"pr_15716": "documentation,needs:contract-review,size/m,tests,tooling (read back)",
"issue_14964": "domain:spec,needs:contract-review,pm:dispatched,priority:p2 (read back)"
},
"cleanup": "worktree /home/user/objectstack-issue-14964 removed without force after remote == local at 260c4d986 (node_modules removed first); no dev server or background monitor started",
"mcp_calls": "2 — both search_issues (one targeted dedup for the node-id finding, one control query that hit #14964); all other GitHub reads and writes via REST",
"open_questions": [],
"out_of_scope_findings": [
"filed as #15713: FlowSchema accepts a flow whose top-level nodes[].id repeats — measured with two lit controls (missing required label; unknown node key — an invalid node type is not a control, the vocabulary is open under ADR-0018); only region nodes are checked by analyzeRegion at registerFlow; unassigned, finding label; dedup: MCP search_issues found no open card (hits: #14964 itself, closed #9889, closed #4027)"
]
}
Review: ACCEPT · Clause-② contract review: PASS — domain:spec seat, session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T04:11Z, PR #15716 at head 260c4d986 (read 2026-09-05T04:07Z; base main, draft, NOT governed — predicate 0 of 3 paths at 2026-09-05T04:08Z).
Tier fuse read before this review: get_session at 2026-09-05T03:50Z — session_context.model and last_served_model both claude-fable-5-1 = CONTRACT_REVIEW_TIER.
Scope: 3 files — flow.zod.ts (+27), flow.test.ts (+105), one changeset. No content/docs/releases/ change, nothing unrelated; packages/spec/src/migrations/registry.ts untouched (held), no generated artifact moved (check:generated all 15 up to date — a refinement adds no key, so no authorable-surface / docs follower).
Ruling A executed as ruled (5548575136): superRefine on edges[], error level (a custom issue, no warning path, no opt-out), message names the id and BOTH positions, anchored on the later edge; the probe and its control reproduced on origin/maina55efc6c1 BEFORE editing; pins: duplicate refused · control still refused (invalid_value at edges.1.type, no duplicate issue beside it) · unique-id flow accepted in authored order · message shape via formatZodError · one issue per later occurrence naming the FIRST declaration · defineFlow refuses with the same anchored issue.
Zone-2 census, lit control: AST scanner over 5,703 code + 366 data files (776 edges[] arrays, 1,098 edges), undecidable elements COUNTED not dropped; control root with five planted duplicates found 5/5; 0 in-repo duplicates; dynamic reading: every shipped example flow parsed through the built schema, 0 refused, control refused; pinned objectui tree at a472b07: 0 literal duplicates (75 computed React-Flow ids not statically decidable — declared). The ruling's qa/** root does not exist at the repo root (packages/qa/** was inside packages/**) — a deviation reported, not hidden. hotcrm NOT MEASURED (not checked out; its one instance was renumbered in hotcrm PR feat(service-ai): extract AI metadata authoring into cloud-only AI Studio #1571 per the card) — accepted as the census's declared gap; the changeset's remedy paragraph is a release note, as the ruling says for zero in-repo instances.
Changeset: @objectstack/specminor with the BREAKING banner and the adr-0087 disposition not-required (no-migration-prescription) — the ruling's exact shape; check-adr-0087-registration --base a55efc6c1 and check-changeset-no-major green per the dev, Lint & Repo Gates on this head is the reading that counts.
Tests: pinned to 260c4d986 = the PR head; real commands and counts (flow.test 102 passed; whole spec 12,692 passed; consumers downstream with closures built: lint 3,006, core 1,190, service-automation 1,294; typecheck OK). Ablation trap-restored with on-disk proof (marker 1→0, blob abc5dc7 ≠ a0d5cf2): exactly the four refusal pins red, control + unique green; restore verified by blob equality. Six dist-reading gates first answered STALE after the restore's mtime bump — a prerequisite refusal, rebuilt and re-run green; reported honestly.
Composition risk checked by this seat: no FlowSchema.extend/.pick/.omit/.shape/.partial/.merge user on origin/main (grep 0 across packages/, control: 43 files import FlowSchema), and the superRefine precedent already sits in the same file (:849), so the strictObject(...).superRefine(...) wrapper changes no importer's type — the dev's Flow/FlowParsed/defineFlow claim holds.
CI: at 2026-09-05T04:07Z 16 runs in progress, 0 failed; convergence belongs to the flip step.
Clause-② contract judgment
① Derived judgments: (a) the accept set of FlowSchema narrows by exactly one shape — two edges sharing an id — as the maintainer ruled (A, decision batch #40, 「同意」); every unique-id flow parses byte-identically; correct. (b) No key renamed, retired or re-typed ⇒ no ADR-0087 registry entry; correct (no-migration-prescription — renumbering is authoring intent). (c) nodes[].id uniqueness deliberately NOT covered — outside the ruling, measured equally unenforced and filed as #15713 with two lit controls; correct scoping. (d) Error path anchoring at the LATER edge with both positions named — the diagnosable shape; correct.
② Semver: @objectstack/spec minor with a BREAKING banner under the repo's launch-window convention for accept-set narrowing — consistent with the ruling's own instruction and the changeset gates.
③ Boundary flags: none (open_questions: []). Deviations declared by the dev (REST /search 403 → one MCP search + control; finding label on #15713 per the dispatch's wording; qa/** absent; branch left unmerged for the landing path — no generated artifact touched, so no regen merge is owed). Not blocking.
No fork between the dev's judgment and this seat's, and no flag ruled in-seat, so no isolated second-opinion reviewer was needed.
Verdict: PASS. Per the 2026-08-31 ruling (in-seat contract PASS ⇒ clear both carriers in one stroke ⇒ land): needs:contract-review is removed from PR #15716 and from this card in this stroke (both hung by the dev at PR creation; read back below). Landing: ready + auto-merge (squash) once every check on 260c4d986 has concluded green (flip timer armed for 04:32Z; NOT governed). After it lands, #14945 (end node outcome: 'refused', same file) becomes dispatchable per the seat's hot-file serial queue.
Landing provenance (domain:spec seat, session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T06:46Z) — PR #15716 at head 260c4d986 flipped ready and auto-merge (squash) armed at 2026-09-05T06:44Z (the 04:32Z flip fired into a session blocked by the account's usage wall; executed after the 06:40Z reset).
Pre-landing checks per the 2026-08-31 ruling (in-seat contract PASS ⇒ clear both carriers ⇒ land): ① in-seat contract-tier PASS on this card (5549257703), ruling A executed as recorded (5548575136); ② needs:contract-review cleared on both carriers in that stroke, pair check node scripts/pm/check-clause2-carriers.mjs --pair 15716 exit 0 at 2026-09-05T04:27Z; ③ every check run on 260c4d986 concluded — 41 runs, 0 not green, mergeable_state: clean at 2026-09-05T06:43Z; governed-surface predicate on the 3-path file list: NOT governed (2026-09-05T04:08Z). Closing-keyword two-read: first line Fixes #14964, no other closing keyword in the body (#15713, #15617, #14945, #14500, #13681 are verb-less references).
On MERGED this seat probes the Duplicate edge id refinement in packages/spec/src/automation/flow.zod.ts on origin/main against the pre-merge tip, strips pm:dispatched, clears the assignee, posts the landing note, and #14945 (end node outcome: 'refused', same file) becomes dispatchable. The dev-filed #15713 (nodes[].id uniqueness) stays bare for triage.
Landing note (domain:spec seat, session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T07:16Z) — PR #15716 merged via the merge queue as 52804cdb4 at 2026-09-05T07:14:32Z (ruling A executed: FlowSchema refuses a duplicate edges[].id at parse time).
Landing probe on origin/main7dafaaedd at 2026-09-05T07:16Z: the Duplicate edge id refinement is present in packages/spec/src/automation/flow.zod.ts (1 hit); control — the pre-merge tip b25a5fc32 has 0 hits. Card closed by Fixes #14964; pm:dispatched stripped and the assignee cleared in one write from a fresh read, read back. Lane inventory re-pulled: only this card closed on this merge. Unblocked: #14945 (end node outcome: 'refused', same file) is now dispatchable in lane order. The dev-filed #15713 (nodes[].id uniqueness) stays bare for triage.
Activity
os-zhuang commentedon Sep 4, 2026
Triage (R+146):
needs-user-decision·domain:spec·priority:p2·Bug. Lands in the flow schema underpackages/spec/src/automation/**.Escalated because the repair narrows the accept set of a published schema: a uniqueness refinement at
errorlevel rejects flows that parse today. That is a protocol change with a migration cost, ⛔ not a declared-to-enforced restoration — nothing currently declares edge ids unique, so there is no contract being restored.⭐ The probe carries its own control, which is why this is a measured gap rather than a guess: the same
FlowSchemainstance rejects a flow whose edge carries an invalidtypewhile accepting two edges withid: 'dup'. ⇒ The schema was live and validating at the moment it accepted the duplicate — a missing rule, not a disabled validator.pnpm validate没发现、3,300 条测试没发现、review 没发现,因为没有任何东西检查这条属性。⇒ 有实测拉动。source过滤出边,从不按id。⇒ 冲突在有东西开始按 id 索引之前是不可见的,那之后是静默错误而不是响亮失败。 候选场景卡里都点了:BPMN 导出、流程设计器、flow-diff,或任何按 id 去重的遍历。而下一个从序列里挑一个「空闲」id 的作者,⛔ 没有任何办法知道它已被占用。推荐:加唯一性 refinement,取⚠️ 那样必须给 warning 定一个有日期的升级时刻,否则它就是上一段说的那种永久提示。
error级,并配发布说明。 四棱同向。⛔warning不建议:本卡的整个证据就是一条 warning 级的现实 —— 没有任何东西检查它,于是它活过了两次发布。再加一条不阻断的提示,只是把「无人检查」换成「无人阅读」。回退:
warning先行、下个 major 升error,若维护者判断现在拒绝已解析的流程代价太大。置信缺口: ⛔ 一个 app 不是普查。 hotcrm 的扫描是 168 条边、1 个重复 —— 说明这是潜伏陷阱而非普遍现象,在那个 app 里。⛔ 未测量任何其它 app 或示例。⇒ 若维护者倾向
error,先跑一次全仓 + examples 的重复 id 普查定 blast radius,那是决定「配发布说明」还是「配迁移」的读数。@objectstack/spec17.2.0(hotcrm 的安装 pin),⛔ 不是本仓当前树。实施前在origin/main上复现,包括那条控制项。Refs:
objectstack-ai/hotcrm#1558/ PR#1571(实例与其修复)。Generated by Claude Code
os-warren commentedon Sep 5, 2026
Maintainer ruling recorded — A:
FlowSchemagains a uniqueness refinement onedges[].idaterrorlevel, preceded by a repo-wide duplicate-id census that decides whether the changeset carries a release note or a migration lineDirector seat, summon #14, session
session_01LsEjuNMPitCHwEfYftZ1um(GitHubos-warren), 2026-09-05. Provenance: maintainer, live PM chat, decision batch #40 (item 1, presented with the recommendation A), verbatim reply 「同意」. Premise: the card's probe on@objectstack/spec17.2.0 with its control (the same schema instance rejects an invalid edgetypewhile accepting two edges withid: 'dup') and triage's facets 5542853873; the real instance hotcrm#1558 / PR #1571 survived two releases of green CI.Ruled: A. A
superRefineon the flow'sedges[]refuses a duplicateidat parse time with a message naming the id and both edge positions;error, notwarning. Not taken: B (warning with a dated escalation — the card's whole evidence is a warning-level reality that nobody checked; a non-blocking hint swaps "nobody checks" for "nobody reads"), C (leave it).Why (① ≥50%): the edge-id space is hand-authored and every reader assumes uniqueness while nothing enforces it — hotcrm's own comment ("surviving edges keep their ids") only means anything if ids are unique. ② already happened; ③ the collision is invisible until something keys on ids (BPMN export, designer, flow-diff) and then silently wrong; ④ a narrowing, no capability added.
Execution:
domain:speclane, S. Zone 2 first, before the refinement: a duplicate-edge-id census over every flow in this repo (packages/**,examples/**,qa/**) and, where the seat can read them, the pinned objectui / hotcrm trees — reported with a lit control; the count decides the changeset's remedy paragraph (a release note when zero in-repo instances remain; a migration sentence naming the files when any do). Reproduce the card's probe and control on today'sorigin/mainbefore editing (the probe ran on 17.2.0). Pins: the duplicate refused, the control still refused, a unique-id flow still accepted, and the message shape.Clause-②: yes(a published schema's accept set narrows) ⇒needs:contract-reviewon the PR. Changeset:@objectstack/specminor with a BREAKING banner (flows with duplicate edge ids are now refused at parse) and an adr-0087 disposition (no rename/retirement; the remedy is renumbering the duplicate, which is authoring intent —no-migration-prescriptionunless the census finds an in-repo instance to name).State transition, same stroke:
needs-user-decision→pm:queue.priority:p2·domain:spec· typeBugunchanged. Ledger: director seat post #12708, batch #40.Generated by Claude Code
os-project-manager commentedon Sep 5, 2026
Claim: PM loop round R1
Session:
session_01M59rPZZFzqhfMUPFqqZTkfBranch:
claude/issue-14964-flow-edge-id-uniquenessWorktree:
objectstack-issue-14964Domain:
domain:specFile surface:
packages/spec/src/automation/flow.zod.ts(FlowSchemaat:586,FlowEdgeSchemaat:503withidat:521, the existingsuperRefineprecedent at:849, all onorigin/main7bf96cfd0) +flow.test.ts· the generatedcontent/docs/references/automation/flow.mdxonly if a describe moves (regenerated, never hand-edited) · one changeset with the BREAKING banner and the ADR-0087 disposition · the Zone-2 duplicate-edge-id census (read-only) overpackages/**,examples/**,qa/**and the pinned objectui / hotcrm trees where readable. Stop on breach; explain in the report.Container & model:
S,mode:subagent,model: fable(claude-fable-5-1) —node scripts/pm/dispatch-gates.mjs --tier packages/spec/src/automation/flow.zod.tsrun 2026-09-05T02:48Z against the tree atb4b37e59: "no path-derived mandate", "Clause ② SUSPECT surface" hint; tier set from card CONTENT — the ruling narrows a published schema's accept set (flows with duplicate edge ids are refused at parse) ⇒ clause ② ⇒CONTRACT_REVIEW_TIER(claude-fable-5-1, re-derived fromorigin/main:scripts/pm/dispatch-gates.mjsat 01:44Z). Maintainer ruling A recorded by the director (5548575136, batch #40, 「同意」).Clause-②: yes
Serial constraints cleared:
packages/spec/src/automation/flow.zod.tshas no in-flight claim at 02:48Z; queued sibling #14945 (ruled 2′ — theendnode gainsoutcome: 'refused') also writes this file ⇒ serial, not folded (different defect, different fix): #14964 goes first (type Bug, older), #14945 is dispatched only after this PR lands. #15617 lives inautomation/execution.zod.ts(different file).packages/spec/src/migrations/registry.tsis HELD by draft PR #15626 — a parse-time refinement retires nothing, so no registry entry is expected; the ADR-0087 disposition rides in the changeset (no-migration-prescriptionunless the census names an in-repo instance); if a gate demands a registry entry the dev stops and reports. Same-round in-flight #14079 (data/filter-text-conformance.ts+ drivers) and #14882 (system/i18n-resolver.ts) are source-disjoint; the generated corpus lands serially viascripts/pm/os-regen-merge.sh. Landing-window PR #15672 (#14954,automation/control-flow.zod.ts+authorable-surface/automation.json) shares theautomation/generated shard only — if this card regeneratesauthorable-surface/automation.json, it mergesmainafter #15672 lands before enqueueing.Generated by Claude Code
claude commentedon Sep 5, 2026
Claim (os-dev): implementing on the PM-dispatched branch, round R1.
Session:
session_01M59rPZZFzqhfMUPFqqZTkfBranch:
claude/issue-14964-flow-edge-id-uniqueness(pushed empty as the landing marker; basea55efc6c1)Worktree:
objectstack-issue-14964Scope: ruling A from 5548575136 — a
superRefineonFlowSchema.edges[]refusing a duplicate edgeidat parse time (error level), preceded by the Zone-2 duplicate-edge-id census with a lit control; probe + control reproduced on today'sorigin/mainbefore editing. File surface:packages/spec/src/automation/flow.zod.ts+flow.test.ts+ one@objectstack/specminor changeset with the BREAKING banner and the ADR-0087 disposition. Assignee untouched.Generated by Claude Code
feat(spec)!: FlowSchema refuses a flow whose edges[] declares the sam…
claude commentedon Sep 5, 2026
os-dev-report
{ "issue": 14964, "status": "done", "branch": "claude/issue-14964-flow-edge-id-uniqueness", "pr": "https://github.com/objectstack-ai/objectstack/pull/15716", "premise_still_valid": true, "head": "260c4d986", "base": "a55efc6c1 (origin/main at worktree creation; the PM claim cited 7bf96cfd0 — landmarks FlowEdgeSchema :503, id :521, FlowSchema :586, superRefine precedent :849 unchanged on a55efc6c1)", "summary": "Ruling A implemented: FlowSchema is strictObject(...).superRefine(...) in packages/spec/src/automation/flow.zod.ts — each later occurrence of an already-declared edges[].id raises one custom issue at [edges, j, id] naming the id and both positions (Zod 4.4.3 superRefine returns the same ZodObject, so Flow/FlowParsed/defineFlow and every importer keep their types; no describe moved, no generated follower). Card probe + control reproduced on origin/main a55efc6c1 before editing (duplicate accepted with parsed ids [dup,dup]; invalid edge type refused at edges.1.type; unique accepted). Zone-2 census with a lit control: 0 in-repo instances, so the changeset (@objectstack/spec minor, BREAKING banner, adr-0087 not-required (no-migration-prescription)) carries the release-note remedy; the ADR-0087 gate first refused my FROM-to-TO heading as a prescription — dropped, gate green. Draft PR #15716 with Fixes #14964; needs:contract-review hung on PR and card (REST additive POST, compare read-back green on both). No registry entry written; execution.zod.ts and the end-node config untouched.", "files_changed": [ "packages/spec/src/automation/flow.zod.ts (+27)", "packages/spec/src/automation/flow.test.ts (+105, 6 pins)", ".changeset/flow-edge-id-uniqueness.md (new)" ], "census": { "method": "AST scanner (TypeScript compiler API for TS/JS; structural walk for YAML/JSON) over every edges: [...] array literal — property assignment, edges variable, inside defineFlow(...), as-const/parenthesised unwrapped; undecidable elements COUNTED (computed id, spread, non-object)", "control": "synthetic root: 3 TS duplicate shapes + YAML + JSON duplicates all reported with positions (5/5); negative file 0 with its spread and computed id counted undecided", "objectstack_a55efc6c1": "5703 code + 366 data files; 776 edges arrays; 1098 edges; 1052 literal ids; 8 computed; 8 spread; 30 non-object; 24 arrays with an undecidable element (all test fixtures or bpmn-mapping computed spreads, none under examples/**); 0 duplicate arrays; root qa/ does not exist (packages/qa/** covered under packages/**)", "dynamic": "every example flow file (app-crm, app-showcase, app-todo: 7 files, 50 distinct flow exports / 73 rows with re-exports) parsed through the built FlowSchema from dist/automation: 0 refused; control refused at edges.1.id", "objectui_pin_a472b07": "read-only, byte-equal to .objectui-sha: 89 arrays / 104 edges, 17 literal, 75 computed (React-Flow state), 0 duplicate literal ids; computed 75 not statically decidable", "hotcrm": "NOT MEASURED (not checked out; its one instance fixed in hotcrm#1571 per the card)" }, "tests": "All heavy runs via scripts/pm/os-verify-lock.sh (slot names set before first attempt; 99 never returned). spec build: VERDICT command-exit 0 (133s). flow.test.ts: Test Files 1 passed, Tests 102 passed; spec typecheck (tsc + check:scripts-typecheck + check:test-typecheck) OK — VERDICT command-exit 0 (54s). Whole spec suite: Test Files 472 passed, Tests 12692 passed — VERDICT command-exit 0 (342s). Consumers (prefix direction ...@objectstack/spec = downstream, closures built with turbo --concurrency=2): lint Test Files 96 passed / Tests 3006 passed 5 skipped + core Test Files 49 passed / Tests 1190 passed — VERDICT command-exit 0 (257s); service-automation Test Files 108 passed / Tests 1294 passed — VERDICT command-exit 0 (183s). Ablation (fix committed first; BASE text of flow.zod.ts restored under an EXIT/INT/TERM trap by absolute path; the test imports ./flow.zod from src so no dist leg): mutation proven on disk — marker Duplicate edge id 1 to 0, superRefine 3 to 2, tree blob abc5dc7 vs HEAD a0d5cf2; expected direction four refusal pins red, control + unique-id green: observed Tests 4 failed | 98 passed (102), exactly those four; restore exit 0, git diff HEAD empty, status empty, blob back to a0d5cf2, marker 1. Gates: family derived with node scripts/pm/dispatch-gates.mjs (no paths) at a4da73eec — 121 commands, 94 exit 0; union re-run at final commit 260c4d986: check:generated all 15 artifacts up to date (api-surface, authorable-surface, docs, strictness-ledger, liveness incl.), check:empty-state, check:yaml-examples, check:skill-examples (257 prose examples, 3 surfaces), check:error-code-provenance, check:exported-any, check:dual-source-exports, check:entry-nameability, check:llms-txt, check:variant-docs, check:browser-reachable-entries, lint check:doc-formula-expressions, check:nul-bytes (7584 files, no control bytes), check:cross-package-test-inputs, check:doc-authoring, check:docs-audit-scope, check:changeset-gate-self-tests, check-empty-changeset --base a55efc6c1, check-changeset-no-major --base a55efc6c1, check-adr-0087-registration --base a55efc6c1 (1 declared-breaking changeset, each carrying an ADR-0087 disposition), check:pm-half-states, check:objectui-changeset, check-keyed-text-bounds, check:spec-parsed-alias, check:test-source-alias, check:merge-driver, reference-carrier-shape — all exit 0 with their own verdict lines (per-gate table in PR comment 5549213320). Six dist-reading gates first answered STALE at 260c4d986 (ablation restore gave flow.zod.ts a newer mtime than dist) — prerequisite refusal, not a reading; spec rebuilt (same .build-input-hash 7c08850e) and re-run green; check:skill-examples additionally needed the client-react closure built (34 tasks). check:partof-closing-keyword on the PR body: no contradiction.", "gates_not_measured": [ "check:react-declaration-parity — external input (objectui sdui.manifest.json), exits 1 by design locally", "check:published-readme-exports — needs a full-repo build; reads READMEs this diff does not touch", "24 CI-step spellings in the derivation (RUNNER_TEMP / matrix / job labels: ESLint, workspace type-check, shard attestations, claim-path, label steps) — CI owns them", "example apps validate/test scripts — declared narrowing (59-63-package closures); their flows parsed directly instead" ], "deviations": [ "REST /search/issues answered 403 while every other repo-scoped REST read/write passed (issue, comments, labels, issues POST, pulls POST all 200/201) — dedup switched to one targeted MCC search_issues plus one control query that hit #14964", "the node-id finding carries the finding label because the dispatch named it as such; the general rule would leave a concrete defect unlabelled — PM may re-triage", "PR body footer: both the repo session-URL form and the harness trailer were written; the platform stored the body byte-equal except for moving the rule+footer block to the tail", "origin/main moved during the run (dispatch-gates STALE TREE notice, 3 derivation inputs changed); branch left unmerged for the PM landing path — no generated artifact regenerated, so the authorable-surface/automation.json shard named in the claim is untouched", "the ruling root qa/** does not exist at the repo root; packages/qa/** was scanned under packages/**" ], "labels": { "pr_15716": "documentation,needs:contract-review,size/m,tests,tooling (read back)", "issue_14964": "domain:spec,needs:contract-review,pm:dispatched,priority:p2 (read back)" }, "cleanup": "worktree /home/user/objectstack-issue-14964 removed without force after remote == local at 260c4d986 (node_modules removed first); no dev server or background monitor started", "mcp_calls": "2 — both search_issues (one targeted dedup for the node-id finding, one control query that hit #14964); all other GitHub reads and writes via REST", "open_questions": [], "out_of_scope_findings": [ "filed as #15713: FlowSchema accepts a flow whose top-level nodes[].id repeats — measured with two lit controls (missing required label; unknown node key — an invalid node type is not a control, the vocabulary is open under ADR-0018); only region nodes are checked by analyzeRegion at registerFlow; unassigned, finding label; dedup: MCP search_issues found no open card (hits: #14964 itself, closed #9889, closed #4027)" ] }Generated by Claude Code
claude commentedon Sep 5, 2026
Review: ACCEPT · Clause-② contract review: PASS —
domain:specseat,session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T04:11Z, PR #15716 at head260c4d986(read 2026-09-05T04:07Z; basemain, draft, NOT governed — predicate 0 of 3 paths at 2026-09-05T04:08Z).Tier fuse read before this review:
get_sessionat 2026-09-05T03:50Z —session_context.modelandlast_served_modelbothclaude-fable-5-1=CONTRACT_REVIEW_TIER.Implemented-by:
claude/issue-14964-flow-edge-id-uniqueness(mode:subagent dev, branch identity)Reviewed-by:
session_01M59rPZZFzqhfMUPFqqZTkfChecklist readings (against GitHub, not the report)
main, first lineFixes #14964— the only closing keyword;FlowSchemaaccepts a flow whose top-levelnodes[]declares the same id twice — measured with two lit controls; only region nodes are checked (analyzeRegion) #15713, spec:FlowRunSummary's two paragraphs disagree for a subflow parent —failedis declared a node fold, while the summary is declared to answer "what did this run cause" and roll a child's totals up #15617, A flow cannot REFUSE with per-record text: the only channel that interpolates is a screendescription, and a message-only screen still renders Submit and toasts "completed" #14945, [finding] The flow language has no aggregation / group-by node — the grouping half of the digest shape, deferred by ruling until a second pull #14500, Aloopnode aborts the entire flow run when one iteration's node fails — a single bad row kills a whole scheduled sweep, and there is no per-iteration containment to opt into #13681, hotcrm feat(service-ai): extract AI metadata authoring into cloud-only AI Studio #1571 and decision batch Hide blog menu from top navigation #40 are cited with no verb beside them.flow.zod.ts(+27),flow.test.ts(+105), one changeset. Nocontent/docs/releases/change, nothing unrelated;packages/spec/src/migrations/registry.tsuntouched (held), no generated artifact moved (check:generatedall 15 up to date — a refinement adds no key, so noauthorable-surface/ docs follower).5548575136):superRefineonedges[],errorlevel (acustomissue, no warning path, no opt-out), message names the id and BOTH positions, anchored on the later edge; the probe and its control reproduced onorigin/maina55efc6c1BEFORE editing; pins: duplicate refused · control still refused (invalid_valueatedges.1.type, no duplicate issue beside it) · unique-id flow accepted in authored order · message shape viaformatZodError· one issue per later occurrence naming the FIRST declaration ·defineFlowrefuses with the same anchored issue.edges[]arrays, 1,098 edges), undecidable elements COUNTED not dropped; control root with five planted duplicates found 5/5; 0 in-repo duplicates; dynamic reading: every shipped example flow parsed through the built schema, 0 refused, control refused; pinned objectui tree ata472b07: 0 literal duplicates (75 computed React-Flow ids not statically decidable — declared). The ruling'sqa/**root does not exist at the repo root (packages/qa/**was insidepackages/**) — a deviation reported, not hidden. hotcrm NOT MEASURED (not checked out; its one instance was renumbered in hotcrm PR feat(service-ai): extract AI metadata authoring into cloud-only AI Studio #1571 per the card) — accepted as the census's declared gap; the changeset's remedy paragraph is a release note, as the ruling says for zero in-repo instances.@objectstack/specminor with the BREAKING banner and the adr-0087 dispositionnot-required (no-migration-prescription)— the ruling's exact shape;check-adr-0087-registration --base a55efc6c1andcheck-changeset-no-majorgreen per the dev,Lint & Repo Gateson this head is the reading that counts.260c4d986= the PR head; real commands and counts (flow.test 102 passed; whole spec 12,692 passed; consumers downstream with closures built: lint 3,006, core 1,190, service-automation 1,294; typecheck OK). Ablation trap-restored with on-disk proof (marker 1→0, blobabc5dc7≠a0d5cf2): exactly the four refusal pins red, control + unique green; restore verified by blob equality. Six dist-reading gates first answered STALE after the restore's mtime bump — a prerequisite refusal, rebuilt and re-run green; reported honestly.FlowSchema.extend/.pick/.omit/.shape/.partial/.mergeuser onorigin/main(grep 0 acrosspackages/, control: 43 files importFlowSchema), and thesuperRefineprecedent already sits in the same file (:849), so thestrictObject(...).superRefine(...)wrapper changes no importer's type — the dev'sFlow/FlowParsed/defineFlowclaim holds.Clause-② contract judgment
① Derived judgments: (a) the accept set of
FlowSchemanarrows by exactly one shape — two edges sharing anid— as the maintainer ruled (A, decision batch #40, 「同意」); every unique-id flow parses byte-identically; correct. (b) No key renamed, retired or re-typed ⇒ no ADR-0087 registry entry; correct (no-migration-prescription— renumbering is authoring intent). (c)nodes[].iduniqueness deliberately NOT covered — outside the ruling, measured equally unenforced and filed as #15713 with two lit controls; correct scoping. (d) Error path anchoring at the LATER edge with both positions named — the diagnosable shape; correct.② Semver:
@objectstack/specminor with a BREAKING banner under the repo's launch-window convention for accept-set narrowing — consistent with the ruling's own instruction and the changeset gates.③ Boundary flags: none (
open_questions: []). Deviations declared by the dev (REST/search403 → one MCP search + control;findinglabel on #15713 per the dispatch's wording;qa/**absent; branch left unmerged for the landing path — no generated artifact touched, so no regen merge is owed). Not blocking.No fork between the dev's judgment and this seat's, and no flag ruled in-seat, so no isolated second-opinion reviewer was needed.
Verdict: PASS. Per the 2026-08-31 ruling (in-seat contract PASS ⇒ clear both carriers in one stroke ⇒ land):
needs:contract-reviewis removed from PR #15716 and from this card in this stroke (both hung by the dev at PR creation; read back below). Landing: ready + auto-merge (squash) once every check on260c4d986has concluded green (flip timer armed for 04:32Z; NOT governed). After it lands, #14945 (endnodeoutcome: 'refused', same file) becomes dispatchable per the seat's hot-file serial queue.Generated by Claude Code
claude commentedon Sep 5, 2026
Landing provenance (domain:spec seat,
session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T06:46Z) — PR #15716 at head260c4d986flipped ready and auto-merge (squash) armed at 2026-09-05T06:44Z (the 04:32Z flip fired into a session blocked by the account's usage wall; executed after the 06:40Z reset).Pre-landing checks per the 2026-08-31 ruling (in-seat contract PASS ⇒ clear both carriers ⇒ land): ① in-seat contract-tier PASS on this card (
5549257703), ruling A executed as recorded (5548575136); ②needs:contract-reviewcleared on both carriers in that stroke, pair checknode scripts/pm/check-clause2-carriers.mjs --pair 15716exit 0 at 2026-09-05T04:27Z; ③ every check run on260c4d986concluded — 41 runs, 0 not green,mergeable_state: cleanat 2026-09-05T06:43Z; governed-surface predicate on the 3-path file list: NOT governed (2026-09-05T04:08Z). Closing-keyword two-read: first lineFixes #14964, no other closing keyword in the body (#15713, #15617, #14945, #14500, #13681 are verb-less references).On MERGED this seat probes the
Duplicate edge idrefinement inpackages/spec/src/automation/flow.zod.tsonorigin/mainagainst the pre-merge tip, stripspm:dispatched, clears the assignee, posts the landing note, and #14945 (endnodeoutcome: 'refused', same file) becomes dispatchable. The dev-filed #15713 (nodes[].iduniqueness) stays bare for triage.Generated by Claude Code
claude commentedon Sep 5, 2026
Landing note (domain:spec seat,
session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T07:16Z) — PR #15716 merged via the merge queue as52804cdb4at 2026-09-05T07:14:32Z (ruling A executed:FlowSchemarefuses a duplicateedges[].idat parse time).Landing probe on
origin/main7dafaaeddat 2026-09-05T07:16Z: theDuplicate edge idrefinement is present inpackages/spec/src/automation/flow.zod.ts(1 hit); control — the pre-merge tipb25a5fc32has 0 hits. Card closed byFixes #14964;pm:dispatchedstripped and the assignee cleared in one write from a fresh read, read back. Lane inventory re-pulled: only this card closed on this merge. Unblocked: #14945 (endnodeoutcome: 'refused', same file) is now dispatchable in lane order. The dev-filed #15713 (nodes[].iduniqueness) stays bare for triage.Generated by Claude Code
feat(spec)!: `FlowSchema` refuses a flow whose `edges[]` declares the…
test(spec): automation test titles state each cited decision in words…