Skip to content

Commit d69dccb

Browse files
committed
Merge remote-tracking branch 'origin/main' into lane-inflight
# Conflicts: # CHANGELOG.md
2 parents f9c0e48 + d250d8b commit d69dccb

8 files changed

Lines changed: 3098 additions & 23 deletions

CHANGELOG.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)