Skip to content

fix(bin): resolve captain calls against the closed-task archive - #3053

Open
AgardnerAU wants to merge 4 commits into
kunchenguid:mainfrom
AgardnerAU:fm/fm-holdgate-archive
Open

fix(bin): resolve captain calls against the closed-task archive#3053
AgardnerAU wants to merge 4 commits into
kunchenguid:mainfrom
AgardnerAU:fm/fm-holdgate-archive

Conversation

@AgardnerAU

Copy link
Copy Markdown

The defect

bin/fm-captain-hold.sh's complete and verify resolved every entry in a task's decision_keys against data/backlog.md only. tasks-axi prune moves closed tasks out of the backlog and into data/done-archive.md, so a finished investigation whose captain calls were correctly answered failed its own completion gate permanently, and its cleanup was refused for good.

Reproduced against a real archived entry (agfp-triage-waves, key issue-2550-idle-timeout-gap), staged into a fixture home:

  • pre-fix: no captain-held task issue-2550-idle-timeout-gap and no legacy identity agfp-triage-waves-decision-issue-2550-idle-timeout-gap in .../data/backlog.md, exit 1
  • fixed: verified: agfp-triage-waves captain-call inventory, exit 0

That entry resolves through the legacy <origin>-decision-<key> identity, so archive lookup covers the legacy path too.

The fix

Lookup only. task_show_durable tries the live backlog first, then the configured archive. What counts as answered is still decided solely by verify_hold_durable, so an archived call closed with no recorded captain answer keeps failing exactly as it did while live. The gate's contract, the archive format, and the captain-hold lifecycle are deliberately untouched.

  • archive_path reads tasks-axi's own [markdown] archive setting rather than hardcoding the path.
  • archived_task_show restages the archive's rows under the ## Done heading tasks-axi parses and hands them to tasks-axi show --file, so the archive row format keeps exactly one parser.
  • require_readable_archive guards both gate paths: an archive that exists but cannot be read refuses by name. An archive that does not exist is deliberately not an error - nothing has been pruned yet, and the lookup finds no task, which still fails the gate. Fail-closed either way.
  • command_hold and command_answer deliberately still read the live backlog only; widening those would change write semantics, outside this fix's scope.

Regression cover, both directions

tests/fm-captain-hold-lifecycle.test.sh gains one behavioural test driving the real prune and asserting observable exit codes and refusal text:

  1. answered then archived - passes complete, verify, and cleanup
  2. archived but closed with no recorded answer - still fails, and cleanup is still refused by the gate
  3. unreadable archive - refuses by name, with a root-safe skip

Proved by three mutations, each caught:

Mutation Test that fails
permissive archive lookup (found-in-archive treated as answered) unanswered direction
remove the unreadable-archive refusal unreadable assertion
reintroduce the original live-backlog-only bug answered-and-archived direction

Verification

Local, against the committed code: fm-captain-hold-lifecycle, fm-teardown, fm-teardown-endpoint-safety, fm-bearings-snapshot, fm-classify-decision-key - 5 suites, 0 failures. bin/fm-lint.sh and bin/fm-doc-audience-check.sh clean. The no-mistakes pipeline completed review, test, document, and lint.

CI has not run on this pull request and must not be read as green: fork pull requests here wait on an upstream maintainer to release workflow runs.

The completion gate resolved every entry in a task's decision_keys against
data/backlog.md alone. tasks-axi prune moves closed tasks out of the backlog
and into data/done-archive.md, so a finished investigation whose captain calls
were correctly answered failed its own gate permanently and its cleanup was
refused for good. Observed on a real archived call whose answer was recorded
in full.

Both gates now look for each entry in the home's configured closed-task
archive as well as its live backlog. Only the lookup is widened: what counts
as answered is still decided by verify_hold_durable, so an archived call
closed with no recorded captain answer keeps failing exactly as it did while
live. An archive that exists but cannot be read refuses both gates by name,
because a check that could not look must never be recorded as a check that
found nothing outstanding; an archive that does not exist yet is not an error
and simply carries no tasks.

The archive rows are handed to tasks-axi to parse rather than read by a second
parser here, so the row format keeps one owner.
@greptile-apps

greptile-apps Bot commented Aug 25, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Reviews (2): Last reviewed commit: "no-mistakes(document): document configur..." | Re-trigger Greptile

Comment thread bin/fm-captain-hold.sh Outdated
archive_path matched only a double-quoted value with nothing after it, so a
home configuring its archive as a TOML literal string, or with an inline
comment, silently fell back to data/done-archive.md. The gate then resolved
captain calls against a different file than the one tasks-axi prunes into, and
a correctly answered, pruned call failed complete, verify, and non-forced
teardown anyway - the same defect this lookup exists to remove, wearing a
config-parsing disguise.

Verified against tasks-axi 0.2.5 which form it actually honours: basic
(double-quoted) strings, literal (single-quoted) strings, and an inline comment
after either all select the configured archive. All four are now read. The
value is matched by its own quote delimiters rather than to end-of-line, so a
'#' inside the path is not mistaken for a comment.

The new regression drives the real prune through every honoured form and
asserts the gate and teardown both accept a call archived at the configured
path. It fails on the previous parser at the single-quoted form, reporting the
default path in its refusal.
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

Reviewed HEAD 6f7ec9f9af6b64551bd70e3d635163fe55f70754 (unstamped). Full diff reviewed.

Class: corrective. Lookup-only widening of complete/verify onto the closed-task archive so an answered, pruned captain call does not fail its own gate forever. Unanswered archived calls still fail; an unreadable archive refuses by name.

VISION (per rule):

  • One captain, one interface — aligns. The gate stays honest after prune.
  • Authority is explicit — aligns. A missing recorded captain answer still fails; unreadability is not treated as "nothing owed".
  • Scripts own the mechanics — aligns. Archive lookup is scripted.
  • A restart is a non-event — aligns. Obligations stay closed by durable records after archive.
  • Delegation with a spine — aligns. The completion/teardown gate still stands.
  • The fleet outlives any vendor — aligns.
  • Scope — aligns. Command-layer hold gate, not workshop/merge policy.

What is not cleared:

  1. No no-mistakes-pipeline-attestation:v1 for this HEAD (none in the body, none in the thread). Blocking.
  2. Greptile P1: archive_path only matches double-quoted archive = "..." and can silently fall back to data/done-archive.md for valid TOML (single quotes / inline comments). That recreates the original gate failure for some real configs.
  3. command_answers now uses task_show_durable, which widens a write-adjacent path even though the body says command_hold / command_answer stay live-backlog-only. Confirm or revert that.
  4. Fork CI was approved after this review; portable CI / no-mistakes had not finished at comment time.

Security: no.

This is waiting on the author, not the captain: address the archive-parser P1, confirm the command_answers lookup, and push a matching no-mistakes attestation for this HEAD.

Merge-eligible: NO. Captain-flag NOW: NO.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

Re-read since the earlier stamp on HEAD 6f7ec9f9af6b64551bd70e3d635163fe55f70754 (outcome=ci-approved-waiting-author). Current HEAD is 2de46051f18723493fef290569f987ef841a7565 vs main 6a2cd6cd2c642c9422bb43dccedbd91103285fb0. Full diff re-reviewed, including 14f1dc6a and 2de46051.

Class: corrective (unchanged). Lookup-only widening of complete/verify onto the closed-task archive so an answered, pruned captain call does not fail its own gate forever. Unanswered archived calls still fail; an unreadable archive refuses by name.

What changed since the stamp:

  • 14f1dc6a reads the configured [markdown] archive as double-quoted, single-quoted, and with an inline comment after either. The archive-parser P1 is addressed. Regression coverage drives real prune through those four forms.
  • 2de46051 documents that lookup.
  • Fork workflows parked again on the new HEAD; re-approved after this review.

VISION (per rule): unchanged, still aligns (honest gate after prune; missing recorded answer still fails; archive lookup is scripted; obligations stay closed by durable records).

What is not cleared:

  1. Still no no-mistakes-pipeline-attestation:v1 for this HEAD (none in the body, none in the thread). Blocking.
  2. command_answers still uses task_show_durable. The body still says command_hold / command_answer stay live-backlog-only. Confirm that bulk-answers widening is intended, or revert it.
  3. CI / Require-no-mistakes were re-approved for this HEAD; they had not finished at comment time.

This is waiting on the author, not the captain: confirm or revert the command_answers lookup, and push a matching no-mistakes attestation for this HEAD.

Merge-eligible: NO.

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.

2 participants