Skip to content

fix(bin): fold routed activity without forking per status line - #6

Merged
nathan-rosquist merged 6 commits into
mainfrom
fm/fm-snapshot-activity-fork-r1
Sep 19, 2026
Merged

nathan-rosquist merged 6 commits into
mainfrom
fm/fm-snapshot-activity-fork-r1

Conversation

@nathan-rosquist

@nathan-rosquist nathan-rosquist commented Sep 18, 2026 •

Copy link
Copy Markdown
Owner

Intent

Fix the first of the three hazards found by the wall-clock budget audit at
data/fm-budget-audit-w1/report.md, section 3.1. Read that section before
starting; it is the evidence base and it names the fix precisely.

The substance, so this brief stands alone:

bounded_parent_activities_json (bin/fm-fleet-snapshot.sh:1488) is the only
reader of a secondmate's parent-channel activity, and it runs under
FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT, a 2-second budget at
bin/fm-fleet-snapshot.sh:1558. On this platform that work was measured at
3.26s with 20 status lines and 17.66s with 120. The budget is therefore blown
every single time, and gets worse as the log grows.

The consequence is the reason this was funded ahead of the other two. On
timeout the function returns well-formed JSON carrying
available:false, reasons:["timeout"] and the snapshot proceeds normally, so
every secondmate's routed-activity evidence is dropped with no error, no
notification and no diagnostic line. It does not self-heal on the next poll.
It is also load-correlated in the worst direction: the busiest mate has the
longest log and is therefore the most reliably invisible.

The code is correct - it produced fully correct output at both input sizes.
Only the clock is wrong. Raising the number is explicitly NOT the fix, because
the cost is super-linear in log size and any larger constant fails again later,
just as silently.

This is portable robustness hardening rather than a Windows workaround: any
sufficiently slow or loaded host reaches the same threshold.

What Changed

  • _fm_key_at_note_head, status_line_note, _fm_decision_key, and _fm_decision_drop in bin/fm-classify-lib.sh now take an optional out-var and assign with printf -v instead of only printing, and _fm_decision_drop splits its record set with parameter expansion rather than a here-document. _fm_status_open_activities_stream calls all of them through the out-var forms, so the routed-activity fold no longer spends four command substitutions per status line; the out-var form of _fm_decision_drop also returns the set newline-terminated, dropping the caller's manual re-append.
  • Added tests/fm-classify-activity-fold-cost.test.sh, which pins the fold's cost shape rather than its output: it counts child-process CPU from times (no clock, no forks in the measurement path) across the real public fold at the shipped 256-line window and at 1024 lines, requiring the fold to stay 8x under one bare subshell per line at both sizes.
  • bin/fm-test-run.sh registers the new script with a 7423 ms duration hint and routes bin/fm-classify-lib.sh changes to the fold-cost and decision-key scripts alongside the watcher-wake-lock family; docs/fm-test-portable-shards.md records that hint as a labelled local stopgap pending a CI refresh.

Risk Assessment

✅ Low: The change is a semantics-preserving hot-path optimization that I verified byte-identical against the base across both folds, the streaming form, every incremental prefix of a mixed corpus and a wide set of malformed/edge status lines, it delivers the intent's required fix (80.2 s -> 1.82 s at 256 lines) without touching the timeout the intent forbids raising, its regression suite asserts observable fold output plus a clock-free fork invariant rather than source text, and the runner wiring, hint row and provenance are internally consistent; the only outstanding concern is a residual cost axis already recorded as out of scope.

Testing

I derived the scenarios from the intent's stated hazard - a secondmate's routed-activity evidence silently dropped because the fold spent a process per status line under a fixed 2 s budget - and drove every one of them against the real product on this Windows/Cygwin host, where forks are expensive enough for the defect to be visible. Baseline: the new fold-cost suite and the existing decision-key suite both run green on the target, and the fold-cost suite fails as designed when pointed at the pre-fix classifier, so it is a real fail-before/pass-after regression test rather than a tautology. End-to-end, bin/fm-fleet-snapshot.sh --json over a fixture fleet with a 256-line parent-channel log went from available:false, reasons:["timeout"] with zero records to available:true with all nine phase records, and bin/fm-bearings-snapshot.sh dropped its "secondmate parent activity evidence unavailable for 1 record(s)" omission - the operator-visible half of the symptom. The read cost, timed exactly as the snapshot runs it, went from 7.2/32.3/69.9 s at 20/120/256 lines to 3.19/2.89/3.12 s, i.e. flat instead of super-linear, with identical records at every size. Adversarially, raising the budget 10x to 20 s over a 1024-line window still times out pre-fix and succeeds post-fix, which is the intent's claim that a larger constant is not the fix; and a byte-level differential over three crafted corpora confirms the out-var rewrite, including the _fm_decision_drop newline change and prefix-sharing keys, changes no classifier output. I also confirmed the changed-file map now routes a classify-lib edit to both classifier suites. Two things I could not clear locally and reported as informational only: at the shipped 2 s budget this host still times out post-fix, which I measured down to three jq startups (~1.7 s) rather than the fold (~0.24 s) and which matches the separately-filed out-of-scope residual; and --check-coverage exits 1 identically at base and target here from a comm/locale mismatch, so CI owns that verdict. No screenshot or rendered-UI artifact applies: every surface this change touches is CLI/JSON and TOON text output, captured as transcripts instead.

  • Live validation: ✅ go - 8 of 8 scenarios driven live against the product
Scenario Result Live Evidence
A secondmate's routed activity survives a full 256-line parent-channel log: the fleet snapshot publishes available:true with all nine phase records, where the pre-fix build published available:false,… ✅ pass live bin/fm-fleet-snapshot.sh --json driven over a fixture fleet home (drive-parent-activity.sh); snapshot-fixed-timeout10.txt vs snapshot-base-timeout10.txt
The routed-activity read stops growing with the log: cost is flat at 20/120/256 lines instead of super-linear, and returns the same nine records at every size ✅ pass live fold-cost-table.sh timing bounded_parent_activities_json's own child script: base 7192/32290/69890 ms vs fixed 3193/2894/3120 ms; parent-activity-read-cost.txt
The operator reading Bearings no longer sees the fleet's routed-activity evidence declared unavailable ✅ pass live bin/fm-bearings-snapshot.sh over the same fixture fleet: omitted[9] carrying "secondmate parent activity evidence unavailable for 1 record(s)" at base, omitted[8] without it after; bearings-base-256…
Adversarial - raising the timeout constant is not a fix: with the budget raised 10x to 20 s over a 1024-line window the pre-fix read still times out silently, while the post-fix read publishes the who… ✅ pass live FM_SNAPSHOT_PARENT_ACTIVITY_LINES=1024 snapshot runs at a 20 s budget against both classifiers; raising-the-constant-is-not-the-fix.txt
The new cost suite genuinely reproduces the defect: it fails against the pre-fix classifier and passes against the fixed one ✅ pass live bash tests/fm-classify-activity-fold-cost.test.sh against a base-classifier tree (not ok - wide fold ... it is forking per status line again, exit 1) and against the target (both cases ok); fold-c…
Adversarial - the out-var and drop rewrite changes no classifier output: prefix-sharing keys are not co-dropped, the open set empties and refills correctly, and malformed/empty slugs, corr tags and mi… ✅ pass live byte-level differential of status_open_activities / status_open_decisions / status_open_decisions_incremental, named-file and streaming forms, over three crafted corpora; base-vs-fixed-semantics-diffe…
The existing decision-key semantics suite still passes on the rewritten helpers ✅ pass live bash tests/fm-classify-decision-key.test.sh - 20 cases green, covering both stated-key positions, malformed-vs-missing slugs, note-head key stripping and the incremental fold
A developer editing bin/fm-classify-lib.sh and running the local changed-file gate gets both classifier suites selected, so a reverted fold or a semantics regression is caught locally ✅ pass live bash bin/fm-test-run.sh --list --changed with bin/fm-classify-lib.sh modified; changed-file-selection.txt lists fm-classify-activity-fold-cost.test.sh and fm-classify-decision-key.test.sh alongside…
Evidence: Evidence index for this run

Source: Evidence index for this run

# Live validation: the routed-activity fold no longer blows the parent-activity budget

Branch `fm/fm-snapshot-activity-fork-r1`, base `f1fc96e`, target `b452807`.
Host: Windows 11 Pro / Cygwin bash 5.3 — a host where a bare fork costs tens of
milliseconds, which is why the defect is visible here at all.

Everything below was driven against the real product: `bin/fm-fleet-snapshot.sh`
and `bin/fm-bearings-snapshot.sh` over a fixture fleet home whose registered
secondmate has a routed parent-channel activity log. "base" runs are the same
product with only `bin/fm-classify-lib.sh` reverted to the base commit.

| file | what it shows |
| --- | --- |
| `parent-activity-read-cost.txt` | The read cost, timed as the snapshot runs it: base 7.2 s / 32.3 s / 69.9 s at 20 / 120 / 256 lines, versus 3.19 s / 2.89 s / 3.12 s after the fix. Same 9 records every time. The cost is now flat in log length. |
| `snapshot-base-timeout10.txt` | Pre-fix `fm-fleet-snapshot.sh --json`: at 20 lines `available:true`; at 256 lines `available:false, reasons:["timeout"]`, zero records, snapshot otherwise normal — the silent drop. |
| `snapshot-fixed-timeout10.txt` | Post-fix, same fixture and budget: `available:true` at both sizes, all nine phase keys published. |
| `bearings-base-256.txt` / `bearings-fixed-256.txt` | The operator-visible Bearings report for the same fleet: `omitted[9]` carrying "secondmate parent activity evidence unavailable for 1 record(s)" before, `omitted[8]` without it after. |
| `raising-the-constant-is-not-the-fix.txt` | Adversarial: budget raised 10x to 20 s over a 1024-line window. Pre-fix still times out; post-fix publishes the whole window. A larger constant is not a fix. |
| `fold-cost-suite-fails-before-the-fix.txt` | The new regression suite run against the pre-fix classifier: `not ok - wide fold ... it is forking per status line again`. It passes on the fixed classifier, so it fails before and passes after. |
| `base-vs-fixed-semantics-differential.txt` | `status_open_activities`, `status_open_decisions` and `status_open_decisions_incremental`, named-file and streaming forms, byte-identical pre-fix vs post-fix over two crafted corpora (both key positions, malformed and empty slugs, corr tags, mid-note key prose, closing verbs, blank lines, reopening). |
| `drop-rewrite-adversarial-corpus.txt` | Adversarial for the `_fm_decision_drop` rewrite specifically: prefix-sharing keys (`phase1`/`phase10`/`phase100`, `a`/`ab`), the open set emptying and refilling, an empty note. Byte-identical to pre-fix. |
| `changed-file-selection.txt` | `bin/fm-test-run.sh --list --changed` with `bin/fm-classify-lib.sh` modified now selects both `fm-classify-activity-fold-cost.test.sh` and `fm-classify-decision-key.test.sh` alongside the watcher family. |
| `snapshot-fixed-shipped-2s-windows.txt` | The known residual: at the shipped 2 s budget this Cygwin host still times out post-fix. `parent-activity-read-cost.txt` attributes that to three jq startups (~1.7 s) and bash startup plus sourcing the classifier (~0.4 s); the fold itself is ~0.24 s of the 3.12 s. Separately filed as residual budget headroom and out of scope here. |

## Reproducing

`drive-parent-activity.sh <bin-dir> <activity-timeout-s> <lines...>` and
`drive-bearings-report.sh` (same arguments) build the fixture fleet and drive the
real product. `fold-cost-table.sh` times the snapshot's own parent-activity child
script. A "base" bin dir is made with
`cp -r bin /tmp/pa/bin-base && git show f1fc96e:bin/fm-classify-lib.sh > /tmp/pa/bin-base/fm-classify-lib.sh`,
and `/tmp/pa/child.sh` is the child script extracted verbatim from
`bounded_parent_activities_json`.
Evidence: Parent-activity read cost, pre-fix vs post-fix, timed as the snapshot runs it

Source: Parent-activity read cost, pre-fix vs post-fix, timed as the snapshot runs it

lib lines rc cost_ms records_in_window base 20 0 7192 9 base 120 0 32290 9 base 256 0 69890 9 fixed 20 0 3193 9 fixed 120 0 2894 9 fixed 256 0 3120 9 # Where the post-fix 3.0 s still goes on this host (none of it the fold): bash -c + source bin/fm-classify-lib.sh : 372 ms the same, plus the whole 256-line fold : 614 ms the three jq startups the child also pays : 1710 ms

# Cost of the parent-activity read, timed exactly as bin/fm-fleet-snapshot.sh
# runs it (the bounded_parent_activities_json child), pre-fix vs post-fix.
# Host: Windows 11 / Cygwin bash 5.3. Shipped budget is 2000 ms.

lib     lines  rc      cost_ms   records_in_window
base    20     0       7192      9
base    120    0       32290     9
base    256    0       69890     9
fixed   20     0       3193      9
fixed   120    0       2894      9
fixed   256    0       3120      9

# Where the post-fix 3.0 s still goes on this host (none of it the fold):
  one bash -c startup                        : 912 ms
  bash -c + source bin/fm-classify-lib.sh    : 372 ms
  the same, plus the whole 256-line fold     : 614 ms
  the three jq startups the child also pays  : 1710 ms
  (plus stat, two tails and two awks)
Evidence: fm-fleet-snapshot.sh --json, pre-fix: routed activity dropped at 256 lines

Source: fm-fleet-snapshot.sh --json, pre-fix: routed activity dropped at 256 lines

--- 256 routed status lines --- activity_scan : {"records":[],"available":false,"input_truncated":false,"retained_truncated":false,"reasons":["timeout"],"lines_in_window":0,"records_in_window":0} open_activities : []

snapshot bin: /tmp/pa/bin-base
FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT=10 (shipped default is 2)

--- 20 routed status lines (snapshot wall 38902 ms) ---
activity_scan   : {"records":[{"key":"phase2","verb":"working","summary":"step 11 under way on the parent channel"},{"key":"phase3","verb":"working","summary":"step 12 under way on the parent channel"},{"key":"phase4","verb":"working","summary":"step 13 under way on the parent channel"},{"key":"phase5","verb":"working","summary":"step 14 under way on the parent channel"},{"key":"phase6","verb":"working","summary":"step 15 under way on the parent channel"},{"key":"phase7","verb":"working","summary":"step 16 under way on the parent channel"},{"key":"phase8","verb":"working","summary":"step 17 under way on the parent channel"},{"key":"phase0","verb":"working","summary":"step 18 under way on the parent channel"},{"key":"phase1","verb":"working","summary":"step 19 under way on the parent channel"}],"available":true,"input_truncated":false,"retained_truncated":false,"reasons":[],"lines_in_window":20,"records_in_window":9}
open_activities : ["phase2=working","phase3=working","phase4=working","phase5=working","phase6=working","phase7=working","phase8=working","phase0=working","phase1=working"]

--- 256 routed status lines (snapshot wall 42653 ms) ---
activity_scan   : {"records":[],"available":false,"input_truncated":false,"retained_truncated":false,"reasons":["timeout"],"lines_in_window":0,"records_in_window":0}
open_activities : []
Evidence: fm-fleet-snapshot.sh --json, post-fix: all nine phase records published at 256 lines

Source: fm-fleet-snapshot.sh --json, post-fix: all nine phase records published at 256 lines

--- 256 routed status lines --- activity_scan : {"records":[{"key":"phase4","verb":"working","summary":"step 247 under way on the parent channel"}, ... ],"available":true,"input_truncated":false,"retained_truncated":false,"reasons":[],"lines_in_window":256,"records_in_window":9} open_activities : ["phase4=working","phase5=working","phase6=working","phase7=working","phase8=working","phase0=working","phase1=working","phase2=working","phase3=working"]

snapshot bin: /c/Users/nathan.rosquist/.no-mistakes/worktrees/20139a6921be/01M2TY5353E1WGP7ANGEETTTHR/bin
FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT=10 (shipped default is 2)

--- 20 routed status lines (snapshot wall 36566 ms) ---
activity_scan   : {"records":[{"key":"phase2","verb":"working","summary":"step 11 under way on the parent channel"},{"key":"phase3","verb":"working","summary":"step 12 under way on the parent channel"},{"key":"phase4","verb":"working","summary":"step 13 under way on the parent channel"},{"key":"phase5","verb":"working","summary":"step 14 under way on the parent channel"},{"key":"phase6","verb":"working","summary":"step 15 under way on the parent channel"},{"key":"phase7","verb":"working","summary":"step 16 under way on the parent channel"},{"key":"phase8","verb":"working","summary":"step 17 under way on the parent channel"},{"key":"phase0","verb":"working","summary":"step 18 under way on the parent channel"},{"key":"phase1","verb":"working","summary":"step 19 under way on the parent channel"}],"available":true,"input_truncated":false,"retained_truncated":false,"reasons":[],"lines_in_window":20,"records_in_window":9}
open_activities : ["phase2=working","phase3=working","phase4=working","phase5=working","phase6=working","phase7=working","phase8=working","phase0=working","phase1=working"]

--- 256 routed status lines (snapshot wall 45059 ms) ---
activity_scan   : {"records":[{"key":"phase4","verb":"working","summary":"step 247 under way on the parent channel"},{"key":"phase5","verb":"working","summary":"step 248 under way on the parent channel"},{"key":"phase6","verb":"working","summary":"step 249 under way on the parent channel"},{"key":"phase7","verb":"working","summary":"step 250 under way on the parent channel"},{"key":"phase8","verb":"working","summary":"step 251 under way on the parent channel"},{"key":"phase0","verb":"working","summary":"step 252 under way on the parent channel"},{"key":"phase1","verb":"working","summary":"step 253 under way on the parent channel"},{"key":"phase2","verb":"working","summary":"step 254 under way on the parent channel"},{"key":"phase3","verb":"working","summary":"step 255 under way on the parent channel"}],"available":true,"input_truncated":false,"retained_truncated":false,"reasons":[],"lines_in_window":256,"records_in_window":9}
open_activities : ["phase4=working","phase5=working","phase6=working","phase7=working","phase8=working","phase0=working","phase1=working","phase2=working","phase3=working"]
Evidence: Operator's Bearings report, pre-fix: routed-activity evidence disclosed as unavailable

Source: Operator's Bearings report, pre-fix: routed-activity evidence disclosed as unavailable

omitted[9]{surface,reveal}: ... secondmate parent activity evidence unavailable for 1 record(s),inspect the parent status logs live PR discovery + checks,"--include-prs"

\### BASE lib (pre-fix): operator's Bearings report, 256 routed status lines, 15s activity budget
snapshot bin: /tmp/pa/bin-base
FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT=15 (shipped default is 2)
FM_SNAPSHOT_PARENT_ACTIVITY_LINES=256 (shipped default is 256)

--- operator-visible Bearings report, 256 routed status lines (wall 65039 ms) ---
schema: fm-bearings.v1
home: fm-parent-activity-drive.hdXduw/home-256
generated: "2026-07-11T18:00:00Z"
prs: "not_requested (run: /bearings include PRs)"
contributions: 
  scope: owned contributions per home
  known: 0
  checked: 0
  counts: 
    captain: 0
    fleet: 0
    maintainer: 0
    nobody: 0
  complete: false
  proven_clear: false
  unmeasured_homes: 1
  unreadable_records: 1
  unmeasured: 0
  stale_verdicts: 0
  missing_verdicts: 0
  captain_omitted: 0
  captain: []
in_flight: []
secondmates[2]{id,state,doing,provenance,freshness,age_seconds,contradiction,reason}:
  (registry),unknown,registered secondmate table read timed out,registered-table,unavailable,null,false,registered secondmate table read timed out
  domain-alpha,unknown,secondmate registration is unknown because the registry read is incomplete or unavailable,parent-event-fallback,historical-event,0,false,secondmate registration is unknown because the registry read is incomplete or unavailable
secondmate_reconcile: []
decisions_open: []
landed: []
gates: []
reports: []
recorded_prs: []
unhealthy_endpoints[1]{id,backend,target,exists,agent}:
  domain-alpha,tmux,"firstmate:fm-domain-alpha",false,unknown
omitted[9]{surface,reveal}:
  backlog item bodies,"--fields bodies"
  task paths,"--fields paths"
  watch/steer actions,"--fields actions"
  healthy endpoint detail,"--fields endpoints"
  full scout-report inventory,"--all-reports"
  "secondmate home(s) with unreadable structured state: 1",inspect the listed secondmate home ledgers
  "secondmate registry unavailable: registered secondmate table read timed out",inspect data/secondmates.md
  secondmate parent activity evidence unavailable for 1 record(s),inspect the parent status logs
  live PR discovery + checks,"--include-prs"
Evidence: Operator's Bearings report, post-fix: that omission is gone

Source: Operator's Bearings report, post-fix: that omission is gone

omitted[8]{surface,reveal}: ... "secondmate registry unavailable: registered secondmate table read timed out",inspect data/secondmates.md live PR discovery + checks,"--include-prs"

\### FIXED lib (this change): operator's Bearings report, 256 routed status lines, 15s activity budget
snapshot bin: /c/Users/nathan.rosquist/.no-mistakes/worktrees/20139a6921be/01M2TY5353E1WGP7ANGEETTTHR/bin
FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT=15 (shipped default is 2)
FM_SNAPSHOT_PARENT_ACTIVITY_LINES=256 (shipped default is 256)

--- operator-visible Bearings report, 256 routed status lines (wall 63617 ms) ---
schema: fm-bearings.v1
home: fm-parent-activity-drive.5TsOEH/home-256
generated: "2026-07-11T18:00:00Z"
prs: "not_requested (run: /bearings include PRs)"
contributions: 
  scope: owned contributions per home
  known: 0
  checked: 0
  counts: 
    captain: 0
    fleet: 0
    maintainer: 0
    nobody: 0
  complete: false
  proven_clear: false
  unmeasured_homes: 1
  unreadable_records: 1
  unmeasured: 0
  stale_verdicts: 0
  missing_verdicts: 0
  captain_omitted: 0
  captain: []
in_flight: []
secondmates[2]{id,state,doing,provenance,freshness,age_seconds,contradiction,reason}:
  (registry),unknown,registered secondmate table read timed out,registered-table,unavailable,null,false,registered secondmate table read timed out
  domain-alpha,unknown,secondmate registration is unknown because the registry read is incomplete or unavailable,parent-event-fallback,historical-event,0,false,secondmate registration is unknown because the registry read is incomplete or unavailable
secondmate_reconcile: []
decisions_open: []
landed: []
gates: []
reports: []
recorded_prs: []
unhealthy_endpoints[1]{id,backend,target,exists,agent}:
  domain-alpha,tmux,"firstmate:fm-domain-alpha",false,unknown
omitted[8]{surface,reveal}:
  backlog item bodies,"--fields bodies"
  task paths,"--fields paths"
  watch/steer actions,"--fields actions"
  healthy endpoint detail,"--fields endpoints"
  full scout-report inventory,"--all-reports"
  "secondmate home(s) with unreadable structured state: 1",inspect the listed secondmate home ledgers
  "secondmate registry unavailable: registered secondmate table read timed out",inspect data/secondmates.md
  live PR discovery + checks,"--include-prs"
Evidence: Adversarial: a 10x larger budget is still not a fix

Source: Adversarial: a 10x larger budget is still not a fix

### BASE lib (pre-fix): 1024-line window, budget raised 10x to 20s activity_scan : {"records":[],"available":false,...,"reasons":["timeout"],"records_in_window":0} ### FIXED lib (this change): same window, same 20s budget activity_scan : {...,"available":true,"reasons":[],"lines_in_window":1024,"records_in_window":9}

\### BASE lib (pre-fix): 1024-line window, budget raised 10x to 20s
snapshot bin: /tmp/pa/bin-base
FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT=20 (shipped default is 2)
FM_SNAPSHOT_PARENT_ACTIVITY_LINES=1024 (shipped default is 256)

--- 1024 routed status lines (snapshot wall 58850 ms) ---
activity_scan   : {"records":[],"available":false,"input_truncated":false,"retained_truncated":false,"reasons":["timeout"],"lines_in_window":0,"records_in_window":0}
open_activities : []

\### FIXED lib (this change): same window, same 20s budget
snapshot bin: /c/Users/nathan.rosquist/.no-mistakes/worktrees/20139a6921be/01M2TY5353E1WGP7ANGEETTTHR/bin
FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT=20 (shipped default is 2)
FM_SNAPSHOT_PARENT_ACTIVITY_LINES=1024 (shipped default is 256)

--- 1024 routed status lines (snapshot wall 37170 ms) ---
activity_scan   : {"records":[{"key":"phase7","verb":"working","summary":"step 1015 under way on the parent channel"},{"key":"phase8","verb":"working","summary":"step 1016 under way on the parent channel"},{"key":"phase0","verb":"working","summary":"step 1017 under way on the parent channel"},{"key":"phase1","verb":"working","summary":"step 1018 under way on the parent channel"},{"key":"phase2","verb":"working","summary":"step 1019 under way on the parent channel"},{"key":"phase3","verb":"working","summary":"step 1020 under way on the parent channel"},{"key":"phase4","verb":"working","summary":"step 1021 under way on the parent channel"},{"key":"phase5","verb":"working","summary":"step 1022 under way on the parent channel"},{"key":"phase6","verb":"working","summary":"step 1023 under way on the parent channel"}],"available":true,"input_truncated":false,"retained_truncated":false,"reasons":[],"lines_in_window":1024,"records_in_window":9}
open_activities : ["phase7=working","phase8=working","phase0=working","phase1=working","phase2=working","phase3=working","phase4=working","phase5=working","phase6=working"]
Evidence: The new cost suite fails against the pre-fix classifier

Source: The new cost suite fails against the pre-fix classifier

# tests/fm-classify-activity-fold-cost.test.sh run against the PRE-FIX bin/fm-classify-lib.sh (base f1fc96e) not ok - wide fold: the fold charged 11354cs of child CPU over 1024 lines, against a budget of one subshell per line (160cs per 64 forks) with a 8x margin - it is forking per status line again exit=1

# tests/fm-classify-activity-fold-cost.test.sh run against the PRE-FIX bin/fm-classify-lib.sh (base f1fc96e)
not ok - wide fold: the fold charged 11354cs of child CPU over 1024 lines, against a budget of one subshell per line (160cs per 64 forks) with a 8x margin - it is forking per status line again
exit=1
Evidence: Base-vs-fixed classifier output differential (two corpora, three fold entry points)

Source: Base-vs-fixed classifier output differential (two corpora, three fold entry points)

IDENTICAL base vs fixed: status_open_activities (named-file form and streaming form) BYTE-IDENTICAL base vs fixed: status_open_decisions (189 bytes of output, named-file and streaming forms) BYTE-IDENTICAL base vs fixed: status_open_decisions_incremental (189 bytes of output, named-file and streaming forms)

=== base-vs-fixed differential over a crafted status corpus ===
corpus:
     1	working [key=phase1]: first phase under way
     2	[key=phase2] working: colon-form key before the verb
     3	needs-decision [key=release]: choose release A or B
     4	working: keyless working line
     5	blocked [key=gate]: waiting on external legal
     6	resolved [key=release]: went with A
     7	working [key=bad slug!]: malformed stated key
     8	working [key=]: empty stated key
     9	done [key=phase1]: first phase complete
    10	working [corr=abc123] [key=phase3]: corr tag ahead of the key
    11	working [corr=zzz]: corr tag with no key
    12	note about [key=phase9] mentioned mid-note but not a tag
    13	captain-held [key=gate]: captain took it
    14	working [key=phase.dot-dash_ok]: full legal slug charset
    15	failed [key=phase3]: third phase failed
    16	working [key=phase4]: fourth phase under way
    17	   
    18	working [key=phase4]: fourth phase re-opened later

IDENTICAL base vs fixed: status_open_activities (named-file form and streaming form)
IDENTICAL base vs fixed: status_open_decisions (named-file form and streaming form)
IDENTICAL base vs fixed: status_open_decisions_incremental (named-file form and streaming form)

records the fixed fold publishes for that corpus (TAB shown as ->):
default -> working -> corr tag with no key
phase.dot-dash_ok -> working -> full legal slug charset
phase4 -> working -> fourth phase re-opened later

byte-level check including the trailing newline:
byte-identical
0000160   s   e       r   e   -   o   p   e   n   e   d       l   a   t
0000200   e   r  \n
0000203
=== decisions differential over an open-decision corpus ===
     1	needs-decision [key=release]: choose release A or B
     2	[key=schema] needs-decision: colon-form key, still open
     3	needs-decision: keyless decision, still open
     4	blocked [key=gate]: waiting on external legal
     5	needs-decision [key=bad slug!]: malformed key must not fold as default
     6	working [key=phase1]: unrelated work
     7	needs-decision [key=closed-one]: will be resolved below
     8	resolved [key=closed-one]: picked B
     9	blocked [key=held-one]: will be captain-held below
    10	captain-held [key=held-one]: captain took it
    11	needs-decision [key=phase.dot_ok-9]: full legal slug charset

BYTE-IDENTICAL base vs fixed: status_open_decisions                    (189 bytes of output, named-file and streaming forms)
BYTE-IDENTICAL base vs fixed: status_open_decisions_incremental        (189 bytes of output, named-file and streaming forms)
BYTE-IDENTICAL base vs fixed: status_open_activities                   (30 bytes of output, named-file and streaming forms)

open decisions the fixed classifier publishes (TAB shown as ->):
release -> needs-decision -> choose release A or B
default -> needs-decision -> keyless decision, still open
gate -> blocked -> waiting on external legal
phase.dot_ok-9 -> needs-decision -> full legal slug charset
Evidence: Adversarial corpus for the _fm_decision_drop rewrite (prefix keys, set emptying and refilling)

Source: Adversarial corpus for the _fm_decision_drop rewrite (prefix keys, set emptying and refilling)

BYTE-IDENTICAL base vs fixed (named-file and streaming forms) records the fixed fold publishes (TAB shown as ->): phase10 -> working -> ten phase100 -> working -> hundred ab -> working -> ab refill -> working -> refilled after empty empty-note -> working -> tabless-note -> working -> note with doubled spaces prefix-sibling records still open: 2 phase1 correctly closed

=== drop-rewrite adversarial corpus: prefix keys, emptying and refilling the open set ===
     1	working [key=phase1]: one
     2	working [key=phase10]: ten
     3	working [key=phase100]: hundred
     4	done [key=phase1]: one closed - phase10 and phase100 must survive
     5	working [key=a]: a
     6	working [key=ab]: ab
     7	done [key=a]: a closed
     8	working [key=only]: sole record
     9	done [key=only]: set is now empty
    10	working [key=refill]: refilled after empty
    11	working [key=empty-note]:
    12	working [key=tabless-note]: note with  doubled  spaces

BYTE-IDENTICAL base vs fixed (named-file and streaming forms)

records the fixed fold publishes (TAB shown as ->):
phase10 -> working -> ten
phase100 -> working -> hundred
ab -> working -> ab
refill -> working -> refilled after empty
empty-note -> working -> 
tabless-note -> working -> note with  doubled  spaces

phase1 closed without taking its prefix-sharing siblings:
  prefix-sibling records still open: 2
  phase1 correctly closed
Evidence: Changed-file selection routes a classify-lib edit to both classifier suites

Source: Changed-file selection routes a classify-lib edit to both classifier suites

# bin/fm-test-run.sh --list --changed with bin/fm-classify-lib.sh modified ... (watcher-wake-lock family) ... tests/fm-classify-activity-fold-cost.test.sh tests/fm-classify-decision-key.test.sh

# bin/fm-test-run.sh --list --changed --base <target> with bin/fm-classify-lib.sh modified
tests/fm-cursor-primary.test.sh
tests/fm-daemon.test.sh
tests/fm-guard-stale-banner.test.sh
tests/fm-inactive-reconcile.test.sh
tests/fm-mail-check.test.sh
tests/fm-mail.test.sh
tests/fm-pi-watch-extension.test.sh
tests/fm-session-lock-ancestry.test.sh
tests/fm-supervision-events.test.sh
tests/fm-task-inbox.test.sh
tests/fm-tool-update-check.test.sh
tests/fm-turnend-guard.test.sh
tests/fm-wake-daemon-lifecycle-e2e.test.sh
tests/fm-wake-drain-unread-status.test.sh
tests/fm-wake-queue.test.sh
tests/fm-watch-arm.test.sh
tests/fm-watch-checkpoint.test.sh
tests/fm-watch-recovery-loop.test.sh
tests/fm-watch-triage.test.sh
tests/fm-watcher-lock.test.sh
tests/fm-classify-activity-fold-cost.test.sh
tests/fm-classify-decision-key.test.sh
Evidence: Known residual: shipped 2 s budget on this Cygwin host still times out post-fix

Source: Known residual: shipped 2 s budget on this Cygwin host still times out post-fix

snapshot bin: /c/Users/nathan.rosquist/.no-mistakes/worktrees/20139a6921be/01M2TY5353E1WGP7ANGEETTTHR/bin
FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT=2 (shipped default is 2)

--- 256 routed status lines (snapshot wall 35887 ms) ---
activity_scan   : {"records":[],"available":false,"input_truncated":false,"retained_truncated":false,"reasons":["timeout"],"lines_in_window":0,"records_in_window":0}
open_activities : []
Evidence: Driver: stands up the fixture fleet and drives fm-fleet-snapshot.sh

Source: Driver: stands up the fixture fleet and drives fm-fleet-snapshot.sh

#!/usr/bin/env bash
# Drives the real product surface - bin/fm-fleet-snapshot.sh --json - over a fleet
# home whose registered secondmate has a routed parent-channel activity log of N
# status lines, and prints the routed-activity evidence the snapshot publishes.
#
# usage: drive-parent-activity.sh <bin-dir> <activity-timeout-seconds> <lines...>
set -u
WT=~/.no-mistakes/worktrees/20139a6921be/01M2TY5353E1WGP7ANGEETTTHR
BIN=${1:-$WT/bin}
TMO=${2:-2}
WIN=${FM_SNAPSHOT_PARENT_ACTIVITY_LINES:-256}
shift 2 || true
SIZES=${*:-20 256}

. "$WT/tests/lib.sh"
. "$WT/bin/fm-secondmate-registry-lib.sh"

TMP_ROOT=$(fm_test_tmproot fm-parent-activity-drive)
export FM_ROOT_OVERRIDE="$TMP_ROOT/fixture-root"; mkdir -p "$FM_ROOT_OVERRIDE"

printf 'snapshot bin: %s\nFM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT=%s (shipped default is 2)
FM_SNAPSHOT_PARENT_ACTIVITY_LINES=%s (shipped default is 256)\n\n' "$BIN" "$TMO" "$WIN"

for n in $SIZES; do
  home="$TMP_ROOT/home-$n"; mate="$TMP_ROOT/mate-$n"
  mkdir -p "$home/state" "$home/data" "$home/projects" "$home/config"
  mkdir -p "$mate/state" "$mate/data" "$mate/config" "$mate/projects" "$mate/bin"
  printf '# Firstmate fixture\n' > "$mate/AGENTS.md"
  printf 'domain-alpha\n' > "$mate/.fm-secondmate-home"
  printf -- '- domain-alpha - sample rollout (home: %s; scope: sample rollout; projects: sample; added 2026-07-13)\n' \
    "$mate" > "$home/data/secondmates.md"
  fm_write_secondmate_meta "$home/state/domain-alpha.meta" "$mate" "firstmate:fm-domain-alpha" sample
  printf '## In flight\n\n## Queued\n\n## Done\n' > "$mate/data/backlog.md"

  # The routed parent-channel activity log: n working events across 9 phase keys.
  i=0
  while [ "$i" -lt "$n" ]; do
    printf 'working [key=phase%s]: step %s under way on the parent channel\n' \
      "$((i % 9))" "$i"
    i=$((i + 1))
  done > "$home/state/domain-alpha.status"

  fb=$(fm_fakebin "$home")
  printf '#!/usr/bin/env bash\nexit 1\n' > "$fb/tmux"; chmod +x "$fb/tmux"

  s=$(date +%s%N)
  json=$(PATH="$fb:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
    FM_SNAPSHOT_NOW_EPOCH=1783792800 FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT="$TMO" FM_SNAPSHOT_PARENT_ACTIVITY_LINES="$WIN" \
    "$BIN/fm-fleet-snapshot.sh" --json 2>/dev/null)
  e=$(date +%s%N)
  rec=$(printf '%s' "$json" | jq -c '.secondmate_current.records[] | select(.id == "domain-alpha")')
  printf -- '--- %s routed status lines (snapshot wall %s ms) ---\n' "$n" "$(( (e - s) / 1000000 ))"
  printf 'activity_scan   : %s\n' "$(printf '%s' "$rec" | jq -c '.parent_event.activity_scan')"
  printf 'open_activities : %s\n' "$(printf '%s' "$rec" | jq -c '[.parent_event.open_activities[]? | "\(.key)=\(.verb)"]')"
  printf '\n'
done
Evidence: Driver: same fixture through the operator's Bearings report

Source: Driver: same fixture through the operator's Bearings report

#!/usr/bin/env bash
# Drives the real product surface - bin/fm-fleet-snapshot.sh --json - over a fleet
# home whose registered secondmate has a routed parent-channel activity log of N
# status lines, and prints the routed-activity evidence the snapshot publishes.
#
# usage: drive-parent-activity.sh <bin-dir> <activity-timeout-seconds> <lines...>
set -u
WT=~/.no-mistakes/worktrees/20139a6921be/01M2TY5353E1WGP7ANGEETTTHR
BIN=${1:-$WT/bin}
TMO=${2:-2}
WIN=${FM_SNAPSHOT_PARENT_ACTIVITY_LINES:-256}
shift 2 || true
SIZES=${*:-20 256}

. "$WT/tests/lib.sh"
. "$WT/bin/fm-secondmate-registry-lib.sh"

TMP_ROOT=$(fm_test_tmproot fm-parent-activity-drive)
export FM_ROOT_OVERRIDE="$TMP_ROOT/fixture-root"; mkdir -p "$FM_ROOT_OVERRIDE"

printf 'snapshot bin: %s\nFM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT=%s (shipped default is 2)
FM_SNAPSHOT_PARENT_ACTIVITY_LINES=%s (shipped default is 256)\n\n' "$BIN" "$TMO" "$WIN"

for n in $SIZES; do
  home="$TMP_ROOT/home-$n"; mate="$TMP_ROOT/mate-$n"
  mkdir -p "$home/state" "$home/data" "$home/projects" "$home/config"
  mkdir -p "$mate/state" "$mate/data" "$mate/config" "$mate/projects" "$mate/bin"
  printf '# Firstmate fixture\n' > "$mate/AGENTS.md"
  printf 'domain-alpha\n' > "$mate/.fm-secondmate-home"
  printf -- '- domain-alpha - sample rollout (home: %s; scope: sample rollout; projects: sample; added 2026-07-13)\n' \
    "$mate" > "$home/data/secondmates.md"
  fm_write_secondmate_meta "$home/state/domain-alpha.meta" "$mate" "firstmate:fm-domain-alpha" sample
  printf '## In flight\n\n## Queued\n\n## Done\n' > "$mate/data/backlog.md"

  # The routed parent-channel activity log: n working events across 9 phase keys.
  i=0
  while [ "$i" -lt "$n" ]; do
    printf 'working [key=phase%s]: step %s under way on the parent channel\n' \
      "$((i % 9))" "$i"
    i=$((i + 1))
  done > "$home/state/domain-alpha.status"

  fb=$(fm_fakebin "$home")
  printf '#!/usr/bin/env bash\nexit 1\n' > "$fb/tmux"; chmod +x "$fb/tmux"

  s=$(date +%s%N)
  json=$(PATH="$fb:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z FM_BEARINGS_NOW=2026-07-11T18:00:00Z NET_LOG="$home/net.log" \
    FM_SNAPSHOT_NOW_EPOCH=1783792800 FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT="$TMO" FM_SNAPSHOT_PARENT_ACTIVITY_LINES="$WIN" \
    "$BIN/fm-bearings-snapshot.sh" 2>/dev/null)
  e=$(date +%s%N)
  printf -- '--- operator-visible Bearings report, %s routed status lines (wall %s ms) ---
' "$n" "$(( (e - s) / 1000000 ))"
  printf '%s

' "$json"
done
Evidence: Driver: times the snapshot's own parent-activity child script

Source: Driver: times the snapshot's own parent-activity child script

#!/usr/bin/env bash
# Times the parent-activity child script exactly as bin/fm-fleet-snapshot.sh runs
# it (bounded_parent_activities_json), against the pre-fix and post-fix classifier.
set -u
WT=~/.no-mistakes/worktrees/20139a6921be/01M2TY5353E1WGP7ANGEETTTHR
printf '%-7s %-6s %-7s %-9s %s\n' lib lines rc cost_ms records_in_window
for lib in base fixed; do
  d=/tmp/pa/bin-base/fm-classify-lib.sh
  [ "$lib" = fixed ] && d="$WT/bin/fm-classify-lib.sh"
  for n in 20 120 256; do
    f=/tmp/pa/cost-$n.status
    if [ ! -f "$f" ]; then
      i=0
      while [ "$i" -lt "$n" ]; do
        printf 'working [key=phase%s]: step %s under way on the parent channel\n' "$((i % 9))" "$i"
        i=$((i + 1))
      done > "$f"
    fi
    s=$(date +%s%N)
    out=$(bash /tmp/pa/child.sh "$d" "$f" 256 65536 8 gnu 2>/dev/null); rc=$?
    e=$(date +%s%N)
    printf '%-7s %-6s %-7s %-9s %s\n' "$lib" "$n" "$rc" "$(( (e - s) / 1000000 ))" \
      "$(printf '%s' "$out" | jq -r '.records_in_window // "n/a"')"
  done
done
- Outcome: ⚠️ 2 infos across 1 run (1h2m59s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ℹ️ bin/fm-test-run.sh:1382 - The new bin/fm-classify-lib.sh arm of families_for_changed_path selects watcher-wake-lock plus __script__:fm-classify-activity-fold-cost.test.sh, but not tests/fm-classify-decision-key.test.sh - the suite that pins the exact semantics this change rewrote. That script is in the pure-contract-unit family (bin/fm-test-run.sh:281) and its header states it drives status_line_verb / status_open_decisions / status_open_decisions_incremental over crafted status files, which is the only coverage for _fm_decision_key's malformed-vs-missing slug distinction, status_line_note's note-head key stripping, and _fm_decision_drop's printed form - the three function bodies rewritten at bin/fm-classify-lib.sh:417-455 and 469-489. Concrete sequence: a developer reintroduces a divergence in the rewritten _fm_decision_key (e.g. returning default instead of failing on a malformed note-head slug), then runs bin/fm-test-run.sh --changed; the case arm matches first and returns only the two entries above, so the decision-key suite never runs and the local gate passes green. This is a local-gate gap only - list_portable_serial derives membership, so the full CI lane still runs it - which is why this is info rather than a warning. The routing gap predates this change (the base arm emitted watcher-wake-lock alone), but it is newly material because this change is precisely a semantics-preserving rewrite of those helpers, and the arm was edited here. Narrow remedy: add printf &#39;%s\n&#39; &#34;__script__:fm-classify-decision-key.test.sh&#34; to the same arm, the one-script mechanism already used at bin/fm-test-run.sh:1337 and 1384. Flagged as ask-user rather than auto-fix because round 3 settled this arm's contents explicitly ("do not change what the arm already selects"), so extending it is the author's call, not a mechanical correction.

🔧 Fix applied.
1 info still open:

  • ℹ️ bin/fm-classify-lib.sh:491 - Recording a measurement, not re-opening a decision. _fm_decision_drop's new split rebuilds the remainder on every record (set=${set#*$&#39;\n&#39;}), so each call is quadratic in open-set size, and the fold calls it once per status line in both branches. Measured on this host through the real fold at the shipped 256-line window, varying only how many keyed phases are simultaneously open: 9 keys -> 1.0 s, 32 -> 2.8 s, 64 -> 5.8 s, 128 -> 12.8 s, 256 -> 15.7 s (each figure includes ~1.0 s of bash startup plus sourcing fm-classify-lib.sh). The base code was 80.2 s at 9 keys and 80.8 s at 256, so this is strictly a 4.6x-44x improvement and introduces no regression - the intent's headline fix is real and the fork-per-line defect is gone. What the numbers add is the threshold: combined with the ~1.7 s the wrapped child script already spends on sourcing plus tail/awk/jq, the 2 s FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT is still reachable at roughly 20-30 open phases, which is low enough to be hit by keyed writers like child-outcome-&lt;child&gt;-&lt;state&gt;-&lt;fp8&gt; (bin/fm-inactive-reconcile.sh:337) or captain-hold-&lt;task&gt;-&lt;n&gt; (bin/fm-captain-hold.sh:229) on a busy parent channel that opens phases faster than it closes them - the same silent available:false path the intent describes. This is the already-filed drop-quadratic-in-open-keys / residual-budget-headroom pair you marked deliberately out of scope and filed separately, so no action is requested on this change; the threshold is just lower than an asymptotic note implies, which may matter when that separate item is prioritised. Note also that the new suite's two cases both use a 9-key log and assert forks, so neither this axis nor a future regression in _fm_decision_drop's per-call cost is pinned by it - a fork-count bound cannot see this cost, since it spends no processes at all.
⚠️ **Test** - 2 infos
  • ℹ️ bin/fm-fleet-snapshot.sh:1558 - At the shipped FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT=2 this Windows/Cygwin host still returns available:false, reasons:["timeout"] after the fix, because the parent-activity child's fixed scaffolding costs ~3.0 s here with the fold contributing only ~0.24 s of it: three jq startups ~1.7 s, bash startup plus sourcing bin/fm-classify-lib.sh ~0.4 s, plus stat/tail/awk forks. This is the separately-filed residual budget headroom that the recorded decisions place out of scope, and it is not a defect introduced by this change - the change removes the super-linear growth term (69.9 s -> 3.1 s at 256 lines) and leaves a constant that fits 2 s comfortably on any host with cheap forks. Recorded so the residual item has measured attribution; no action requested here.
  • ℹ️ bin/fm-test-run.sh:1077 - bin/fm-test-run.sh --check-coverage exits 1 on this host at BOTH the base commit f1fc96e and the target b452807, emitting "comm: file 2 is not in sorted order". The guard's sets are built with LC_ALL=C sort but comm runs under the host's en_US locale, which disagrees with C collation on '-' and '.', so the local result is a host-locale artifact rather than a real coverage gap. This change cannot be its cause: it only adds a hint row, which strictly lowers the unhinted count. Noted because round 3 asked for a check-coverage confirmation that cannot be obtained locally on this host; CI owns the real verdict.
  • Live validation: ✅ go - 8 of 8 scenarios driven live against the product
Scenario Result Live Evidence
A secondmate's routed activity survives a full 256-line parent-channel log: the fleet snapshot publishes available:true with all nine phase records, where the pre-fix build published available:false,… ✅ pass live bin/fm-fleet-snapshot.sh --json driven over a fixture fleet home (drive-parent-activity.sh); snapshot-fixed-timeout10.txt vs snapshot-base-timeout10.txt
The routed-activity read stops growing with the log: cost is flat at 20/120/256 lines instead of super-linear, and returns the same nine records at every size ✅ pass live fold-cost-table.sh timing bounded_parent_activities_json's own child script: base 7192/32290/69890 ms vs fixed 3193/2894/3120 ms; parent-activity-read-cost.txt
The operator reading Bearings no longer sees the fleet's routed-activity evidence declared unavailable ✅ pass live bin/fm-bearings-snapshot.sh over the same fixture fleet: omitted[9] carrying "secondmate parent activity evidence unavailable for 1 record(s)" at base, omitted[8] without it after; bearings-base-256…
Adversarial - raising the timeout constant is not a fix: with the budget raised 10x to 20 s over a 1024-line window the pre-fix read still times out silently, while the post-fix read publishes the who… ✅ pass live FM_SNAPSHOT_PARENT_ACTIVITY_LINES=1024 snapshot runs at a 20 s budget against both classifiers; raising-the-constant-is-not-the-fix.txt
The new cost suite genuinely reproduces the defect: it fails against the pre-fix classifier and passes against the fixed one ✅ pass live bash tests/fm-classify-activity-fold-cost.test.sh against a base-classifier tree (not ok - wide fold ... it is forking per status line again, exit 1) and against the target (both cases ok); fold-c…
Adversarial - the out-var and drop rewrite changes no classifier output: prefix-sharing keys are not co-dropped, the open set empties and refills correctly, and malformed/empty slugs, corr tags and mi… ✅ pass live byte-level differential of status_open_activities / status_open_decisions / status_open_decisions_incremental, named-file and streaming forms, over three crafted corpora; base-vs-fixed-semantics-diffe…
The existing decision-key semantics suite still passes on the rewritten helpers ✅ pass live bash tests/fm-classify-decision-key.test.sh - 20 cases green, covering both stated-key positions, malformed-vs-missing slugs, note-head key stripping and the incremental fold
A developer editing bin/fm-classify-lib.sh and running the local changed-file gate gets both classifier suites selected, so a reverted fold or a semantics regression is caught locally ✅ pass live bash bin/fm-test-run.sh --list --changed with bin/fm-classify-lib.sh modified; changed-file-selection.txt lists fm-classify-activity-fold-cost.test.sh and fm-classify-decision-key.test.sh alongside…
  • bash tests/fm-classify-activity-fold-cost.test.sh on the target (green, 4 runs, 10.3-19.5 s wall on this host)
  • bash tests/fm-classify-activity-fold-cost.test.sh against a tree with bin/fm-classify-lib.sh reverted to base f1fc96e (fails: not ok - wide fold ... it is forking per status line again)
  • bash tests/fm-classify-decision-key.test.sh (20 cases green - the suite pinning the semantics of the rewritten _fm_decision_key / status_line_note / _fm_decision_drop)
  • bin/fm-fleet-snapshot.sh --json driven over a fixture fleet home with 20 / 256 / 1024 routed parent-channel status lines, base-lib vs fixed-lib, at FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT of 2 s, 10 s and 20 s (drive-parent-activity.sh)
  • bin/fm-bearings-snapshot.sh over the same fixture fleet at 256 routed status lines, base-lib vs fixed-lib, comparing the operator-visible omitted surfaces (drive-bearings-report.sh)
  • Timing of bounded_parent_activities_json's own child script, extracted verbatim, at 20 / 120 / 256 lines against both classifiers (fold-cost-table.sh)
  • Attribution of the post-fix residual: bash startup, sourcing bin/fm-classify-lib.sh, the 256-line fold, and the child's three jq startups, timed separately
  • Byte-level differential of status_open_activities, status_open_decisions and status_open_decisions_incremental, named-file and streaming forms, base vs fixed, over three crafted corpora (both stated-key positions, malformed and empty slugs, corr tags, mid-note key prose, closing verbs, blank lines, reopening, prefix-sharing keys phase1/phase10/phase100 and a/ab, the open set emptying then refilling, an empty note)
  • bash bin/fm-test-run.sh --list --changed --base b452807 with bin/fm-classify-lib.sh modified (selects both fm-classify-activity-fold-cost.test.sh and fm-classify-decision-key.test.sh)
  • bash bin/fm-test-run.sh --check-coverage at both the base commit and the target (identical pre-existing exit 1 on this host)
⚠️ **Document** - 1 info
  • ℹ️ docs/fm-test-portable-shards.md:61 - Judgment call left unresolved, not a doc defect. The provenance sentence records the fold-cost hint as "the 7423 ms local native-Windows measurement ... from 2026-09-18, the slowest of three green local runs ... pending a CI refresh". A fresh green run of tests/fm-classify-activity-fold-cost.test.sh on this same Windows host just now measured 12606 ms — about 70% above the recorded stopgap — because the suite's fork calibration loop scales with host load. The doc is still honest as written (line 10 warns local timings are load-sensitive and line 11 labels such hints as stopgaps), so I made no edit: correcting the number would mean changing portable_serial_weight_hints in bin/fm-test-run.sh, which is code and belongs to the review/testing phase, and under-weighting only costs shard balance, never coverage. Raising it so the author knows the stopgap sits at the optimistic end of its own local spread until the CI refresh replaces it.
⏭️ **Lint** - skipped
  • ⚠️ linter found issues (exit code 1)
✅ **Push** - passed

✅ No issues found.

Validation notes (stages, and what did and did not run)

The test stage ran, and passed

The pipeline's test stage completed - status=completed, 63 minutes. It was
not skipped, and nothing about it was waived. It drove bin/fm-fleet-snapshot.sh --json over a fixture fleet whose secondmate carries a 256-line routed
parent-channel log and confirmed the defect and its fix on the real product
surface: before, the snapshot publishes available:false, reasons:["timeout"]
with zero records; after, available:true with all nine phase records. Measured
in that same harness, the fold is flat in log length - base 7192 / 32290 / 69890
ms at 20 / 120 / 256 lines against 3193 / 2894 / 3120 ms fixed. It also checked
the intent's claim that a bigger constant is not a fix: with the budget raised
tenfold over a 1024-line window, the pre-fix read still times out while the
post-fix read publishes the whole window.

An earlier attempt at this stage was cut twice at a 30-minute agent cap on this
host. That cap was cutting live work rather than catching a wedge - the stage
needs about 63 minutes here - and it was raised in local operator configuration,
not in anything this repository ships.

The lint stage did not run at all

Lint was skipped, and skipped is what the record says. It is not a waiver and
the check was not unwanted.

The stage never executed. This repository pins commands.lint: bin/fm-lint.sh
so the pipeline uses the same lint owner CI uses, but on this host the daemon
hands that command to cmd.exe rather than bash, so it dies in about a second
with 'bin' is not recognized as an internal or external command without
linting anything. Run properly through bash on this branch, bin/fm-lint.sh
exits 0. The local invocation is the defect; the script is sound, and the repo's
pinned configuration was deliberately left untouched rather than bent to suit one
machine.

Lint is still exercised before merge: .github/workflows/ci.yml runs
bin/fm-lint.sh independently on this pull request. So the distinction that
matters to a reader is that this is a check which ran elsewhere, not a check that
was skipped past.

Branch history

The branch was rebased onto the merge of #7 and the gate branch ref was
force-pushed (lease-guarded against the previous head) to the rebased head.
Before that rebase was adopted, the change's fold output was verified
byte-identical to the pre-change base at the new rebase parent, across a corpus
covering malformed and empty key slugs, note-head keys, correlation tokens,
reserved namespaces, colonless prose, both folds, both entry points and every
incremental prefix. That verification is why rewriting the ref was safe rather
than merely convenient.

Incidental finding, for anyone who pushes to the gate

Pushing to the no-mistakes gate ref starts a pipeline run by itself. The run
spawned that way had intent: skipped - its intent log read scanning recent agent transcripts... no matching agent transcript found - so it was validating
with no intent at all and its review had no acceptance criteria. It was cancelled
and the authorized run restarted with an explicit --intent. This is a trap for
anyone who force-pushes and walks away: the run looks ordinary in the run list
and is only distinguishable by reading its intent-step log.

status_open_activities ran four command substitutions per status line, and its
expensive branch was the most common verb, so the fold cost lines x 4 forks.
Its only caller reads a secondmate's parent-channel activity under a fixed 2s
wall-clock budget, and on a platform where a bare subshell costs tens of
milliseconds that budget was exceeded on every poll and by more as the log grew.
The timeout path returns well-formed JSON carrying available:false, so the
evidence was dropped with no error, no notification and no diagnostic line, and
the busiest mate - having the longest log - was the most reliably invisible.

Take the out-var form of status_line_verb, add out-var forms to
_fm_key_at_note_head, status_line_note and _fm_decision_key beside their
existing printed forms, fold _fm_decision_drop in place, and hoist the key
lookup behind the verb test the way _fm_decision_fold_line already does.
_fm_decision_drop also splits its set with parameter expansion instead of a
here-document, which on a per-line caller was a filesystem round trip per line.

Behaviour is unchanged: every touched surface, both folds, the streamed form and
every incremental prefix of a mixed corpus produce byte-identical output before
and after, including the timeout path.

Measured on the reporting platform, the child script the budget wraps, at 20 /
120 / 256 status lines: 4.67s / 22.77s / 48.39s before, 1.72s / 1.89s / 1.70s
after - flat in log length rather than super-linear. Under the real 2s budget it
went from a timeout at every size to available:true at every size. The budget is
deliberately left at 2s: it was never the defect, and raising it would only move
the same silent failure further out.

The new suite pins the cost shape rather than a millisecond count, by charging
the fold against the CPU the shell spends on reaped children - so it is honest
on a fast runner and a slow one alike, and it fails on the pre-fix code by a
factor of 30.
@nathan-rosquist
nathan-rosquist force-pushed the fm/fm-snapshot-activity-fork-r1 branch from bb51cad to b452807 Compare September 18, 2026 21:05
@nathan-rosquist
nathan-rosquist merged commit 84cc167 into main Sep 19, 2026
15 checks passed
@nathan-rosquist
nathan-rosquist deleted the fm/fm-snapshot-activity-fork-r1 branch September 19, 2026 02:40
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