@@ -73,6 +73,28 @@ oh-my-graph is **alpha software**. The graph YAML schema, the CLI, and the
7373 surfaces are unchanged: ` /api/cards ` , ` /api/graph ` and ` events.jsonl ` keep
7474 their shapes and gain no prose — a consumer counts live runs itself from the
7575 state token, as the dashboard's own page already does.
76+ - ** The spawn retry now waits long enough to be useful.** #214 gave the
77+ assessor a bounded retry for a CLI that never started, and #226 extended it to
78+ the planner. Both shipped with a ** 3-attempt, 300ms** bound — a 600ms window —
79+ and then two more lanes died on the same failure * after* the retry was in
80+ place:
81+
82+ ```
83+ assessor run: claude run: spawn failed: exec: "claude": executable file not found in $PATH
84+ planner run: claude run: spawn failed: exec: "claude": executable file not found in $PATH
85+ ```
86+
87+ Four occurrences in one day, two of them post-fix, say the bound was ** correct
88+ in shape and useless in size** : 600ms is less than a package manager takes to
89+ relink a binary. Widened to ** 5 attempts, 2s apart** — eight seconds of
90+ patience, which buys the common case. A machine that genuinely has no CLI
91+ installed still fails, eight seconds later, saying exactly what it said before.
92+
93+ Nothing else changed: only a spawn that never happened is retried. A refused
94+ reply, a non-zero exit, a timeout and a cancelled context still stop on the
95+ first answer.
96+
97+ ### Changed
7698
7799- ** An unresolvable ` {{ inputs.x }} ` or ` {{ artifacts.id }} ` now says that a
78100 merely-quoted placeholder is resolved too, and how to quote one.** The two
@@ -206,6 +228,61 @@ oh-my-graph is **alpha software**. The graph YAML schema, the CLI, and the
206228
207229### Documented
208230
231+ - ** Every rule in the batch-lane idiom now ends in a disposition: the check
232+ that enforces it, named, or one sentence saying why it is judgement**
233+ ([ ` graphs/backlog-batch.yaml ` ] ( graphs/backlog-batch.yaml ) ). The header stated
234+ seven hard-won rules and left a reader to guess which of them anything
235+ enforced — a rule with a test behind it read exactly like one no code has
236+ ever looked at.
237+
238+ Three rules are mechanically checked, all three as tests over ` graphs/ ` in
239+ ` internal/graph/shipped_graphs_test.go ` , so a violation fails THIS repo's
240+ build rather than warning at a user's own graph:
241+ ` TestIndependentLanesFailIndependently ` (rule 5 — two or more weakly
242+ connected components in the resolved graph must come with
243+ ` on_fail: continue ` ), ` TestASessionGateCitesTheColdSafeFragment ` (rule 3 — a
244+ node that resumes a session and retries must have spliced ` e2e-verify ` , where
245+ the cold-safe wording exists once), and
246+ ` TestAGatingReviewCarriesItsRecoveryArc ` , extended to rule 6's second half:
247+ it already refused a narrowed review check with no ` feedback: ` arc, and now
248+ equally refuses an arc on a node whose check still accepts ` FINDINGS: ` — an
249+ arc the failing verdict can never reach. Rule 3's risky shape keeps its
250+ existing advisory warning from ` handoff.LintSessions ` ; the test guards only
251+ what the warning cannot, that the wording has not been copied out by hand.
252+
253+ Rules 2, 4 and 7 stay prose, each saying why in the header: rule 2 would have
254+ to read prompt prose for an instruction, the predicate family this repo
255+ measured and rejected at 110 noise in 114 hits
256+ ([ ` docs/measurements/0213-tool-grant-predicate.md ` ] ( docs/measurements/0213-tool-grant-predicate.md ) );
257+ rule 4's subject is a lane's diff, which does not exist when a graph is
258+ loaded; and rule 7 asks whether a repeat is a shape or a difference in a key,
259+ which is the judgement it exists to provoke.
260+
261+ Rule 1 — "lanes must not share files" — stays prose too, and was measured
262+ before it was written off, in
263+ [ ` docs/measurements/0034-lane-file-ownership-predicate.md ` ] ( docs/measurements/0034-lane-file-ownership-predicate.md ) .
264+ Over 45 graphs and 216 resolved nodes, only one graph can fire the predicate
265+ at all (a planned graph may not declare a ` worktree: ` ), and the lexical form
266+ produced ** 1 hit, of which the hand-check made 1 noise** — both lanes cite
267+ ` CONTRIBUTING.md ` as the address of the commit-trailer convention and neither
268+ edits it. Verdict: DO-NOT-SHIP. The command is the address for those numbers:
269+
270+ ``` sh
271+ go run docs/measurements/0034-lane-file-ownership-predicate.go
272+ ```
273+
274+ What survives of rule 1 reads no paths at all and is a test:
275+ ` TestAWorktreeGraphLeavesNoNodeOutsideALane ` refuses a graph that declares
276+ lanes and leaves some node without one, since that node runs in the user's
277+ own tree and so shares files with every lane at once.
278+
279+ No new ` lint ` sweep and no new load error — a new warning owes a measured
280+ noise rate, and the corpus that could measure one is n=1 across everything
281+ this repo ships. The header also settles the fragment question so it is not
282+ reopened: the lane shape is already a fragment,
283+ [ ` graphs/fragments/gated-lane.yaml ` ] ( graphs/fragments/gated-lane.yaml ) , and
284+ no second lane fragment is coming. Nothing in the graph body changed, and
285+ both runtimes' ` lint ` output for the eight shipped graphs is unchanged.
209286- ** ADR 0033 — the run, not the node, is the unit of engine-run evidence, and
210287 ADR 0030 is deliberately not extended one level down**
211288 ([ ` docs/adr/0033-the-run-is-the-unit-of-evidence-not-the-node.md ` ] ( docs/adr/0033-the-run-is-the-unit-of-evidence-not-the-node.md ) ,
@@ -241,6 +318,37 @@ oh-my-graph is **alpha software**. The graph YAML schema, the CLI, and the
241318 while ` cmd/oh-my-graph/version.go:9 ` reads ` 0.11.0 ` . The clause that survived
242319 the deletion is the true half: a run that types nothing is byte-for-byte the
243320 run that shipped in v0.10.0.
321+ - ** ` graphs/backlog-batch.yaml ` 's header diagrammed four nodes the graph no
322+ longer has, and its rule 1 advised an edit that will not load.** ADR 0029
323+ folded lane A into a single ` use: gated-lane ` node, and commit ` d232ce4 `
324+ deleted ` dev-a ` /` e2e-a ` /` review-a ` /` pr-a ` — but that commit's diff on this
325+ file begins at ` @@ -45,15 +45,24 @@ ` , so it never reached the header. The
326+ runtime ids are ` lane-a/dev ` , ` lane-a/e2e ` , ` lane-a/review ` , ` lane-a/pr ` , as
327+ the file's own body comment already said (` graphs/backlog-batch.yaml:88 ` ) and
328+ as loading it prints:
329+
330+ ``` sh
331+ go run ./cmd/oh-my-graph run graphs/backlog-batch.yaml --dry-run \
332+ --input repo=/tmp --input checks_command=" make local" \
333+ --input task_a=x --input task_b=y
334+ # Graph "backlog-batch" (8 nodes): lane-a/dev, lane-a/e2e, lane-a/review,
335+ # lane-a/pr, dev-b, e2e-b, review-b, pr-b
336+ ```
337+
338+ The second line was worse than stale. Rule 1 offered "make dev-b depend on
339+ ` pr-a ` " as the way to serialize two overlapping tasks, and `depends_on:
340+ [ pr-a] ` is a load error — ` depends_on unknown node`,
341+ ` internal/graph/validate.go:454 ` — so a reader who took the advice got a
342+ graph that would not run. It now names ` lane-a/pr `
343+ (` graphs/backlog-batch.yaml:16 ` ). ` init ` emits this file and users copy it,
344+ which is why a stale comment here is a stale instruction to a stranger.
345+
346+ Comments only: no node, prompt, or key changed, the graph still validates at
347+ 8 nodes, and no verdict clause moved, so the qualifier sweep's counts are
348+ untouched. The remaining thirteen shipped graphs and fragments were audited
349+ against v0.11.0 in the same pass and left alone — none of them names ` auto ` ,
350+ so neither ` --accept-loaded-user-config ` nor anything else new in v0.11.0
351+ reaches them.
244352
245353## [ v0.11.0] - 2026-08-21
246354
0 commit comments