Skip to content

fix: verify archived captain decisions after retention#905

Open
codenum137 wants to merge 11 commits into
kunchenguid:mainfrom
codenum137:fm/firstmate-decision-hold-archive-aware-scout-v1
Open

fix: verify archived captain decisions after retention#905
codenum137 wants to merge 11 commits into
kunchenguid:mainfrom
codenum137:fm/firstmate-decision-hold-archive-aware-scout-v1

Conversation

@codenum137

Copy link
Copy Markdown

What Changed

  • Make decision lookup archive-aware so complete, verify, and identical resolve retries work after a captain decision is retained, while hold cannot recreate an archived resolution.
  • Fail closed on duplicate or colliding identities, unsafe or changing stores, malformed archive records, resolution mismatches, and noncanonical decision-file bytes.
  • Document the retained-decision byte contract and expand lifecycle coverage for quoted dash reasons and adversarial archive cases.

Risk Assessment

✅ Low: Captain, the CR rejection now closes the parser-normalization gap without changing writer or lifecycle semantics, and no remaining material source defect was substantiated.

Testing

已核对目标 SHA,聚焦回归通过;真实 CLI 流程完成 retention 后的三条调用路径,持久化证据与篡改负控符合预期,并用 base commit 在同一状态复现原失败。测试残留已清理;按要求未运行 lint、formatter 或静态分析,此 CLI 变更无 UI 截图适用。

Evidence: 目标提交端到端 CLI 记录

active_lookup_exit=1 complete: sample-archive-review decision inventory reviewed (route) verified: sample-archive-review unresolved-decision inventory resolved: sample-archive-review-decision-route archive SHA-256 unchanged tampered_verify_exit=1 E2E_RESULT=PASS

Archive-aware decision lifecycle end-to-end evidence
target_commit=c7314195f61e05cbce6fa7cba54df6bd71349aec
tasks_axi_version=0.2.3

$ tasks-axi add sample-archive-review ... --start
ok: added sample-archive-review (scout, repo sample) -> In flight
task:
  id: sample-archive-review
  title: Review archive-aware routing
  state: in_flight
  blocked: no
  blocked_by: none
  held: no
  hold_reason: "-"
  hold_kind: "-"
  hold_until: "-"
  kind: scout
  repo: sample
  priority: "-"
  created: 2026-07-23
  closed: "-"
  deps: none
  links: none
  body: ""
help[2]:
  - Run `tasks-axi done sample-archive-review --pr <url>` when it ships
  - Run `tasks-axi block sample-archive-review --by <other>` to record a dependency

$ fm-decision-hold.sh hold sample-archive-review route ...
sample-archive-review-decision-route

$ fm-decision-hold.sh complete sample-archive-review route
complete: sample-archive-review decision inventory reviewed (route)

$ tasks-axi add/block dependent work
ok: added sample-archive-dependent (ship, repo sample) -> Queued
task:
  id: sample-archive-dependent
  title: Apply the retained route
  state: queued
  blocked: no
  blocked_by: none
  held: no
  hold_reason: "-"
  hold_kind: "-"
  hold_until: "-"
  kind: ship
  repo: sample
  priority: "-"
  created: 2026-07-23
  closed: "-"
  deps: none
  links: none
  body: ""
help[2]:
  - Run `tasks-axi start sample-archive-dependent` to move it to in flight
  - Run `tasks-axi block sample-archive-dependent --by <other>` to record a dependency
ok: block sample-archive-dependent -> blocked-by sample-archive-review-decision-route
help[2]:
  - Run `tasks-axi unblock sample-archive-dependent --by <other>` to clear it
  - Run `tasks-axi ready` to see what is still dispatchable

$ fm-decision-hold.sh resolve sample-archive-review route --routed-to sample-archive-dependent
resolved: sample-archive-review-decision-route -> sample-archive-dependent

$ complete 11 later items to trigger done_keep=10 retention
retention_result=decision exists only in data/done-archive.md

$ tasks-axi show sample-archive-review-decision-route --full
error: "Task \"sample-archive-review-decision-route\" not found in this backlog"
code: NOT_FOUND
help[1]: Run `tasks-axi list` to see existing tasks
active_lookup_exit=1

$ fm-decision-hold.sh complete sample-archive-review route  # after retention
complete: sample-archive-review decision inventory reviewed (route)

$ fm-decision-hold.sh verify sample-archive-review         # after retention
verified: sample-archive-review unresolved-decision inventory

$ fm-decision-hold.sh resolve sample-archive-review route ... # identical retry after retention
resolved: sample-archive-review-decision-route
archive_sha256_before=c97e1a4cbf868175ad0a2ee0d804828470a6d7accab54c4903cdb698f71ec1e8
archive_sha256_after=c97e1a4cbf868175ad0a2ee0d804828470a6d7accab54c4903cdb698f71ec1e8

$ tasks-axi show sample-archive-dependent --full
task:
  id: sample-archive-dependent
  title: Apply the retained route
  state: queued
  blocked: no
  blocked_by: none
  held: no
  hold_reason: "-"
  hold_kind: "-"
  hold_until: "-"
  kind: ship
  repo: sample
  priority: "-"
  created: 2026-07-23
  closed: "-"
  deps: none
  links: none
  body: ""

$ archived decision record
- [x] sample-archive-review-decision-route - Choose the retained route (repo: sample) (kind: captain) (done 2026-07-23) (hold: captain retained route pending) (hold-kind: captain)
  Resolution recorded by fm-decision-hold.
  Decision digest: 1a97d3719671e4a3edd2531a973bcdaa6da31c98c1ac6392cfc4d41d8ff786ec
  Routed identities: sample-archive-dependent

  Captain decision:
  Use route north.

  Unicode proof: 中文 and é.

  Routed work:- sample-archive-dependent


private_parser_views_remaining=0

$ fm-decision-hold.sh verify sample-archive-review  # tampered archive negative control
fm-decision-hold: archived captain decision sample-archive-review-decision-route does not match its decision digest
tampered_verify_exit=1

E2E_RESULT=PASS archived complete/verify/resolve succeeded; tampered archive was rejected
Evidence: 基线提交反事实

Base commit reports the archived decision absent from backlog.md and exits 1 on the same retained state.

Base commit counterfactual on the same retained state
base_commit=bd43c73fc956af2c7dfddd8ba74bab214a3012c0

$ fm-decision-hold.sh verify sample-archive-review
fm-decision-hold: captain decision sample-archive-review-decision-route is absent from /var/folders/_7/702wv9kj39gcb3_wqvrzq3r40000gn/T/no-mistakes-evidence/01KY6YX41FVCXZKBQ9VMXG6KNH/baseline-counterfactual-home/data/backlog.md
baseline_verify_exit=1
Evidence: Retention 后的持久化 archive

## Archived 2026-07-23
- [x] sample-archive-review-decision-route - Choose the retained route (repo: sample) (kind: captain) (done 2026-07-23) (hold: captain retained route pending) (hold-kind: captain)
  Resolution recorded by fm-decision-hold.
  Decision digest: 1a97d3719671e4a3edd2531a973bcdaa6da31c98c1ac6392cfc4d41d8ff786ec
  Routed identities: sample-archive-dependent

  Captain decision:
  Use route north.

  Unicode proof: 中文 and é.

  Routed work:- sample-archive-dependent

## Archived 2026-07-23
- [x] sample-retention-01 - Retention filler 01 (repo: sample) (kind: ship) (done 2026-07-23)
Evidence: Retention 后的 active backlog
## In flight
- [ ] sample-archive-review - Review archive-aware routing (repo: sample) (kind: scout) (since 2026-07-23)

## Queued
- [ ] sample-archive-dependent - Apply the retained route (repo: sample) (kind: ship) (since 2026-07-23)
## Done
- [x] sample-retention-11 - Retention filler 11 (repo: sample) (kind: ship) (done 2026-07-23)
- [x] sample-retention-10 - Retention filler 10 (repo: sample) (kind: ship) (done 2026-07-23)
- [x] sample-retention-09 - Retention filler 09 (repo: sample) (kind: ship) (done 2026-07-23)
- [x] sample-retention-08 - Retention filler 08 (repo: sample) (kind: ship) (done 2026-07-23)
- [x] sample-retention-07 - Retention filler 07 (repo: sample) (kind: ship) (done 2026-07-23)
- [x] sample-retention-06 - Retention filler 06 (repo: sample) (kind: ship) (done 2026-07-23)
- [x] sample-retention-05 - Retention filler 05 (repo: sample) (kind: ship) (done 2026-07-23)
- [x] sample-retention-04 - Retention filler 04 (repo: sample) (kind: ship) (done 2026-07-23)
- [x] sample-retention-03 - Retention filler 03 (repo: sample) (kind: ship) (done 2026-07-23)
- [x] sample-retention-02 - Retention filler 02 (repo: sample) (kind: ship) (done 2026-07-23)

Pipeline

Updates from git push no-mistakes

⏭️ **intent** - skipped

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 4 issues found → auto-fixed (3) ✅
  • 🚨 bin/fm-decision-hold.sh:327 - active_identity_count treats every non-regular backlog path as an empty store because [ -f ] is also false for directories, FIFOs, and broken symlinks. Archived verification can therefore succeed and permit scout cleanup while the active backlog is invalid and was never checked for collisions. Return zero only when the path is genuinely absent; reject unsafe or unreadable active-store paths.
  • 🚨 bin/fm-decision-hold.sh:396 - hold_reason is still TOON-encoded. A malformed archived header containing (hold: ) (hold-kind: captain) yields an empty reason rendered as &#34;&#34;; both shell predicates pass, so the record is accepted. Decode the field or explicitly reject the encoded empty value.
  • 🚨 bin/fm-decision-hold.sh:280 - The extractor accepts non-empty whitespace-only raw lines, which tasks-axi normalizes to empty lines before digest validation. Changing a canonical blank decision line to spaces therefore produces a stable archive that still verifies, contradicting the lossless canonical-record contract. Reject whitespace-only raw lines or validate the extracted record before parsing.
  • ⚠️ bin/fm-decision-hold.sh:505 - The active identity is rechecked before verify_archived_resolution, but that validation invokes another hash subprocess and the final pass checks only the archive. A concurrent insertion of the same active ID during that window can leave an active/archive collision while lookup returns success. Repeat the active check after final structured/archive validation or use a coherent store lock.

🔧 Fix: Harden archive-aware decision verification
1 error still open:

  • 🚨 bin/fm-decision-hold.sh:403 - The new decode loses the distinction between TOON's missing sentinel - and the valid quoted value &#34;-&#34;. Both command_hold and tasks-axi accept --reason -; after retention, this block decodes &#34;-&#34; to - and line 408 rejects it, making a writer-created decision unusable by complete, verify, resolve retry, and scout teardown. Check the raw unquoted sentinel before decoding, then reject only a decoded empty string.

🔧 Fix: Preserve quoted dash hold reasons
1 error still open:

  • 🚨 bin/fm-decision-hold.sh:249 - semanticLine strips trailing carriage returns while extractRecord accepts and copies CR-terminated nonblank lines into the parser view. Because tasks-axi also strips those carriage returns, changing an archived decision line from LF to CRLF leaves the parsed decision and digest unchanged, allowing a byte-corrupted archive to verify. Reject carriage returns anywhere in the extracted record before parsing.

🔧 Fix: Reject carriage returns in archived decisions
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • git status --short --branch && git rev-parse HEAD && git branch --show-current
  • git diff --name-status bd43c73fc956af2c7dfddd8ba74bab214a3012c0 c7314195f61e05cbce6fa7cba54df6bd71349aec
  • bin/fm-test-run.sh tests/fm-decision-hold-lifecycle.test.sh
  • bash .tmp-archive-aware-evidence.sh(临时驱动已删除;使用真实 tasks-axi 0.2.3 完成 hold、resolve、retention、归档后重试及篡改负控)
  • FM_HOME=<baseline-counterfactual-home> bash <base-code>/fm-decision-hold.sh verify sample-archive-review
  • 证据完整性检查:身份计数、archive SHA-256、关键 transcript 行及最终 git status --short
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

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