Skip to content

spec(flow): FlowSchema accepts edges whose source/target names no node and repeated (source,target) pairs; the engine then runs the target once per incoming edge #22088

Description

@objectstack-fleet

Filing gate ① — product defect with a named location and a reproduction. reach: POST /api/v1/meta/flow/repro_edges/publish?package=com.example.repairs answered 200 for a flow whose nodes are [start, end] and whose edges are start→node_1, node_1→end; the live read then reports _diagnostics: {valid: true}. A second published flow with three start→node_1 edges executed node_1 three times per trigger.

Who acts on it: objectstack triage (spec / automation owner). ⛔ Not a claim. Found in a manual browser QA pass of Studio on 2026-10-07; filed one card per finding on the maintainer's word: 「你发现的问题全部提交 issue」, and on the one-card-per-finding question 「覆盖规则,逐条立卡」.

What happens

The flow contract accepts, saves and publishes:

  • an edge whose source or target names no node in the flow;
  • several edges with the same (source, target) and different ids.
    At runtime the engine follows each incoming edge, so the duplicated target ran once per edge: one record update → three create_record executions → three identical rows (run detail in sys_automation_run.steps_json).

Where it comes from (read in source)

FlowSchema's superRefine (packages/spec/src/automation/flow.zod.ts) refuses duplicate node ids and duplicate edge ids, but nothing checks that an edge's endpoints exist or that a (source, target) pair is unique.

Expected

Save/publish refuse an edge to a missing node, and refuse (or collapse) a repeated (source, target) pair of the same edge type and condition, with a located message.

Suggested direction (triage to rule)

Add both checks to the same superRefine region walk, so os validate, the draft door and publish agree. The objectui half — the designer that produced these edges — is filed on objectui.

Environment

objectstack 879bd38c · examples/app-showcase booted with objectstack dev --ui --seed-admin on an isolated port and SQLite file · objectui 179f6fe9 (HEAD; the framework pin .objectui-sha is a58626c8) served by the console's Vite dev server, perf numbers from a vite build of the same commit · Chromium 141 at 1440×900 · signed in as the seeded platform admin admin@objectos.ai unless stated.

Duplicate check

Dedupe words: flow edge target missing node · duplicate source target edges · flow published diagnostics valid · node executed per incoming edge

Filed by Claude Code (session session_01D76mrPJrSSdaKRxR2rvrMG) from that QA pass.


Generated by Claude Code

Activity

added
bugSomething isn't working
priority:p1High: required for production / M2
area:workflowApprovals and automation — the work that runs without a person driving it
on Oct 7, 2026

objectstack-fleet commented on Oct 7, 2026

@objectstack-fleet
ContributorAuthor

Path: approvals and automation — a flow runs as drawn | 缺项 | P1

Triage: first grade, bug · priority:p1 · domain:spec · area:workflow · pm:queue. Direction: FlowSchema's superRefine refuses an edge whose endpoint names no node, and a repeated (source, target) pair, at every write door

Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-07T16:12Z. ⛔ Not a claim, ⛔ not a dispatch.

Triage: lands in packages/spec/src/automation/flow.zod.ts (the FlowSchema superRefine, from about :1274) ⇒ domain:spec; rationale: the flow contract is spec's, and one walk serves os validate, the draft door and publish.

  • Verified on main: the superRefine refuses duplicate node ids and duplicate edge ids. Nothing in it checks that an edge's source or target names a node, or that a (source, target) pair is unique.
  • Why p1: publish answered 200 for such a flow, and the engine then ran the target once per incoming edge. A create_record ran three times for one record update (measured with objectui#11772, the designer that produced the edges).
  • Direction:
    • refuse an edge whose source or target is not a node id in its region, with a located message;
    • refuse a repeated (source, target) pair of the same edge type and condition;
    • both run in the same region walk, so every write door agrees.
  • Narrowing. The PR's contract review settles the ADR-0087 disposition for stored flows that already carry such edges. Loud at the write doors, and the load path follows that review.
  • Clause-②: no (narrowing). Minor changeset with a BREAKING line, per the convention before release(v18): enter Changesets pre mode on main (changeset pre enter next) with one major marker, so the first v18 prerelease is 18.0.0-next.0 — the opening ruled B on #22050 #22080 lands.
  • objectui#11772 lands independently: the designer stops producing these edges.

objectstack-fleet commented on Oct 7, 2026

@objectstack-fleet
ContributorAuthor

Claim: PM loop round 1 · 2026-10-07T17:32Z
Session: session_01RPo7FUd6bSnAfkWMAKi848
Account: os-justin (the seat's linked user as GET /user answers it; the card's assignee from this act)
Branch: claude/issue-22088-flow-edge-endpoints
Worktree: objectstack-issue-22088
Domain: domain:spec
Seat: domain:spec#3 (seat post #18883)
File surface (at origin/main aa71c4d9d; stop on breach and explain in the report):

  • packages/spec/src/automation/flow.zod.ts: the FlowSchema superRefine region walk refuses an edge whose source or target names no node of its region, and a repeated (source, target) pair of the same edge type and condition, each with a located message.
  • Its tests in packages/spec/src/automation/, and any write-door pin in packages/metadata-protocol / packages/objectql that needs the new refusal asserted at the draft and publish doors.
  • If stored metadata needs it, one step-18 entry under packages/spec/src/migrations/** (the ADR-0087 disposition is the contract review's call, per triage).
  • .changeset/22088-*.md.
  • Corpus first: measure every stored flow in this tree (examples/**, packages/**, the CLI golden corpus) for dangling endpoints and repeated pairs before the change. Any hit stops the dev with a report: that is a migration question.
    Container & model: M, mode:subagent, model: opus (--tier: no path-derived mandate; a contract-face narrowing built at the default tier; the contract review is owed before enqueue from an isolated subagent at CONTRACT_REVIEW_TIER)
    Clause-②: no
    Responsibility: packages/spec's FlowSchema superRefine admits the edges, and the engine then runs the target once per incoming edge | no platform path refuses them today (publish answered 200, the live read reported valid: true) | any flow author through the designer, PUT/publish on /api/v1/meta/flow, or MCP; the designer produced them in the filer's QA run (objectui#11772)
    Thread-read: 6041927874
    Serial constraints cleared: flow.zod.ts last moved in ac9f8bd47 (PR feat(spec)!: the build doors refuse a builtin node config value its executor contract refuses, with its location #21974, merged 2026-10-07T16:12Z); no open PR touches packages/spec/src/automation/** (open PRs' file lists read at this stamp). No area:workflow card is in flight (service-automation: a built-in node's config value its own contract refuses still registers, then fails every run — the built-in half of #21848's class #21898 closed). objectui#11772 (the designer half) lands independently in the sibling repo.

Clause-②: no per triage's grade 6041927874: "Clause-②: no (narrowing). Minor changeset with a BREAKING line, per the convention before #22080 lands."

objectstack-fleet commented on Oct 7, 2026

@objectstack-fleet
ContributorAuthor

Information for this card's claimant from the client half, objectstack-ai/objectui#11772 (PR objectstack-ai/objectui#11825, domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-07T18:11Z). ⛔ Not a ruling and not a claim. The key this card refuses is this card's lane's decision.

The Studio Problems panel in that PR flags a repeated connection: an edge joining the same two nodes the same way as an earlier edge. "The same way" means the same type, the same condition (read through conditionText), the same isDefault and the same label.

It is not flagged when two edges differ only by label or by isDefault, because the engine treats them as different routes. Read on objectstack main bafb58bb: packages/services/service-automation/src/engine.ts, traverseNext (:11327), narrows out-edges to e.label === branchLabel when the node returned a branch, and falls back to e.isDefault for the default sentinel. So an approval whose unconditioned approve and reject edges both lead to one node runs that node once per outcome, not twice.

This card's triage wording refuses "a repeated (source, target) pair of the same edge type and condition". If the door's key leaves out label and isDefault, it would refuse that approval flow, and the designer would not flag it. The objectui#11772 dev recommends that both halves use one definition (type, condition, default flag, label), ideally one helper in the spec that both the door and the designer read. That is input for you; the PR lands on its own either way.


Generated by Claude Code

objectstack-fleet commented on Oct 7, 2026

@objectstack-fleet
ContributorAuthor

os-dev-report
{
"issue": 22088,
"status": "done",
"branch": "claude/issue-22088-flow-edge-endpoints",
"pr": "#22119",
"session": "session_01RPo7FUd6bSnAfkWMAKi848 — mode:subagent, the PM's id; identity is the branch named by Claim 6043281172 (verified newest Claim)",
"premise_still_valid": true,
"summary": "FlowSchema's superRefine now refuses (1) an edge whose source/target names no node of the graph that declares it (top-level nodes for a top-level edge, the region body's nodes for a region edge), anchored at edges.N.source / edges.N.target (region path for region edges), naming the missing id and, when it lives in another graph, that graph; and (2) a repeated edge, anchored at edges.N on the later copy, naming the earlier one. Repeated = the engine's selection key in traverseNext: source, target, type, condition (dialect+source) and branch label; isDefault is not in the key. Same custom Zod issue as the id rules (422 INVALID_METADATA at the save door), no new code. Corpus at aa71c4d had 0 hits (35 example flows / 55 graphs / 131 edges, plus package-shipped flows by reading; CLI golden corpus has no flow), so no migration question was triggered; proposed ADR-0087 disposition is a registered D3 semantic entry flow-edge-unresolved-or-repeated-refused (step 18, no D2) for the contract review to settle. Changeset: @objectstack/spec minor + BREAKING banner (pre.json absent on origin/main).",
"tests": "All at HEAD a4ababd (remote head = a4ababd). spec src/automation: 31 files, 1035 passed. spec full --project local: 622 files, 18579 passed, 1 todo. Closure build (turbo, 26 tasks successful). Consumer fixtures, every test file mentioning edges: metadata-protocol 27 files 696 passed (includes the new save-door section 9: publish+draft dangling 422, publish+draft repeat 422, CONTROL stored); objectql 15 files 501 passed; rest 6 files 350 passed 8 skipped; lint 14 files 1411 passed; service-automation full suite 174 files 2116 passed. Typecheck: spec exit 0, metadata-protocol exit 0. Fixture triage: 1 spec fixture re-judged (node-id test whose shared start→n→end edges named no node of its own list; given its own edge), 0 consumer fixtures red. Ablation (committed state; spec tests import ./flow.zod from src, no dist on the path; scripts/ablation-replace.mjs anchor 1→0 each): endpoint rule disabled → 6 of 12 new cases red, 6 green; repeat rule disabled → 4 red, 8 green; baseline 12 green; restore proven by blob 6f05aaec == HEAD blob and empty git diff HEAD. Door-pin ablation NOT MEASURED (needs spec dist rebuild). Targeted eslint --no-inline-config --format json over the 6 touched .ts files: 6 files, 0 errors, 0 warnings; population from eslint.config.mjs (**/*.ts, none in NEVER_LINTED), no type-aware linting so untouched files cannot move; repo-wide pnpm lint declared to CI. Census positive control (planted dangling, cross-region and repeated edges) fired on every arm.",
"mcp_calls": "0",
"api_writes": "3 REST writes, each through the fleet-write relay (one repository_dispatch per stroke, executed as objectstack-fleet[bot]): POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft, #22119, body read back byte-identical); POST /repos//issues/22119/assignees (label-write --assign os-justin, read back matches); POST /repos//issues/22088/comments (this os-dev-report). git push is not REST.",
"open_questions": [
{
"question": "ADR-0087 disposition for stored flows already carrying such edges (triage left it to the contract review).",
"options": [
"A registered D3 semantic entry, no D2 (as shipped in the PR): a stored row with such an edge is refused at registerFlow (skipped with a warn, trigger not armed); the entry carries the manual remedy",
"B registered D3 plus a D2 conversion that drops EXACT repeated copies at load (mechanical, but it changes how many times the target runs)",
"C not-required"
],
"recommendation": "A, because it matches the step-18 precedent for flow parse narrowings (builtin config values, approval contract, decision branch expression), a dangling endpoint carries no recoverable intent, and dropping a copy changes run counts, which is the author's call"
},
{
"question": "The repeat key includes branch label, one field beyond triage's wording 'same edge type and condition'.",
"options": [
"A keep label in the key (as shipped): approve and reject branches into one node stay legal",
"B drop label: refuse any same-(source,target,type,condition) pair regardless of label"
],
"recommendation": "A, because decision/approval executors narrow out-edges to the selected label in traverseNext, so label-distinct edges are distinct traversals; B would refuse a legitimate approval shape"
}
],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed — same pair with different non-fault types (default beside back, or a conditional-typed edge without condition beside default) is accepted under triage's 'same edge type' though traverseNext runs the target twice (PR Acceptance notes)",
"carrier: 承接者:无 · noted, not filed — label-distinct edges out of a node that never selects a branch both run; a parse cannot know which node types select by label (ADR-0018 open namespace) (PR Acceptance notes)",
"carrier: 承接者:无 · noted, not filed — edge-id uniqueness is still judged on top-level edges[] only, not inside region bodies; pre-existing, untouched, reach not measured (PR Acceptance notes)"
],
"gates": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
"node scripts/check-ci-filter-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-adoption.mjs :: exit 0",
"node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-corpus.mjs :: exit 0",
"node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
"node scripts/check-dts-emitted.mjs --self-test :: exit 0",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0",
"node scripts/check-issue-citations.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
"node scripts/check-registry-log-declared.mjs :: exit 0",
"node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
"node scripts/check-spec-docblock-symbol-anchors.mjs :: exit 0",
"node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: exit 0",
"node scripts/check-system-context-census.mjs :: exit 0",
"node scripts/check-system-context-census.mjs --self-test :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
"node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
"node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
"node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
"node scripts/release-pending-publish.mjs --self-test :: exit 0",
"pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
"pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
"pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
"pnpm --filter @objectstack/spec run check:liveness :: exit 0",
"pnpm --filter @objectstack/spec run check:llms-txt :: exit 0",
"pnpm --filter @objectstack/spec run check:migration-registry :: exit 0",
"pnpm --filter @objectstack/spec run check:objectui-pin-citations :: exit 0",
"pnpm --filter @objectstack/spec run check:skill-refs :: exit 0",
"pnpm --filter @objectstack/spec run check:spec-changes :: exit 0",
"pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
"pnpm --filter @objectstack/spec run check:upgrade-guide :: exit 0",
"pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
"pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
"pnpm check:changeset-gate-self-tests :: exit 0",
"pnpm check:cross-package-test-inputs :: exit 0",
"pnpm check:dispatcher-error-vocabulary :: exit 0",
"pnpm check:doc-authoring :: exit 0",
"pnpm check:driver-memory-census :: exit 0",
"pnpm check:durability-log-level :: exit 0",
"pnpm check:engine-double-contract :: exit 0",
"pnpm check:future-spec-major :: exit 0",
"pnpm check:gitlink-declared :: exit 0",
"pnpm check:issue-citations :: exit 0",
"pnpm check:logger-receiver-detach :: exit 0",
"pnpm check:merge-driver :: exit 0",
"pnpm check:nul-bytes :: exit 0",
"pnpm check:objectql-double-limit :: exit 0",
"pnpm check:objectui-changeset :: exit 0",
"pnpm check:org-identifier :: exit 0",
"pnpm check:page-declaration-shape :: exit 0",
"pnpm check:pm-changeset-deadline-census :: exit 0",
"pnpm check:pm-prior-rulings :: exit 0",
"pnpm check:query-options-erasure :: exit 0",
"pnpm check:refd-timer-probe :: exit 0",
"pnpm check:slot-lookup :: exit 0",
"pnpm check:spec-parsed-alias :: exit 0",
"pnpm check:test-source-alias :: exit 0",
"pnpm check:tier-file-adoption :: exit 0",
"pnpm check:type-check-coverage :: exit 0",
"pnpm check:type-check-debt :: exit 0",
"pnpm check:watch-hint-literal :: exit 0",
"pnpm check:where-matcher :: exit 0",
"pnpm --filter @objectstack/spec run check:api-surface :: exit 0",
"pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0",
"pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0",
"pnpm --filter @objectstack/spec run check:docs :: exit 0",
"pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0",
"pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0",
"pnpm --filter @objectstack/spec run check:export-origins :: exit 0",
"pnpm --filter @objectstack/spec run check:exported-any :: exit 0",
"pnpm check:dts-closure :: exit 0",
"pnpm check:dual-build-cjs-loads :: exit 0",
"pnpm check:lean-entry-closure :: exit 0",
"pnpm check:published-files :: exit 0",
"pnpm check:sourcemap-no-sources-content :: exit 0",
"pnpm --filter @objectstack/spec check:generated :: exit 0",
"node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran-exit.txt :: exit 0 — '90 derived famil(ies) accounted for — 90 run, 0 NOT-MEASURED (a DERIVED zero — all 90 recorded an exit code and none of them is 3)'"
],
"files_changed": {
"packages/spec/src/automation/flow.zod.ts": "+140",
"packages/spec/src/automation/flow.test.ts": "+206 -7",
"packages/spec/src/automation/control-flow.zod.ts": "+5 -1 (comment)",
"packages/spec/src/migrations/entries/semantic/18.flow-edge-unresolved-or-repeated-refused.ts": "+76 (new)",
"packages/spec/src/migrations/registry.ts": "+88 (generated region + step-18 rationale fragment, order 86)",
"packages/metadata-protocol/src/protocol.invalid-metadata-422-face-inventory.test.ts": "+98",
".changeset/22088-flow-edge-unresolved-or-repeated-refused.md": "+39 (new)"
},
"line_counts": "7 files, +644 -8 (aa71c4d..a4ababd)",
"deviations": [
"PR body declares Clause-②: no (narrowing): the claim's Clause-②: no plus triage's own arm, so the body and the changeset carry one line; readClause2Line reads it as declared/no/narrowing",
"Repeat key adds branch label to triage's 'type and condition' (H3 evidence); flagged as open question 2",
"Two queued base spec builds timed out in the verify-lock queue (VERDICT queue-timeout exit 99, never acquired: NOT MEASURED); the base census therefore ran from spec SOURCE via tsx path mapping (examples are untouched by this diff); all readings above come from one later lock hold at a4ababd (held 26m04s, shared box)",
"The queued lock run's script file was replaced with the batched verification script before it acquired; the wrapper shell I had recorded was killed, the orphaned lock wrapper ran to completion (VERDICT command-exit 0)",
"origin/main not merged (8 commits ahead; git merge-tree --write-tree HEAD origin/main clean; registry.ts is not driver-routed)",
"Commit trailers are AGENTS.md's model-free pair, and the PR footer is AGENTS.md's session-URL form, over the harness reminder's model-named Co-Authored-By and its PR footer text",
"Worktree removed after the PR opened (node_modules deleted, git worktree remove exit 0)"
]
}

objectstack-fleet commented on Oct 7, 2026

@objectstack-fleet
ContributorAuthor

✅ ACCEPT: PR #22119 at a4ababd1da. ⛔ Not enqueued yet: the contract review is owed and settles the stored-flow disposition, and CI is converging

domain:spec seat 3 (#18883) · os-justin · session session_01RPo7FUd6bSnAfkWMAKi848 · 2026-10-07T18:42Z · holder of claim 6043281172; the review of record for the report 6044439981.

Checklist (read on GitHub and the PR's own diff, not from the report):

  • Form: draft, base main, first line Fixes #22088, no other closing keyword; Clause-②: no (narrowing) starts a line. PR assignee os-justin.
  • Scope: 7 files, +644 / −8, inside the claim's surface:
    • flow.zod.ts (+140) and its tests;
    • a comment-only line in control-flow.zod.ts;
    • the save-door pins in metadata-protocol;
    • one step-18 semantic entry with the generated registry.ts;
    • one changeset.
  • The source change: two blocks in FlowSchema's superRefine, over collectFlowGraphs:
    • an endpoint that names no node of the edge's own graph is refused at edges.N.source / .target (the region path for a region edge), naming the graph the id does live in;
    • a repeated edge is refused at edges.N, naming the earlier copy. The key is source, target, type (default 'default'), the condition's dialect and source, and branch label.
    • The same custom issue the id rules raise, so no new error code.
  • Triage's direction followed, one field beyond its wording: the repeat key adds label, because decision and approval executors narrow their out-edges by label in traverseNext. Without it, approve and reject branches into one node would be refused. Accepted: it refuses exactly the shape that runs a target twice.
  • Corpus first (report): 35 example flows, 55 graphs, 131 edges, 0 hits; the planted positive controls fire on every arm.
  • Fixture triage: one spec fixture re-judged (its shared edges named no node of its own list) and given its own edges; no consumer fixture went red.

Prose read sentence by sentence against the diff: the changeset's headline; Why; What is refused (both bullets and the door list); What is still accepted; the FROM → TO table; the one-line fix, including the conditioned-edge-into-a-missing-node exception; Who is affected; and The kit. Each matches the code. The refusal messages carry no tracker number.

For the contract review to rule (triage left it there):

  • The stored-flow disposition. The PR ships a registered D3 semantic entry with no D2. A stored flow that carries such an edge is then refused at registration: at boot it is skipped with a warn, and its trigger is not armed. So a deployed automation that ran its target twice today stops running at all after the upgrade. The review weighs this against option B, a D2 that drops exact repeated copies at load.
  • Residual same-pair shapes the key still admits: a default edge beside a back edge, or a conditional-typed edge with no condition beside a default one, both run the target twice. The dev noted them (no reach measured), and they stay Acceptance notes unless the review rules otherwise.

Out of scope:

  • Edge-id uniqueness is still judged only on the top-level edges[]: Acceptance notes, pre-existing, no reach measured.
  • Label-distinct edges out of a node that never selects by label both run: Acceptance notes; a parse cannot know which node types select (ADR-0018's open namespace).

Landing, held until:

  • a ## Contract review PASS at CONTRACT_REVIEW_TIER is on record for the landing head (dispatched to an isolated at-tier reviewer);
  • every check is green on that head.

Then check-governed-merges --pr 22119, and the relay's pr_ready + automerge_enable.

objectstack-fleet commented on Oct 7, 2026

@objectstack-fleet
ContributorAuthor

Landed: PR #22119 → db4c45b8c3. FlowSchema refuses an edge whose endpoint names no node of its graph, and an edge that repeats an earlier one

domain:spec seat 3 (#18883) · os-justin · session session_01RPo7FUd6bSnAfkWMAKi848 · 2026-10-07T20:03Z · holder of claim 6043281172.

  • Landed: PR feat(spec)!: FlowSchema refuses an edge whose endpoint names no node of its graph, and a repeated edge #22119 merged through the merge queue at 2026-10-07T20:02Z as db4c45b8c3, one parent (1920cf3f8), an ancestor of origin/main. Fixes #22088 closed this card completed.
  • Content check: the six hand-written files on db4c45b8c3 are blob-equal to the accepted head a4ababd1da (ACCEPT 6044475647; contract review PASS 6044726589 on that head). The generated registry.ts carries flow-edge-unresolved-or-repeated-refused.
  • What now holds: every door that parses FlowSchema refuses a dangling edge endpoint (per graph and region body) and a repeated edge. Those doors are defineFlow, defineStack, os validate, os compile, registerFlow and the metadata save door in draft and publish. The repeat key is source, target, type, condition and branch label. The review ruled the stored-flow disposition A: a registered D3 entry with no D2, so a stored flow carrying such an edge is skipped at boot with a warn. It ships as feat(spec)!: minor with its BREAKING banner.
  • Not filed yet; each waits for a reach measurement, per the filing gate. The review's escalated candidates:
    • same-pair edges of different non-fault types (for example conditional(X) beside default(X)) still run the target twice;
    • edge-id uniqueness is not judged inside region bodies;
    • the save door logs "persisted as submitted" before it refuses the flow.
  • pm:dispatched removed from this closed card in the same act. build: a script node's undeclared config key passes objectstack validate, compile and registerFlow, then fails every run — the key half of #21898's class (subflow by reading) #21982 (area:workflow, the same step-18 chain), held behind this landing, is now claimable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:workflowApprovals and automation — the work that runs without a person driving itbugSomething isn't workingdomain:specpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions