Skip to content

The fleet view draws every live worker, not only ship tasks - #61

Merged
kirangathani merged 6 commits into
mainfrom
fm/flow-show-all-workers-n8
Aug 13, 2026
Merged

kirangathani merged 6 commits into
mainfrom
fm/flow-show-all-workers-n8

Conversation

@kirangathani

@kirangathani kirangathani commented Aug 13, 2026 •

Copy link
Copy Markdown
Owner

The defect

The fleet pipeline view drew only ship tasks. Every other live worker was filtered into an out_of_scope list the renderer showed as a single dim count, so the captain saw 0 agents ... 1 running no pipeline with no agents in flight beneath it while a scout was demonstrably alive, and read the view as broken.

It was not broken. It was telling the truth about a set it had defined too narrowly, and a view whose body is empty while workers are running is indistinguishable from one that has failed.

The change

Liveness is now the whole membership test. Kind decides only the shape of the row.

drawn as carries
pipeline: true the full nine-cell pipeline block, 7 rows the run, its steps, its GitHub checks, its testing skips
pipeline: false a compact block, 3 rows: head, state, blank the worker's kind, its window, and one state object

The original concern is kept honest: a worker with no pipeline gets no pipeline boxes. Nine permanently empty boxes over a scout would be an invented journey, which is a worse lie than the omission was. pipeline is a field the snapshot states rather than a kind string the renderer matches on, so a kind the renderer has never heard of still lands on the right side of the question.

The compact row's state is read, not derived: bin/fm-crew-state.sh already owns reconciling a crew's current state out of its run step, its pane, and its append-only status log, so the collector calls it and passes its answer through split into its stated fields. A read that fails reports ok:false and the row says state not read; it never falls back to the status log's last line, which is a wake event and not a current state.

A quiet second mate is healthy and is not painted as a fault. AGENTS.md section 8 says its idle endpoint is normal, and bin/fm-crew-state.sh encodes the same rule by skipping the pane busy-check for kind=secondmate - so a quiet one reads unknown by construction. unknown is magenta everywhere else in this view, which is right where it means nobody could find out and wrong here where it means there is nothing to report. It renders dim idle, with the detail dropped. Every other state a second mate can report keeps its own colour, because those come from its status log and are real.

Blocks now come in two heights, so visibleRows() + resolveTop() are replaced by one scrollWindow(heights, avail, top, sel): how many blocks fit depends on which is first, and dividing available rows by a single constant would answer for a frame that is not being drawn. The captain's scroll rule is unchanged and is the three clauses in that function.

Schema bump, deliberately

fm-flow-snapshot.v1 -> v2. This is a genuine break in both directions, not an additive change:

  • agents changed meaning - it was the live ship tasks and is now every live worker - so a v1 consumer reading a v2 document would draw pipeline boxes over workers that have none.
  • out_of_scope is removed, not emitted empty. It existed to name the live workers agents excluded, and agents now excludes none, so an empty array left in place would be a field whose emptiness meant the opposite of what it used to.
  • omitted now covers dead records of any kind and carries each one's kind. A dead scout previously appeared in neither list and was dropped silently.

bin/fm-flow-tui.mjs refuses any other schema id outright, so the two ship together or neither runs. docs/flow-tui.md is updated to match; the :482-487 comment justifying the old filter is replaced by one describing the new behaviour.

Verification

Regression tests cover both directions, as required - a fix that only asserts the live worker is drawn would put a finished worker back on screen beside running ones.

  • tests/fm-flow-snapshot.test.sh (31 assertions): the fixture fleet gains a live scout, a live second mate, and a dead scout. The live ones must be agents; the dead one must still be held back in omitted with its kind. The two compact workers get real state/<id>.meta records over real directories, so what is asserted is bin/fm-crew-state.sh's actual bytes rather than a hand-written approximation of them. The read-only hash is taken again over the home that reader touches, because the compact path calls a whole second script per worker.
  • tests/fm-flow-tui.test.sh (51 assertions): the scout and second mate appear in the body; 3 agents counts them; no agents in flight prints only when nothing is live; a pipeline-less worker gets no stage labels and no box borders; a quiet second mate's row uses none of the three alarm colour slots while a blocked one keeps yellow; a v1 document is refused; and a fleet of both block heights fits its terminal at every size swept, 40 to 200 columns.

bin/fm-lint.sh clean. bin/fm-test.sh: 108 assigned, 108 run, 108 passed, 0 failed.

fm-supersession: v1 flow-show-all-workers-n8 4480c77 aWQJYW55CXRlc3RzL2ZtLWZsb3ctc25hcHNob3QudGVzdC5zaDo6c2hpcHMgd2l0aCBhIGxpdmUgZW5kcG9pbnQgYmVjb21lIGFnZW50czsgc2NvdXRzIGFuZCBkZWFkIGVuZHBvaW50cyBkbyBub3QKaWQJYW55CXRlc3RzL2ZtLWZsb3ctc25hcHNob3QudGVzdC5zaDo6YSBsaXZlIHdvcmtlciB3aXRoIG5vIHBpcGVsaW5lIGlzIG5hbWVkIHJhdGhlciB0aGFuIHNpbGVudGx5IGFic2VudAppZAlhbnkJdGVzdHMvZm0tZmxvdy1zbmFwc2hvdC50ZXN0LnNoOjplbWl0cyB0aGUgZm0tZmxvdy1zbmFwc2hvdC52MSBzY2hlbWEgaWQKaWRzCWZhaWxpbmcJdGVzdHMvZm0tZmxvdy1zbmFwc2hvdC50ZXN0LnNoOjoqCmlkcwlmYWlsaW5nCXRlc3RzL2ZtLWZsb3ctdHVpLnRlc3Quc2g6OioK 1b53efdf173662522823fe18503f5e75140204396f8e8b655bd84c5f855ea8b0

The fleet pipeline view filtered every non-ship live worker into an
out_of_scope list rendered as a single dim count, so a captain watching a
running scout saw "0 agents ... 1 running no pipeline" over "no agents in
flight" and read the view as broken.

Liveness is now the whole membership test and kind decides only the shape of
the row. A worker with no pipeline gets a compact three-row block - id, kind,
window, and the state read through bin/fm-crew-state.sh - and never gets
pipeline boxes it does not have. A secondmate reading unknown, which
bin/fm-crew-state.sh produces by construction for a quiet one, renders as dim
"idle" rather than as an alarm.

Blocks now come in two heights, so scrollWindow() solves the window start and
the block count together instead of dividing available rows by one constant.

Schema bumped to fm-flow-snapshot.v2: agents changed meaning and out_of_scope
is removed.
The snapshot suite gains a live scout, a live second mate and a DEAD scout in
one fixture fleet, so the live ones must be drawn and the dead one must still
be held back - a fix that only asserts the live direction would let a finished
worker back onto the screen. The two compact workers get real state/<id>.meta
records over real directories so their state is bin/fm-crew-state.sh's actual
bytes rather than a hand-written approximation, and the read-only hash is taken
again over the home that reader touches.

The renderer suite asserts the drawn rows, the absence of pipeline boxes for a
worker that has none, that the empty-fleet line prints only when nothing is
live, that a quiet second mate uses no alarm colour while a blocked one keeps
its own, and that a fleet of both block heights still fits its terminal at
every size swept.
A fleet of pipeline-less workers narrowed to 80 columns still carried
"stages 1-6 of 9" from the horizontal layout, describing cells that were not
on screen.
…changed

Three assertions were renamed for no reason beyond wording, which reads to the
kept-tests gate as the base losing them. The behaviour each asserts is
unchanged, so the base's own names are restored and the cases this branch adds
sit alongside them rather than replacing them.
@kirangathani

Copy link
Copy Markdown
Owner Author

Base assertions re-verified fails, and it needs a decision that is not mine

CI: missing=6 failing=60 on the first push. I have fixed the part that was my own damage; the residue is a real supersession of behaviour the base asserts, and AGENTS.md section 7 says that is never the worker's or firstmate's call.

Fixed (commit 6ee083d) — three names I changed for no reason

Three base assertions were renamed purely for wording while the behaviour they assert is unchanged. That reads to the gate as the base losing them. The base's own names are restored and the cases this branch adds now sit alongside them:

  • tests/fm-flow-snapshot.test.sh::a task whose recorded endpoint is gone is named in omitted, not drawn as an agent
  • tests/fm-flow-tui.test.sh::refuses input that is not a snapshot it understands
  • tests/fm-flow-tui.test.sh::the window moves only when the selector would otherwise leave it

missing should drop 6 -> 3.

The residue, and why it cannot be fixed in the test file

3 missing: — assertions whose stated behaviour this branch deliberately replaces. Keeping the name over changed behaviour is precisely the deception check 2 exists to catch, so none of these can honestly be restored:

assertion why it cannot survive
ships with a live endpoint become agents; scouts and dead endpoints do not the task is to make a live scout an agent
a live worker with no pipeline is named rather than silently absent it is now drawn, not named in out_of_scope
emits the fm-flow-snapshot.v1 schema id the schema is v2

60 failing: — every single one is in the base's own copies of tests/fm-flow-snapshot.test.sh and tests/fm-flow-tui.test.sh. None is a behaviour regression. Two mechanical causes:

  1. The base's renderer fixtures build fm-flow-snapshot.v1 documents; this branch's renderer refuses any other schema id, so every one of them exits 1.
  2. tests/lib.sh's fail exits the file. So the base's file dies at its first mismatch and every assertion after that point is recorded as lost — including ones with nothing to do with this change, e.g. every tracked bin/*.mjs passes node --check.

The whole branch's own suite is green (bin/fm-test.sh: 108 of 108 files), and the two rewritten files cover everything the base's covered plus the new behaviour.

The supersession is unavoidable, whatever is decided about the schema

Worth being explicit, because the obvious escape does not exist. This base assertion:

[ "$got" = "arm-lock-gate-q4,eager-dispatch-e2,no-run-yet-n1" ] || fail "unexpected agent set: $got"
pass "ships with a live endpoint become agents; scouts and dead endpoints do not"

runs against a fixture containing a live scout. Making that scout an agent is the whole task, so this assertion falls under any version of this change. Reverting the schema bump would shrink the failing count but cannot reach zero, and it would cost an honest wire format: out_of_scope would have to stay as an always-empty field whose emptiness means the opposite of what it used to.

Options

  1. Approve a supersession entry in data/supersessions/firstmate.md for the three named assertions (entry format in bin/fm-pr-merge.sh's header), and merge. Recommended: the supersession is exactly the behaviour change that was asked for, the branch's own suite is green, and the wire format stays honest.
  2. Keep fm-flow-snapshot.v1 and carry out_of_scope as an always-empty compatibility field. Fewer failing: entries, still at least one missing:, and a wire format that lies about what an empty out_of_scope means. I do not recommend it.
  3. Something else — I will implement whichever is chosen.

Nothing further is pushed until this is decided.

@kirangathani

Copy link
Copy Markdown
Owner Author

Merged origin/main in, re-verified, pushed

Head is now 4480c77, a merge commit (never a rebase).

  • The merge applied cleanly — no conflicts, so nothing was hand-resolved and the merge-resolution gate has nothing to catch. Verified lossless both ways: git diff HEAD origin/main and git diff origin/main HEAD --name-only each name only this branch's own six files, so none of #62's material was dropped and none of this branch's was.
  • bin/fm-lint.sh clean, 230 files.
  • bin/fm-test.sh: 109 of 109 test files passed.

One note on that suite run, recorded rather than buried: the first run after the merge reported tests/fm-afk-launch.test.sh failing. It did not reproduce — the re-run is the 109/109 above, and the file passes in isolation with every assertion green. It has no reference to anything this branch changes, bin/fm-afk-launch.sh likewise, and its last touch was ad9f3a7 (kunchenguid#490). It creates and kills six real tmux sessions, and #62 added a 109th test file which shifts the round-robin shard partition, so a local all-shards-on-one-machine run puts it against a different neighbour on a shared tmux server. CI runs each shard as its own ubuntu-latest job, so that contention does not arise there. Flagging it as a local-concurrency flake rather than claiming it never happened.

What is still needed, and it is not mine to do

bin/fm-supersession-attest.sh is firstmate-side only by its own header — it reads the master key and the captain's private approval record, and never runs in a worker's worktree. It also signs against the PR's current head, which is why it has to come after this push.

So: bin/fm-supersession-attest.sh attest flow-show-all-workers-n8, then re-run Base assertions re-verified.

The record needs to cover two finding classes. The three missing: are exact ids:

tests/fm-flow-snapshot.test.sh::ships with a live endpoint become agents; scouts and dead endpoints do not
tests/fm-flow-snapshot.test.sh::a live worker with no pipeline is named rather than silently absent
tests/fm-flow-snapshot.test.sh::emits the fm-flow-snapshot.v1 schema id

The 60 failing: all arise from a single cause, so they suit the ids: batch form — tests/fm-flow-snapshot.test.sh::* and tests/fm-flow-tui.test.sh::*. One suggestion on those two: pin kind: failing rather than leaving kind absent. An absent kind means any, which would silently excuse a future missing in those same files, and the whole point of the gate is that a lost assertion stays visible.

@kirangathani
kirangathani merged commit d213ff8 into main Aug 13, 2026
12 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant