fix(lanes): count every pipeline run, not one per lane
Lane Pipeline Pass Rates read `lanes.stages`, which is a snapshot of the
pass in flight: re-entering a node overwrites its record, and moving
backwards deletes every record ahead of the new stage. The report treated
one lane row as one run, so shipping ten features through a lane still
counted as one, and no node could ever exceed 1/1. History survived only
via `lane_features`, which is written when a feature is activated — a
path most users never take. The panel was not wrong, it was structurally
incapable of counting.
Add `lane_stage_events`, an append-only log written by every declaration
and every accepted detection, and aggregate from that. A run is one pass
through the template: the log starts a new one when the lane moves
backwards or leaves the template's last node. Existing snapshots are
seeded as one run each on first boot, so upgrading does not read as
"never run". Templates a lane is configured with are still listed at
zero — a pipeline going unused has to be visible as such.
Count inferred reaches too, in their own column and their own colour.
Detection stays out of the pass rate ("inference is never evidence"), but
reporting a stage the hooks demonstrably saw as "never reached" is what
made the panel look broken to anyone who works without running the
pipeline commands.
Also reject a stage naming no node of the lane's pipeline (400 EBADSTAGE,
listing the ones that exist) instead of storing it with a warning nobody
reads. A real lane had accumulated five such keys, one of them `test` on
a ship-feature lane whose CI node is `gates` — which is why that row read
never-reached while the stage had in fact been declared. Aliases still
resolve, so shorthand is unaffected.
This commit is contained in:
+12
-2
@@ -687,8 +687,18 @@ things beyond the ordinary transition:
|
||||
The node the declaration lands on keeps its own record (its evidence and
|
||||
`result` survive), and `stage_since` restarts, so time-on-phase measures this
|
||||
pass. Records stored under a key matching **no** node in the pipeline are left
|
||||
untouched — their position is unknown, so they cannot be proven to be ahead;
|
||||
for the same reason, declaring a stage that matches no node prunes nothing.
|
||||
untouched — their position is unknown, so they cannot be proven to be ahead.
|
||||
Such keys only exist on lanes that predate validation: `POST
|
||||
/api/lanes/:id/stage` now rejects a stage naming no node of the lane's pipeline
|
||||
with **400 `EBADSTAGE`** and lists the ones that exist. Aliases still resolve,
|
||||
so this catches typos and names borrowed from another template — `agenthub
|
||||
stage test` on a `ship-feature` lane, whose CI node is `gates` — which used to
|
||||
be stored, warned about on stderr, and then counted nowhere.
|
||||
|
||||
Every declaration is also appended to `lane_stage_events`, the append-only log
|
||||
the pass-rate report reads. `lanes.stages` describes the pass in flight and is
|
||||
overwritten and truncated by the rules above; the log is what remembers that
|
||||
the previous pass happened at all.
|
||||
|
||||
Going back this way is a *declaration*, not a reset: the lane, its worktree, and
|
||||
its archived features are untouched. To start a genuinely separate piece of work
|
||||
|
||||
Reference in New Issue
Block a user