fix(bin): recognize correlation tokens in status transitions - #1967
Open
mremond wants to merge 4 commits into
Open
fix(bin): recognize correlation tokens in status transitions#1967mremond wants to merge 4 commits into
mremond wants to merge 4 commits into
Conversation
status_line_verb stripped a trailing [key=...] from a status line's prefix but left everything else glued to the verb, so a line carrying the correlation token bin/fm-pending-reply-lib.sh embeds and a secondmate echoes back matched no arm of _fm_decision_fold_line. Such a line folded as ordinary status in both directions: a needs-decision or blocked opener never opened its key, and a resolved or captain-held closer never closed one. The same glued verb also hid correlated done and blocked lines from status_is_captain_relevant and status_is_terminal_verb, and let correlated working and resolved lines leak through the free-text fallback the nonterminal guard was meant to stop. The verb parse now walks whole words and drops only a token of the exact shape a firstmate library writes - corr=<16 hex>, plus the bracketed form bin/fm-secondmate-report.sh emits - before or after the key token, unkeyed, or doubled. An arbitrary name=value word is deliberately NOT skipped: skipping unknown tokens would let free text carrying an equals sign reduce to a bare verb and impersonate a transition, which is the takeover the strict parse and _fm_decision_key_transition_allowed exist to prevent. A prefix with no corr= substring is returned byte-for-byte as before, so every line without a token keeps its exact historical verb. FM_OPEN_DECISIONS_FOLD_VERSION goes to 3, because every cursor persisted under the previous reading carries an open set computed while correlated lines were invisible and must be rebuilt from byte 0. Measured over a real 383-line status log: 254 lines keep byte-identical captain-relevance, pause, terminal-verb and captain-held verdicts, and all 129 changed lines carry a valid token - 14 correlated done/blocked/ needs-decision lines become captain-visible, and 20 correlated working/resolved lines stop being escalated on prose alone.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
A captain-facing decision can be silently invisible. The durable decision fold in bin/fm-classify-lib.sh does not recognize a status line whose verb is followed by a correlation token, so such a line is folded as ordinary status: it neither OPENS nor CLOSES a decision. Both directions of the failure were live in the operating home.
THE DEFECT. status_line_verb stripped a trailing [key=...] from the prefix but left everything else glued to the verb, so 'resolved corr=7ab3e5dd13c9a993 [key=k]: closed' parsed as verb 'resolved corr=7ab3e5dd13c9a993'. No case arm in _fm_decision_fold_line matches that, so the line is not a transition. _fm_decision_key extracted the key correctly, which is why the two halves disagreed. bin/fm-pending-reply-lib.sh writes corr=<16 hex> by design (fm_pending_reply_corr_token) and secondmates put it on their status lines, so this is a shape firstmate's own tooling produces, not malformed input.
TWO LIVE CONSEQUENCES. Closers that do not close: six answered, shipped and merged decisions stayed on the captain's board for days. Openers that do not open, the worse half: two genuine captain decisions were the last lines of the real state/site-perso.status and had never appeared in any OPEN DECISIONS listing. A decision the captain was owed never reached him.
WHAT WAS ASKED. Make the verb reading tolerate a correlation token between the verb and the rest of the line, in BOTH directions, so an opener opens and a closer closes. Establish first, by reading the code rather than assuming, whether status_line_verb is the right place or whether a shared prefix-normalisation helper is, and whether any other consumer of status_line_verb changes behaviour when the token is stripped. Name every consumer checked and what changed for it. A fix that closes decisions but makes the away-mode daemon misclassify a wake is a worse defect than the one it repairs.
THE TRAP, AND IT IS THE WHOLE RISK OF THIS TASK. Do NOT loosen the verb parse into prose matching. The parse is deliberately strict so free-text prose can never impersonate a transition; _fm_decision_key_transition_allowed exists for exactly that class of takeover. Recognise a token of the exact shape the correlation library produces - an anchored, bounded, hex-shaped corr= token - and nothing else. A line reading 'resolved the corr= issue yesterday [key=x]:' must NOT become a closing transition. It was required to decide explicitly whether an unknown foo=bar token should be skipped too, and to state the trade-off rather than settle it silently.
DECISIONS MADE WHILE DOING THE WORK, all deliberate:
status_line_verb is the right place, established by reading the code. _fm_decision_key already scans the prefix for [key=...] anywhere in it and was never wrong - that asymmetry is exactly why the two halves disagreed. No shared prefix-normalisation helper was introduced because only one parser was broken.
Reading the REAL status log surfaced more shapes than the report named. Four token placements exist in the wild plus one more from a helper: 'verb corr= [key=k]:' (broken), 'verb corr=:' (broken, default key), 'verb corr= corr= [key=k]:' (broken, a recovery turn re-embedding two tokens on one line), 'verb [corr=]:' (broken, written by bin/fm-secondmate-report.sh), and 'verb [key=k] corr=:' (which worked only by accident, because the old [key= strip ate the tail). All are handled now. Including the bracketed helper shape was a deliberate widening of the reported scope: it is equally broken and is written by a firstmate library, so it belongs to the same defect.
STRICT ROAD TAKEN on the foo=bar question, deliberately. Only corr=<16 hex> and its bracketed form are recognised; an arbitrary name=value token is NOT skipped. Rationale stated in the code: skipping unknown tokens would let any free-text word carrying an equals sign ('resolved x=1 [key=k]: ...') reduce to a bare verb and impersonate a transition, which is the takeover the strict parse exists to prevent. The cost is one reviewed line each time a genuinely new token shape appears, and that is the intended trade - a new shape becomes a deliberate edit rather than a silent widening.
Implementation shape. status_line_verb keeps its existing [key= strip and trim, then takes a fast path: a prefix with no 'corr=' substring is returned byte-for-byte as before, which is the no-regression guarantee for every untokened line including odd interior spacing. Only when 'corr=' is present does it walk whole words, dropping ONLY recognised tokens and keeping everything else - so a multi-word prose prefix stays multi-word and matches no verb arm, preserving the strictness.
The 16 hex classes are written out literally rather than built from a variable. A first implementation built the glob from a variable; that only re-globs under some shells' expansion rules (it silently fails under zsh), and a safety parse must not depend on that. This matches the existing precedent in bin/fm-secondmate-report.sh.
FM_OPEN_DECISIONS_FOLD_VERSION bumped 2 -> 3. This was NOT in the original report but is load-bearing: the incremental cursor persists a folded open set, so every cursor written under the previous reading holds decisions computed while correlated lines were invisible. The operating home's live cursor is version=2, offset=, with an EMPTY open set - so without the bump the fix would ship and those two decisions would still never surface there.
EVERY CONSUMER OF status_line_verb, enumerated with its verdict, as required:
EVIDENCE GATHERED, not assertion. All four predicate verdicts were diffed old-code-vs-new over the real 383-line state/site-perso.status: 254 lines byte-identical, and all 129 changed lines carry a valid correlation token in their prefix. No untokened line changed any verdict. Of the changed lines, 14 correlated done/blocked/needs-decision lines become captain-visible and 20 correlated working/resolved lines stop being escalated on prose alone. Before the fix the whole-file fold returned NOTHING on that log; after it, eleven genuinely open captain decisions including the two named openers.
TESTS. New tests/fm-classify-corr-token.test.sh with 10 cases: both directions through the REAL bin/fm-wake-drain.sh; every token position (before the key, after it, unkeyed, doubled, bracketed); the untokened pair to prove nothing regressed; ten impostor shapes (prose containing corr=, wrong-length, non-hex, glued prefix, unknown name=value) asserted to neither open nor close; captain-relevance and pause verdicts pinned unchanged for untokened lines and proven correct for tokened ones; the reserved pending-reply- key namespace still protected; the incremental cursor-backed fold and the whole-file fold asserted to agree AND to give the right answer at every step; a version-2 cursor proven to be discarded and refolded; and a drift pin that drives the REAL writers (fm_pending_reply_corr_token over generated ids, and bin/fm-secondmate-report.sh in both its plain and --doc modes) to prove this library's second statement of the token shape cannot drift from the library that owns the grammar. Each test was run individually against the pre-fix library: 7 fail there, and the 3 that pass are the deliberate no-regression walls.
VERIFICATION RUN. bin/fm-lint.sh clean, bin/fm-doc-audience-check.sh clean, and every affected suite green: watch-triage 46, daemon 99, crew-state 49, pending-reply 29, remote-reply 16, fleet-snapshot-view 15, wake-drain-open-decisions 9, wake-drain-open-decisions-cursor 6, decision-hold-lifecycle 9, send-resolve-key 10, afk-return 5.
OUT OF SCOPE, deliberately. Issue #1879 is a different defect in the same family (key position after the colon). It was not fixed here. It lives in _fm_decision_key's handling of a key after the colon, which this change does not touch, so it is neither easier nor harder to fix.
KNOWLEDGE PLACEMENT. Per .agents/skills/firstmate-coding-guidelines, this is library mechanics, so the knowledge went into the fm-classify-lib.sh header comment where the parsers live. No AGENTS.md edit: nothing here is needed by every session, and AGENTS.md's existing cross-reference to the cursor file remains accurate. No docs/ surface documents this grammar. The token grammar's one owner remains bin/fm-pending-reply-lib.sh; this library states only the SHAPE, with the drift pin test holding the two together, because that library sources this one and cannot be sourced back.
The real state/site-perso.status was copied into the task worktree for analysis; nothing under the operating home's state/ was written.
What Changed
corr=<16 hex>status tokens, including bracketed and repeated forms, so correlated decision openers and closers are classified correctly while token-first lines, prose, malformed tokens, and unknownname=valuewords remain non-transitions.Risk Assessment
✅ Low: Captain, the corrected parser now preserves the leading word, resolves the prior token-first transition path, and keeps the change narrowly bounded to recognized post-verb correlation tokens.
Testing
The author-supplied baseline reported the affected suites and lint checks green; this phase independently passed the focused regression script and product-level wake-drain and away-daemon checks, after discarding and successfully retrying one invalid zsh evidence attempt under Bash.
Evidence: OPEN DECISIONS correlated-token transcript
Evidence: Away-mode daemon correlated-working transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed ✅
bin/fm-classify-lib.sh:242- Intent requires “the verb reading [to] tolerate a correlation token between the verb and the rest of the line” and says free-text prose “must never impersonate a transition,” but_fm_classify_is_corr_token "$word" && continueremoves valid tokens regardless of position. Afterneeds-decision [key=victim]: owed, appendingcorr=c44897ee2db4326b resolved [key=victim]: prosenow parses asresolvedand silently closesvictim; previously the glued prefix matched no transition. Restrict removal to tokens after the retained leading verb, and pin token-first opener/closer lines as non-transitions.🔧 Fix: Captain, block token-first decision impersonation
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
Inspectedgit diff 833a9a25bcf2ae522d6f93dbbd9911a6d8e7c409..ba444d8000a9fda5dc83415068f246d3db78eb56and allstatus_line_verbconsumers.bash tests/fm-classify-corr-token.test.shAppended a valid correlated opener, prose impostor, and valid closer to an isolated status log; ranFM_STATE_OVERRIDE=… bin/fm-wake-drain.shafter each append and captured its actual output.Sourcedbin/fm-supervise-daemon.shunder Bash; exercisedclassify_staleandhandle_wakewith correlatedworkingplusmergedprose, asserting transient classification, preserved wedge aging, and no terminal escalation.✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.