Skip to content

fix: classify captain holds from structured state - #3508

Merged
kunchenguid merged 47 commits into
mainfrom
fm/fm-bearings-undated-hold-aging-r1
Sep 5, 2026
Merged

fix: classify captain holds from structured state#3508
kunchenguid merged 47 commits into
mainfrom
fm/fm-bearings-undated-hold-aging-r1

Conversation

@kunchenguid

@kunchenguid kunchenguid commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Intent

Fix a Bearings board bug in the firstmate repo: captain holds were rendering wrongly on the board, showing roughly 28 live "Captain's Call" entries that were mostly old parked items rather than decisions actually waiting on the captain.

The board decided where each captain hold belonged using several independent filter expressions, two of which matched natural-language text in the hold reason or body. That produced a recurring class of defects. Holds fell through every expression and vanished from the board entirely - a hold whose deferral date had arrived, a hold blocked by unresolved work, a hold on an actively working task, and remote holds restored from a pre-change cache. Separately, live decisions were silently suppressed because their wording happened to contain a marker word: a hold reasoned "non-deferred release choice" matched DEFERRED and disappeared from Captain's Call, even though the wording means the opposite.

Replace all of that with one total classifier that assigns every captain hold exactly one bucket, using structured fields only - hold_kind, state, hold_until, unresolved_blocker_ids, and the machine-written hold-set timestamp. The buckets are blocked when any blocker is unresolved, else dated while hold_until is still in the future, else aged when an undated hold is at least FM_SNAPSHOT_UNDATED_HOLD_AGE_DAYS old (default 14, measured from the hold-set timestamp so a fresh hold on an old task does not age, with legacy unstamped holds falling back to the task since date), else live. Being exhaustive and mutually exclusive is the point: no hold can fall through them all and none can match two, which is what ends both the disappearing-hold and the misclassification defects.

captain_actionable is exactly the live bucket. A live hold is a default Captain's Call entry. Blocked, dated and aged holds leave the default Captain's Call, render as Charted Next gates stating why - the blocking work, the until date, or the floored age - and are counted in the omitted[] disclosure. --all-decisions reveals every captain hold and drops its gate, so a hold is shown once and never twice. Remote secondmate summaries cached under the pre-change schema are refreshed or reported unavailable rather than fed through the projection with missing fields.

No hold reason or body prose is matched anywhere in the projection. Wording must never hide, reveal, or reclassify a decision. A hold that is superseded or no longer required is closed through the hold lifecycle rather than lingering as an open hold flagged by a keyword, so the parked-style and SUPERSEDED keyword matching is deliberately deleted rather than reimplemented. Re-holding with --until remains the durable way to defer a captain call.

Accepted documented limits, deliberately not fixed: a remote or secondmate hold retains the producing home age and aging decision rather than being recomputed against the parent threshold, and a rare concurrent answer-close and re-hold race can leave a re-held task without its age basis.

Scope note: tests/fm-herdr-lab.test.sh is deliberately identical to main. Earlier stabilization reworked it while chasing a flake that only appears under heavy local load, and asserted that a SIGSTOP-blocked process is gone after a timed-out provision, which is false on Linux because a stopped process does not die from SIGTERM. Keep main's version and do not reintroduce that process-liveness assertion.

Do not merge the pull request.

What Changed

  • Classify captain holds as blocked, dated, aged, or live using structured fields and durable hold-set timestamps, removing prose-based matching.
  • Show live holds in Captain's Call and non-live holds as explained Charted Next gates; --all-decisions reveals all available holds without duplicate gates.
  • Reject pre-classifier secondmate summaries and expand lifecycle, snapshot, and Bearings regression coverage and documentation.

Risk Assessment

✅ Low: The structured classifier, lifecycle timestamping, remote-schema boundary, projections, and behavioral regressions are internally consistent with the stated intent and prior authorized decisions.

Testing

The successful changed-test baseline was supplemented with focused Bearings, fleet-classifier, and hold-lifecycle tests plus an end-to-end CLI board fixture; structured buckets, prose-independent live classification, hold-set aging, omitted disclosure, and --all-decisions reveal behavior all worked as intended, with a reviewer-visible transcript captured and temporary worktree data removed.

Evidence: Default and expanded Bearings boards demonstrating structured captain-hold buckets

Source: Default and expanded Bearings boards demonstrating structured captain-hold buckets

=== Default Bearings board ===
schema: fm-bearings.v1
home: .tmp-test-evidence/home
generated: "2026-07-11T18:00:00Z"
prs: "not_requested (run: /bearings include PRs)"
in_flight: []
secondmates: []
secondmate_reconcile: []
decisions_open[2]{id,key,verb,summary,owner}:
  live-wording,live-wording,captain-hold,"Release decision: non-deferred release choice",(main)
  fresh-rehold,fresh-rehold,captain-hold,"Fresh hold on old task: choose again",(main)
landed: []
gates[3]{id,title,blocked_by,reason,owner}:
  blocked-call,Dependency choice,blocker-one,"blocked-by blocker-one: choose fallback",(main)
  dated-call,Later rollout decision,"-","until 2026-08-01: choose rollout",(main)
  aged-call,Old undated decision,"-","held 40d: choose route",(main)
reports: []
recorded_prs: []
omitted[7]{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"
  "captain holds bucketed blocked, dated, or aged: 3","--all-decisions"
  live PR discovery + checks,"--include-prs"

=== Bearings board with --all-decisions ===
schema: fm-bearings.v1
home: .tmp-test-evidence/home
generated: "2026-07-11T18:00:00Z"
prs: "not_requested (run: /bearings include PRs)"
in_flight: []
secondmates: []
secondmate_reconcile: []
decisions_open[5]{id,key,verb,summary,owner}:
  live-wording,live-wording,captain-hold,"Release decision: non-deferred release choice",(main)
  blocked-call,blocked-call,captain-hold,"Dependency choice: blocked-by blocker-one: choose fallback",(main)
  dated-call,dated-call,captain-hold,"Later rollout decision: until 2026-08-01: choose rollout",(main)
  aged-call,aged-call,captain-hold,"Old undated decision: held 40d: choose route",(main)
  fresh-rehold,fresh-rehold,captain-hold,"Fresh hold on old task: choose again",(main)
landed: []
gates: []
reports: []
recorded_prs: []
omitted[6]{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"
  live PR discovery + checks,"--include-prs"

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 5 issues found → auto-fixed (6) ✅
  • 🚨 bin/fm-bearings-snapshot.sh:487 - This contradicts the required invariant that blocked, dated, and aged holds “render as Charted Next gates.” A captain-held in-flight task whose child still reports working and whose bucket is blocked, dated, or aged is excluded from decisions_open, then the preceding state filter excludes it from gates; it therefore still vanishes from the default board. Bucketed captain holds must bypass the working-state gate filter, while --all-decisions should continue removing their gate.
  • 🚨 bin/fm-fleet-snapshot.sh:945 - This contradicts the requirement that a live hold is a default Captain's Call entry and specifically leaves the actively-working-task failure reachable for secondmates. $queued_all excludes an in-flight held row when its child state is working; consequently even a hold_bucket == "live" row never reaches decisions_open or queued, and the parent can show it only as Underway—even under --all-decisions. Preserve every bucketed captain hold in the secondmate decision/queued surfaces independently of active-child projection.
  • 🚨 bin/fm-fleet-snapshot.sh:1063 - The intent requires “--all-decisions reveals every captain hold,” but secondmate summaries permanently slice queued and decisions_open. For example, the 21st deferred captain hold with the default queued bound is absent from both exported arrays, so the parent cannot render its gate or reveal it with --all-decisions. The newly added documentation explicitly acknowledges this additional limitation, but the authoritative intent accepts only the producer-age and answer/re-hold race limits. Meeting the invariant requires an authorized decision about widening or bypassing the cross-home bounds.
  • ⚠️ .agents/skills/bearings/SKILL.md:45 - The changed operational skill still instructs agents that parked-style reasons and explicit deferred/superseded/not-required prose control placement and that --all-decisions reveals presentation hints. This directly contradicts “No hold reason or body prose is matched anywhere in the projection” and the new structured-only buckets. Remove the prose-classifier instructions and describe blocked/dated/aged/live bucket behavior instead; the script header contains the same stale claims.
  • ⚠️ bin/fm-fleet-snapshot.sh:376 - Simplification: current_prose_lines, resolution_leader, and hold_stamp_line were introduced to prepare body prose for the now-deleted keyword classifier, but remain as a separate filtering path that changes the --fields bodies excerpt. No current intent requirement needs a special “current prose” representation once classification is structured-only. Remove these helpers and restore the direct bounded body excerpt unless this extra user-visible filtering is intentionally retained.

🔧 Fix: Preserve working captain holds across bucket surfaces
1 error still open:

  • 🚨 bin/fm-fleet-snapshot.sh:1315 - Intent requires pre-change remote summaries to be “refreshed or reported unavailable,” but the v1 compatibility path validates new fields only on captain-hold rows. A pre-change cache produced while the original bug omitted a captain hold from both decisions_open and queued has no row to validate, so both all(...) checks pass and the stale v1 summary is accepted, preserving the disappearing hold. Remove the unrequired v1 compatibility path and enforce a summary-level schema/version boundary for the new classifier.

🔧 Fix: Reject pre-classifier secondmate summary caches
1 warning still open:

  • ⚠️ bin/fm-bearings-snapshot.sh:348 - Simplification: hold_summary applies the new 46/42 title/reason split to live holds even though only deferred holds need space reserved for an annotation. A live hold with a 60-character title and short reason previously retained its complete sub-90-character summary, but now silently loses title text. This contradicts the prior requirement that the default board remain unchanged. Preserve the original whole-summary truncation when hold_note is null, and reserve space only for blocked, dated, or aged annotations.

🔧 Fix: Preserve complete live hold summaries
1 error still open:

  • 🚨 bin/fm-bearings-snapshot.sh:442 - The required invariant says each hold is “shown once and never twice,” but a captain-held in-flight task whose child reports working is still added to in_flight_all at lines 423-432, then its bucket adds the same task to Captain's Call here when live or to Charted Next at lines 472-479 when blocked, dated, or aged. The secondmate path similarly exports it through both active_children and the hold surfaces. Thus the intended working-hold sequence now renders the same task in Underway and its hold bucket. Exclude bucketed captain holds from the corresponding main and secondmate Underway projections so each hold appears only in its assigned bucket.

🔧 Fix: Clarify working hold decision bucket semantics
2 errors still open:

  • 🚨 bin/fm-bearings-snapshot.sh:455 - The requirement says --all-decisions reveals every captain hold available within the remote-summary bound, but the queued-summary path accepts only deferred buckets. If a producing home exports one live hold in decisions_open and two in queued (for example, FM_SNAPSHOT_SECONDMATE_DECISIONS=1 and the default queued bound), the second live hold is present in the bounded summary yet is neither revealed nor gated under --all-decisions. Include all queued captain holds during expansion and deduplicate those already supplied by decisions_open.
  • 🚨 bin/fm-bearings-snapshot.sh:351 - The required blocked-by <work> explanation does not always survive the 90-character summary bound: the entire note plus reason receives only 42 characters. A short title with one 40-character blocker ID would fit within 90 characters, but this branch truncates the blocker identifier, and --all-decisions simultaneously removes the gate containing the complete blocked_by field. Reserve space for the complete note before allocating remaining space to title/reason.

🔧 Fix: Reveal bounded remote holds and preserve blocker notes
1 error still open:

  • 🚨 bin/fm-bearings-snapshot.sh:358 - --all-decisions removes the Charted Next gate, but fit(90) truncates the only remaining blocker note when its complete value exceeds 90 characters. A supported hold with two unresolved 40-character blocker IDs produces a note longer than 90 characters and silently loses part of the second ID. This conflicts with the authorized requirements that the complete blocked-by identity survive and that output remain within 90 characters; those constraints cannot both hold for multiple or longer blockers, so the desired output contract needs user resolution.

🔧 Fix: Make blocker overflow explicit in hold summaries
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bin/fm-test-run.sh --changed --exclude-family real-herdr-gated
  • Baseline: bin/fm-test-run.sh --changed --exclude-family real-herdr-gated
  • bin/fm-test-run.sh tests/fm-bearings-snapshot.test.sh
  • bin/fm-test-run.sh tests/fm-captain-hold-lifecycle.test.sh
  • bin/fm-test-run.sh tests/fm-fleet-snapshot-view.test.sh
  • Manual end-to-end fixture through bin/fm-bearings-snapshot.sh in default and --all-decisions modes, with semantic jq -e checks of live, blocked, dated, aged, freshly re-held, disclosure, reveal, and gate-removal behavior
  • git diff --quiet a5c64a0b6791b7643024436af9f737059d15cef2 -- tests/fm-herdr-lab.test.sh
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains in the reviewed captain-hold classification and projection paths.

No blocking failure remains.

Reviews (17): Last reviewed commit: "no-mistakes(ci): Updated the stock macOS..." | Re-trigger Greptile

Comment thread bin/fm-fleet-snapshot.sh Outdated
Comment thread bin/fm-fleet-snapshot.sh
@kunchenguid
kunchenguid force-pushed the fm/fm-bearings-undated-hold-aging-r1 branch from 32fe314 to e889ca0 Compare September 2, 2026 09:04
@kunchenguid kunchenguid changed the title fix(bin): defer parked and aged undated captain holds fix: age undated captain holds in Bearings Sep 2, 2026
Comment thread bin/fm-captain-hold.sh Outdated
@kunchenguid
kunchenguid force-pushed the fm/fm-bearings-undated-hold-aging-r1 branch from e889ca0 to ce9453b Compare September 2, 2026 09:49
@kunchenguid kunchenguid changed the title fix: age undated captain holds in Bearings fix(bearings): keep deferred undated holds off Captain's Call Sep 2, 2026
Comment thread bin/fm-fleet-snapshot.sh Outdated
Comment thread bin/fm-fleet-snapshot.sh
Comment thread bin/fm-fleet-snapshot.sh Outdated
@kunchenguid
kunchenguid force-pushed the fm/fm-bearings-undated-hold-aging-r1 branch from 95696f1 to 3c8da5d Compare September 3, 2026 22:43
@kunchenguid kunchenguid changed the title fix(bearings): keep deferred undated holds off Captain's Call fix(bin): keep parked and aged holds out of Captain's Call Sep 3, 2026
Comment thread bin/fm-fleet-snapshot.sh Outdated
@kunchenguid
kunchenguid force-pushed the fm/fm-bearings-undated-hold-aging-r1 branch from 8c7437f to 998ed7c Compare September 4, 2026 00:01
@kunchenguid kunchenguid changed the title fix(bin): keep parked and aged holds out of Captain's Call fix: project deferred and aged holds outside Captain's Call Sep 4, 2026
@kunchenguid
kunchenguid force-pushed the fm/fm-bearings-undated-hold-aging-r1 branch from 998ed7c to 9dcbe8c Compare September 4, 2026 04:14
@kunchenguid kunchenguid changed the title fix: project deferred and aged holds outside Captain's Call fix(bearings): defer parked and aged undated captain holds Sep 4, 2026
Comment thread bin/fm-bearings-snapshot.sh
Comment thread bin/fm-bearings-snapshot.sh Outdated
…est count to 51. Preserved the v1 summary schema for compatibility while rejecting hold-bearing summaries missing the new aging fields, preventing stale caches from restoring noisy calls. Verified fleet snapshot, Bearings snapshot (51 tests), home-summary refresh, secondmate reconciliation, Bash 3.2 parsing, and diff checks
…w reveals deferred/aged captain holds even when blocked, for both main and secondmate homes, and removes their duplicate Charted Next gates. Added behavioral regression coverage and updated documentation. The prose-classifier finding was not applied because exact complete-phrase matching is explicitly required by the author intent; contextual wording remains live. Verified with Bearings and fleet snapshot tests, `bin/fm-lint.sh`, Bash syntax checking, and `git diff --check`
…ed parked-style hold is live only when it is not explicitly non-actionable, so blocked due holds remain gated by default and are revealed by --all-decisions. Added behavioral regression coverage for that case. Preserved complete-reason parked-style classification as required by the author intent. Verified with tests/fm-bearings-snapshot.test.sh, bin/fm-lint.sh, and git diff --check
Under --all-decisions a deferred hold is revealed and its Charted Next gate
is removed, but the revealed row carried only the bare hold reason. A
date-deferred or blocked hold therefore read exactly like a genuine live
decision, because the until date, the age, and the blocking work only ever
appeared on the gate row that the reveal replaces.

Annotate a row that is revealed because it is deferred with the same
vocabulary the gate uses - until <date>, held <n>d, and the blocking work -
so the expanded view reads as deferred-but-shown. A genuinely live call is
left unannotated, and the default board is unchanged.
Bucket membership was decided by several independent expressions, and two of
them matched hold reason or body prose. That produced a recurring class of
defects: holds that fell through every bucket and vanished from the board, and
live decisions silently suppressed because their wording happened to contain a
marker word - a reason of "non-deferred release choice" matched DEFERRED and
disappeared.

Replace all of it with one total classifier over structured fields only:
hold_kind, state, hold_until, unresolved_blocker_ids, and the machine-written
hold-set timestamp. Every captain hold gets exactly one hold_bucket - blocked,
dated, aged, or live - so no hold can fall through and none can match two.
captain_actionable is exactly the live bucket, and the --all-decisions reveal
is a property of the bucket rather than a second filter.

No hold reason or body prose is matched anywhere in the projection, so wording
can no longer hide, reveal, or reclassify a decision. A hold that is superseded
or no longer required is closed through the hold lifecycle instead of lingering
as an open hold flagged by a keyword.
@kunchenguid
kunchenguid force-pushed the fm/fm-bearings-undated-hold-aging-r1 branch from c2a78eb to 8333797 Compare September 5, 2026 00:57
@kunchenguid kunchenguid changed the title fix(bearings): defer parked and aged undated captain holds fix: classify captain holds from structured state Sep 5, 2026
… from 17 to 18 tests. Verified the suite under Bash 3.2.57: all 18 tests pass. `git diff --check` also passes
…to 53 Bearings tests. Verified all 53 pass under Bash 3.2.57; git diff --check passes
@kunchenguid
kunchenguid merged commit 86ff1bf into main Sep 5, 2026
15 of 16 checks passed
@kunchenguid
kunchenguid deleted the fm/fm-bearings-undated-hold-aging-r1 branch September 5, 2026 04:42
lytv pushed a commit to lytv/mymate that referenced this pull request Sep 8, 2026
* Keep parked and aged undated captain holds off live Captain's Call.

Bearings was treating undated parked-style holds as live calls; mark those phrasings deferred and project holds older than a configurable 14-day since date as Charted Next gates instead.

* no-mistakes(review): Bound parked marker matching to lexical tokens

* no-mistakes(review): Age undated holds from durable hold-set dates

* no-mistakes(review): Reset re-held timestamps and scan full bodies

* no-mistakes(review): Preserve timestamp precision and prioritize parked suppression

* no-mistakes(document): Document undated captain-hold aging

* no-mistakes(ci): Fixed stock Bash CI test-count expectations (16 snapshot, 45 Bearings). Prevented fresh holds on old tasks from aging via stale `since` dates by aging only stamped holds. Added behavioral regressions and verified both suites plus Bash 3.2 parsing

* no-mistakes(ci): account for rebased snapshot regression

* no-mistakes(review): Restore legacy hold aging and mandate wrapper

* no-mistakes(review): Restrict hold stamps to canonical leading lines

* no-mistakes(review): Exclude historical answers and deduplicate revealed holds

* no-mistakes(document): Correct captain-hold projection documentation

* no-mistakes(ci): Rebased onto 8988af2 and resolved Bearings conflicts. Fixed the hold timestamp race by persisting and verifying the timestamp before publishing the captain hold; failures now leave the task unheld. Added behavioral coverage for ordering and failure handling. Preserved the required parked-phrase projection behavior. Relevant snapshot, Bearings, lifecycle, syntax, and ShellCheck validations pass

* no-mistakes(review): Bound current prose before historical resolutions

* no-mistakes(review): Preserve hold age across interrupted answers

* no-mistakes(review): Preserve leading hold stamps until answer closure

* no-mistakes(review): Normalize answer bodies on matching retries

* no-mistakes(review): Document concurrent re-hold age-basis limitation

* no-mistakes(document): Refresh captain hold lifecycle documentation

* no-mistakes(ci): Fixed both CI failures. Updated the macOS Bash snapshot expectation from 45 to 46 Bearings tests. Narrowed parked-style deferral matching to explicit hold-reason prefixes while preserving legacy explicit markers and preventing contextual prose from hiding active decisions. Added behavioral regression coverage. Verified with stock Bash 3.2: 17 fleet snapshot tests and 46 Bearings tests pass; full lint and workflow validation also pass

* no-mistakes(ci): Fixed Greptile’s P1 finding by restricting parked-style deferral phrases to complete hold-reason markers. Contextual reasons beginning with “not urgent,” “queued opportunity,” or “captain-gated” now remain visible decisions. Added behavioral coverage through the real fleet and Bearings snapshot paths and updated documentation. Verified both snapshot suites under Bash 3.2 (17 fleet tests and 46 Bearings tests), syntax checks, and git diff checks. The no-mistakes attestation failure is external/stale and requires the outer pipeline to refresh it for the new head

* no-mistakes(ci): Fixed parked-style undated captain holds disappearing from the default Bearings board. They now project to Charted Next with omitted[] disclosure, while --all-decisions reveals them and removes the safety gate. Added behavioral coverage for the reported “not urgent” case and aligned documentation. Verified fm-bearings-snapshot, fleet snapshot view, and captain-hold lifecycle tests; shellcheck, bash syntax, and git diff checks pass

* no-mistakes(test): Stabilize concurrency budget and provision timeout tests

* no-mistakes(document): Correct captain hold documentation details

* no-mistakes(ci): Fixed hold-reason parsing so commas in contextual reasons are preserved and do not incorrectly defer live Captain's Call decisions. Added end-to-end fleet/Bearings regression coverage. Reworked the flaky Herdr timeout test to assert observable late-launch behavior rather than process-ID liveness. Verified both snapshot suites, Herdr test 5 consecutive times, shell syntax, shellcheck, and git diff checks

* Restore the Herdr lab timeout test to its main version.

The stabilization rounds reworked tests/fm-herdr-lab.test.sh while chasing a
load-induced flake, replacing the fake server's wall-clock delay with a
SIGSTOP'd process and asserting that the blocked process is gone after a
timed-out provision. A stopped process does not die from SIGTERM, so that
assertion fails on Linux and the portable parallel shard stayed red.

That test is unrelated to the undated captain-hold projection this branch
delivers and was identical to main before these rounds, so restore main's
version exactly. It still proves that a timed-out provision cancels its late
launch before teardown.

* no-mistakes(review): Preserve metadata-like prose in captain hold reasons

* no-mistakes(review): Resurface due dated captain holds

* no-mistakes(review): Distinguish parked holds from explicit deferrals

* no-mistakes(review): Invalidate legacy secondmate summary caches

* no-mistakes(review): Keep blocked deferred holds in Charted Next

* no-mistakes(review): Count blocked deferred holds in omission disclosure

* no-mistakes(document): Correct captain-hold projection documentation

* no-mistakes(ci): Fixed both CI failures. Updated the macOS Bearings test count to 51. Preserved the v1 summary schema for compatibility while rejecting hold-bearing summaries missing the new aging fields, preventing stale caches from restoring noisy calls. Verified fleet snapshot, Bearings snapshot (51 tests), home-summary refresh, secondmate reconciliation, Bash 3.2 parsing, and diff checks

* no-mistakes(ci): Fixed Greptile’s valid finding: `--all-decisions` now reveals deferred/aged captain holds even when blocked, for both main and secondmate homes, and removes their duplicate Charted Next gates. Added behavioral regression coverage and updated documentation. The prose-classifier finding was not applied because exact complete-phrase matching is explicitly required by the author intent; contextual wording remains live. Verified with Bearings and fleet snapshot tests, `bin/fm-lint.sh`, Bash syntax checking, and `git diff --check`

* no-mistakes(ci): Fixed the actionable-state bug in Bearings: an arrived parked-style hold is live only when it is not explicitly non-actionable, so blocked due holds remain gated by default and are revealed by --all-decisions. Added behavioral regression coverage for that case. Preserved complete-reason parked-style classification as required by the author intent. Verified with tests/fm-bearings-snapshot.test.sh, bin/fm-lint.sh, and git diff --check

* Show why a revealed captain hold is deferred.

Under --all-decisions a deferred hold is revealed and its Charted Next gate
is removed, but the revealed row carried only the bare hold reason. A
date-deferred or blocked hold therefore read exactly like a genuine live
decision, because the until date, the age, and the blocking work only ever
appeared on the gate row that the reveal replaces.

Annotate a row that is revealed because it is deferred with the same
vocabulary the gate uses - until <date>, held <n>d, and the blocking work -
so the expanded view reads as deferred-but-shown. A genuinely live call is
left unannotated, and the default board is unchanged.

* Classify captain holds from structured fields alone.

Bucket membership was decided by several independent expressions, and two of
them matched hold reason or body prose. That produced a recurring class of
defects: holds that fell through every bucket and vanished from the board, and
live decisions silently suppressed because their wording happened to contain a
marker word - a reason of "non-deferred release choice" matched DEFERRED and
disappeared.

Replace all of it with one total classifier over structured fields only:
hold_kind, state, hold_until, unresolved_blocker_ids, and the machine-written
hold-set timestamp. Every captain hold gets exactly one hold_bucket - blocked,
dated, aged, or live - so no hold can fall through and none can match two.
captain_actionable is exactly the live bucket, and the --all-decisions reveal
is a property of the bucket rather than a second filter.

No hold reason or body prose is matched anywhere in the projection, so wording
can no longer hide, reveal, or reclassify a decision. A hold that is superseded
or no longer required is closed through the hold lifecycle instead of lingering
as an open hold flagged by a keyword.

* no-mistakes(review): Preserve working captain holds across bucket surfaces

* no-mistakes(review): Reject pre-classifier secondmate summary caches

* no-mistakes(review): Preserve complete live hold summaries

* no-mistakes(review): Clarify working hold decision bucket semantics

* no-mistakes(review): Reveal bounded remote holds and preserve blocker notes

* no-mistakes(review): Make blocker overflow explicit in hold summaries

* no-mistakes(document): Correct captain-hold projection documentation

* no-mistakes(ci): Updated the stock macOS Bash CI snapshot expectation from 17 to 18 tests. Verified the suite under Bash 3.2.57: all 18 tests pass. `git diff --check` also passes

* no-mistakes(ci): Updated the stock macOS Bash CI expectation from 51 to 53 Bearings tests. Verified all 53 pass under Bash 3.2.57; git diff --check passes
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