fix: preserve decision completion after history pruning - #3159
Open
Charliekirk-creator wants to merge 35 commits into
Open
Charliekirk-creator wants to merge 35 commits into
Charliekirk-creator wants to merge 35 commits into
Conversation
Charliekirk-creator
force-pushed
the
fm/fm-decision-hold-pruned-history
branch
from
August 27, 2026 06:15
d8302e6 to
05b1298
Compare
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the previously reported teardown path now invokes the shared archive-aware decision verifier. Reviews (3): Last reviewed commit: "no-mistakes: apply CI fixes" | Re-trigger Greptile |
Charliekirk-creator
force-pushed
the
fm/fm-decision-hold-pruned-history
branch
from
August 31, 2026 01:12
95d6ad8 to
ea404f5
Compare
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
Fix the retained-history defect in the shared decision-completion owner so a live investigation or visual review can open and complete later decisions after older resolved decision records have left the backlog's normal retained Done window. Reproduce and regress the behavior through the public bin/fm-decision-hold.sh interface in isolated fixture homes, including the initiating trigger, retention masking condition, visible completion failure, a proven retained-history path, relevant history, the smallest counterfactual, and disconfirming evidence. Preserve durable ownership of every unresolved captain decision: missing, malformed, or mismatched active decision records must stop completion. A historical decision durably resolved and later pruned by normal configured retention may remain pruned without preventing a later review pass. Do not weaken origin identity, home isolation, dependency routing, decision-file, containment, ordinary-file, symlink, hardlink, or answer-routing protections, and do not infer decisions from prose, artifacts, status text, chat, or titles. Keep completion and verification deterministic, idempotent, compatible with existing metadata, and ensure bounded Done retention does not oscillate by restoring pruned historical rows. Keep semantic policy in .agents/skills/decision-hold-lifecycle/SKILL.md and exact mechanics in bin/fm-decision-hold.sh, using pointers elsewhere. Cover live origins with pruned earlier resolutions, successful later decisions, unresolved decisions with missing records, malformed or mismatched retained records, repeated complete and verify calls, and existing metadata compatibility. Do not modify private Workstack Compass artifacts or operational records, change retention limits, restore pruned history by hand, weaken current decision ownership, or modify live captain decisions.
What Changed
Risk Assessment
Testing
The focused lifecycle suite passed, and isolated public-interface verification reproduced the base defect then demonstrated successful, idempotent later completion on the target without restoring pruned history; missing current ownership still failed safely and the worktree remained clean.
Evidence: Retained-history defect reproduction and fixed public-interface transcript
Source: Retained-history defect reproduction and fixed public-interface transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 4 issues found → auto-fixed (31) ✅
bin/fm-decision-hold.sh:370- Intent requires “do not infer decisions from ... titles,” but these wildcard checks accept(kind: captain)and(hold-kind: captain)anywhere in the raw header. A normally pruned ordinary task can place both strings in its title before its actual trailing(kind: ship)metadata and, with a resolution-shaped body, satisfy completion for a missing hold. Parse canonical structured fields rather than matching title text.bin/fm-decision-hold.sh:470- Intent requires “Do not weaken origin identity” and that missing active records stop completion, but archived proof is bound only to the concatenated hold ID. Because<origin>-decision-<key>is ambiguous, a genuine pruned resolution for originsample, keyroute-decision-lateralso proves originsample-decision-route, keylater;completethen succeeds without that second decision ever having a hold. Bind and validate origin/key in the shared resolution record, including the retained-Done sibling path.bin/fm-decision-hold.sh:278- Intent requires coverage and rejection of “malformed or mismatched retained records,” but the validator checks only that the digest is 64 lowercase hex characters and that decision text is nonempty. A retained or archived record containing 64 zeroes with unrelated captain text is accepted as resolved. Recompute and compare the decision digest, and validate routed identities against the routed-work section.bin/fm-decision-hold.sh:323- Intent says “Do not weaken ... home isolation ... containment ... symlink ... protections,” but backlog reads run underFM_HOMEwhile archived proof is read independently fromFM_DATA_OVERRIDE, with only the final leaf checked. WithFM_HOME=AandFM_DATA_OVERRIDE=B/data, a missing hold in A can be satisfied by B's archived row; a symlinked parent has the same issue. Resolve and verify the configured archive against the authoritative home before accepting it.🔧 Fix: Harden archived decision resolution proof
7 errors still open:
bin/fm-decision-hold.sh:619- Intent requires “missing ... active decision records must stop completion.” After keykwas resolved and pruned, a current structuredneeds-decision [key=k]with no active hold still passes because this fallback accepts the old archive;completethen records the captain-held transfer. Require active verification for currently open keys before allowing historical fallback.bin/fm-decision-hold.sh:370- Intent requires compatibility with existing metadata, but every pre-change resolution body lacksOrigin:andDecision key:. The parser recognizes that legacy shape, then this comparison rejects its empty identity fields, so retained or archived decisions created by released versions now failcomplete,verify, and retries. Add a versioned migration or compatibility path that preserves origin safety.bin/fm-decision-hold.sh:428- Intent forbids inferring decisions from titles. An ordinary kind-captain archived row titledOrdinary (hold: forged) (hold-kind: captain)has its actual(kind: captain)parsed first, then this loop continues into the title and treats both title fragments as hold provenance; a resolution-shaped body then satisfies completion. The added ship-kind test misses this sibling path. Stop parsing at the canonical metadata boundary or use a semantic provenance field that titles cannot supply.bin/fm-decision-hold.sh:412- Intent requires history pruned by normal configured retention to remain usable, but this hardcodesdata/done-archive.mdinstead of reading[markdown].archivefrom the same.tasks.tomlused by tasks-axi. A home configured with an in-home archive such asdata/history.mdprunes normally there and then fails verification. Resolve and containment-check the configured archive path.bin/fm-decision-hold.sh:337- Intent requires malformed or mismatched retained records to stop completion and preserves answer-routing semantics, but the parser acceptsResolution mode: declined|answered|repairedwith nonempty routed identities, orroutedwith(none), whenever the two routing lists match. Enforce the mode-to-routing invariant, retaining only an explicitly supported legacy no-mode format.bin/fm-decision-hold.sh:697- Intent says not to weaken origin identity and docs claimholdrejects identity collisions. For two origin/key pairs that concatenate to the same ID, an existing active hold with the same title reaches this mutation; tasks-axi changes its reason before line 699 discovers the body belongs to the other origin and fails. Validate existing body provenance before mutating the hold.bin/fm-decision-hold.sh:618- Intent requires malformed active records to stop completion, but everytasks-axi showfailure—not only NOT_FOUND—is treated as pruning. If the backlog or tasks configuration is unreadable/malformed while a matching archive remains readable, verification succeeds without inspecting active state. Distinguish an exact not-found result from backend, parse, permission, and configuration failures before consulting history.🔧 Fix: Harden retained decision history verification
2 errors still open:
bin/fm-decision-hold.sh:392- Intent requires compatibility and deterministic, idempotent later reviews, but legacy resolution proof requiresstate/<origin>.meta. Normal teardown deletes that file, so a base-version resolution later pruned from Done is no longer recognized; a post-teardownholdretry can recreate the same resolved ID and eventually produce duplicate archive rows. Persist or migrate an unambiguous origin/key attestation before teardown rather than authenticating durable history with ephemeral live metadata.bin/fm-decision-hold.sh:1269- Intent says “Do not weaken origin identity,” butrepairvalidates only state, kind, and hold kind before replacing the body. If origin A/key X was directly closed and origin B/key Y composes to the same hold ID,repair B Yskips the resolution branch and overwrites A's structured awaiting provenance with a resolution bound to B. Validate the existing body's origin/key provenance before this mutation, as the active close paths do.🔧 Fix: Preserve legacy decision identity through teardown
5 errors still open:
bin/fm-decision-hold.sh:425- Intent requires “Do not weaken origin identity.” This fallback treats surviving claimant metadata as proof of a legacy record’s origin, but metadata for historical owners is normally deleted. A released resolution for originsample, keyroute-decision-latercan therefore be accepted and attested for an existing colliding inventory at originsample-decision-route, keylateronce the original metadata is gone. Only attest while the original structured owner is durably verifiable; unattested legacy records cannot be safely rebound from claimant metadata alone.bin/fm-decision-hold.sh:895- Intent requires compatibility with existing metadata and deterministic, idempotent answer routing. If a releasedresolvewrote its legacy body and then failed while unblocking work, the hold remains queued. Teardown verification validates that body through live metadata here but returns without persisting an attestation; teardown then deletes the metadata, and the documented resolution retry fails provenance validation. Persist exact queued legacy resolution identity before teardown, while distinguishing it from an ordinary awaiting body.bin/fm-decision-hold.sh:395- Intent says “Do not weaken ... home isolation ... symlink ... protections.” Archive and data paths are bound toFM_HOME, but legacy identity is authorized from an unvalidatedSTATE.FM_STATE_OVERRIDEpointing at another home, or a symlinked$FM_HOME/state, can supply reviewed metadata that causes an attestation to be written into the current home. Resolve and containment-check the authoritative state directory before metadata can authorize migration.bin/fm-decision-hold.sh:582- Intent requires normal configured retention to work. tasks-axi lazily creates configured archive parents during pruning, but this check rejects a valid path such asdata/history/done.mdwhiledata/historydoes not yet exist. Every newholdthen fails during its absent-history check before creating the decision. Validate the normalized path through an existing contained ancestor without requiring the future archive directory to preexist.bin/fm-decision-hold.sh:323- Intent requires malformed retained records to be rejected and decision-file protections preserved. New decisions are limited to 8192 bytes, but the retained-record parser only requires nonempty text and a matching digest, so a correctly hashed resolution containing an arbitrarily large captain decision is accepted despite being impossible for this owner to create. Enforce the same byte bound on parsed retained decisions.🔧 Fix: Harden legacy resolution migration and archive validation
4 issues (3 errors, 1 warning) still open:
bin/fm-decision-hold.sh:1098- Intent requires “missing ... active decision records must stop completion.” Forneeds-decision [key=k]followed by a normal terminaldone,origin_open_decisionssuppressesk, so active verification is skipped. If an olderkis archived,complete origin ksucceeds and later appends acaptain-heldtransfer for the nonexistent active hold. Require active ownership for every raw key being transferred before allowing historical fallback.bin/fm-decision-hold.sh:833- Intent permits only a decision “later pruned by normal configured retention” and forbids inference from prose or artifacts, but this scan accepts a matching top-level bullet anywhere in the file without requiring an## Archived YYYY-MM-DDblock. A resolution-shaped row under## Notesor before any archive heading can therefore prove a missing hold. Track archive section boundaries and accept only canonical archived Done records.bin/fm-decision-hold.sh:434- Intent requires compatibility with existing metadata, but released versions allowed valid keys or origins containing-decision-. Their legacy resolution records are now rejected unconditionally before live metadata or an existing exact attestation is checked, even when no competing owner exists. This deliberate containment needs explicit approval or a migration path that preserves both compatibility and origin identity.bin/fm-decision-hold.sh:745- Attestation publication links the staging file to its final name and then removes the staging link without checking removal. Interruption or removal failure leaves link count 2; subsequent validation hard-fails as a hardlink and cannot retry even while valid metadata survives. Use a crash-recoverable no-clobber publication mechanism or safely recognize and clean the owned staging link.🔧 Fix: Harden active and archived decision ownership
5 errors still open:
bin/fm-decision-hold.sh:1024- Intent requires “Do not weaken ... home isolation ... symlink ... protections,” but authoritative state validation runs only for legacy records. With FM_HOME=A, FM_STATE_OVERRIDE=B/state (or a symlinked A/state), B’s origin metadata, and A’s archived current-format record for the same origin/key, this return accepts history andcompletecan update B’s metadata. Validate the state root before any complete/verify state read, lock, or write.bin/fm-decision-hold.sh:1147- Intent requires “missing ... active decision records must stop completion” and preserved symlink protections.status_open_decisionsreturns empty for a present symlinked or unreadable status file, so this code treats an unsafe current-key source as no current key and permits matching archived history to satisfy completion without an active hold. Reject unsafe present status files before historical fallback.bin/fm-decision-hold.sh:412- Intent requires compatibility with existing metadata, but any alternate-decomposition origin blocks migration even when it does not claim the colliding key. A valid legacy record forsample/route-decision-laterwith exact reviewed source metadata is rejected merely because unrelated originsample-decision-routeexists without keylater. Detect competing reviewed ownership, not mere origin existence.bin/fm-decision-hold.sh:812- Intent forbids inferring decisions from titles, but this regex accepts an empty(hold:). A semantically ordinary Done row titled with trailing(repo: sample) (kind: captain) (hold:) (hold-kind: captain)can be normally pruned; tasks-axi does not parse the malformed hold token, while this archive parser strips it as provenance and accepts a resolution-shaped body. Require a nonempty canonical hold field and stop at malformed metadata.bin/fm-decision-hold.sh:381- Intent requires malformed retained records to stop completion, but routed identities only receive a character whitelist. A retained body usingRouted identities: -ghostwith matching routed-work text, digest, and mode is accepted even though tasks-axi task IDs must begin with an alphanumeric character and the owner could never create that route. Enforce the tasks-axi ID grammar here.🔧 Fix: Harden retained decision ownership and compatibility
3 errors still open:
bin/fm-decision-hold.sh:1177- Intent forbids inferring decisions from “status text,” but this branch uses the status fold to decide whether a listed key requires an active hold or may fall back to archived history. Consequently, the same explicitcompleteinventory receives weaker ownership checks when a visual review has no matching status event. Carry current-versus-historical provenance through the public completion inventory or another structured owner instead of deriving it from status text.bin/fm-decision-hold.sh:425- Intent requires compatibility with existing metadata, but every pre-upgrade resolution lacking embedded origin/key is rejected after normal teardown unless this new version previously created an attestation. For a common unambiguous ID such assample-review-decision-route, teardown removessample-review.meta, so retained Done or normally archived history that worked before the upgrade can no longer satisfycompleteorverify. Accept the unique ID decomposition directly while reserving attestations for genuinely ambiguous IDs.bin/fm-decision-hold.sh:443- Intent says “Do not weaken origin identity” and missing active records must stop completion, but alternate ownership is checked only while the historical origin still has live metadata, a report, or an active backlog row. If released history for A/x is archived after A’s ship origin and metadata are pruned, a previously reviewed colliding B/y whose active hold is missing makesorigin_exists_here(A)false; B’s metadata then attests A’s legacy row and completion succeeds. Ambiguous legacy rows need an exact pre-existing attestation or proof bound while their original owner remains verifiable, not claimant metadata plus absence of live artifacts.🔧 Fix: Harden current and legacy decision provenance
3 errors still open:
bin/fm-decision-hold.sh:421- Intent requires compatibility with existing metadata, but released records such as originsample, keyroute-decision-laterhave no embedded identity and cannot have this change's newly introduced attestation. This unconditional rejection means they fail verification even while original reviewed metadata still exists. Migrate only while the original structured owner is durably verifiable, or explicitly authorize this permanent fail-closed containment.bin/fm-decision-hold.sh:1150- The shared slug grammar andholdstill accept keys such as--route, and the basecompleteparser accepted them, but this new wildcard treats every leading-hyphen key as an option. Such a hold can now be created but never inventoried, and equivalent pre-upgrade active holds break compatibility. Add end-of-options handling or consistently reserve the grammar with a migration path.bin/fm-decision-hold.sh:236- Every hold, complete, and verify operation now requiresjq, but the documented universal toolchain and bootstrap do not require it for default tmux or orca homes. A currently supported installation without optional jq therefore cannot use the required completion gate and scout teardown remains blocked. Avoid the dependency or make it an explicit universal prerequisite through the toolchain owner.🔧 Fix: Preserve legacy decisions and option-shaped keys without jq
1 error still open:
bin/fm-decision-hold.sh:515- Intent requires “Do not weaken origin identity,” but this migration treats claimant metadata plus missing or excluding alternate artifacts as provenance. Once legacy history forsample/route-decision-laterloses its origin artifacts, metadata forsample-decision-route/latercan bind the same concatenated ID, persist a false attestation at line 527, and satisfy completion without that owner’s resolution. Only attest while the original owner is exactly verifiable; otherwise require an existing attestation or fail closed.🔧 Fix: Prevent ambiguous legacy decision ownership rebinding
3 errors still open:
bin/fm-decision-hold.sh:478- Intent requires both “compatible with existing metadata” and “Do not weaken origin identity.” This rejects a valid legacysample/route-decision-laterrecord whenever alternatesample-decision-route.metanever existed, yet mutable metadata still permits rebinding ifsampleis later reused with a reviewed inventory excludingroute-decision-laterand claimant metadata claimssample-decision-route/later; the old row is then falsely attested. Ambiguous legacy rows need pre-existing durable identity proof or explicit approval for fail-closed/manual migration.bin/fm-decision-hold.sh:820- Intent requires compatibility with existing metadata, but released task IDs have no length limit while attestations use<hold-id>.attestationas one filesystem component. On common 255-byte filesystems, a valid legacy hold ID over 243 bytes passes tasks-axi and identity validation butlnfails withENAMETOOLONG, making verification permanently fail. Use a bounded digest-based filename while retaining the full identity in the attestation content.bin/fm-decision-hold.sh:798- Intent forbids weakening hardlink protections, but any second hardlink named.attestation.*is treated as an owned interrupted stage and deleted. The added regression even uses.attestation.interrupted, which the six-charactermktemptemplate cannot produce. Without durable stage ownership evidence, an unrelated hardlink is silently modified and accepted instead of rejected.🔧 Fix: Harden legacy decision attestations and provenance
2 errors still open:
bin/fm-decision-hold.sh:451- Intent requires compatibility with existing metadata, but released records have no attestations. A valid pre-upgrade record for originsample, keyroute-decision-lateris rejected solely because its ID has another decomposition, even when exact reviewed metadata survives. The policy/test rewrite to fail closed does not satisfy the authoritative requirement; obtain explicit containment approval or provide a safe migration path.bin/fm-decision-hold.sh:472- Intent requires malformed active records to stop completion, but this accepts any valid resolution mode as active provenance. A queued captain hold containing a correctly hashedResolution mode: repairedbody passescompleteandverify, althoughrepaircan only create that mode after confirming the hold is Done. Enforce the mode/state invariant at the shared queued-record validation boundary.🔧 Fix: Harden legacy migration and queued resolution validation
4 issues (3 errors, 1 warning) still open:
bin/fm-decision-hold.sh:1276- Intent requires “Do not weaken origin identity,” butmigrate-legacytreats the absence of current competing metadata as ownership proof. After a genuine legacy row forsample/route-decision-lateris archived and its metadata is removed by teardown, metadata for collidingsample-decision-route/latercan supply the recorded answer, persist a false attestation, and makecomplete --none --resolved latersucceed without that origin ever owning a hold. Ambiguous rows require pre-existing origin-bound proof or an independently authorized mapping; claimant metadata and a readable answer digest cannot establish ownership.bin/fm-decision-hold.sh:1353- Intent requires preserving “home isolation ... symlink, hardlink ... protections,” butcompleteandverifyvalidate only the state directory, then follow a symlinked or hardlinked<origin>.meta. A foreign metadata leaf can supply archived keys to the new fallback, andcompletemay append through that link. Validate the metadata leaf as readable, ordinary, non-symlinked, and single-linked after locking and before every read or write.bin/fm-decision-hold.sh:713- The new path checks requireFM_DATA_OVERRIDEandFM_STATE_OVERRIDEto equal exactly$FM_HOME/dataand$FM_HOME/state, breaking the documented use of alternate operational directories for specialized harnesses even when they are ordinary and contained withinFM_HOME. A home-local fixture data directory with matching tasks-axi backlog/archive configuration now fails before creating a hold. Enforce physical containment and alignment with the effective override rather than equality with the default path.docs/decision-hold-lifecycle.md:20- Intent requires “exact mechanics in bin/fm-decision-hold.sh, using pointers elsewhere,” but this section duplicates archive normalization, canonical header parsing, digest/routing validation, attestation publication/recovery, and migration mechanics. Replace these details with pointers to the script or its public help while retaining regression evidence.🔧 Fix: Harden decision history ownership and path safety
2 errors still open:
bin/fm-decision-hold.sh:454- Intent requires “compatible with existing metadata,” but legacy records are accepted only when the composed ID is uniquely decomposable or this new feature's attestation already exists. Released versions allowed identities such assample/route-decision-laterand could not create that attestation;migrate-legacyalso requires an existing attestation at line 1262. This is permanent fail-closed containment without explicit authorization. Provide independently origin-bound migration or obtain approval for the compatibility break.bin/fm-decision-hold.sh:274- Intent requires preserving “home isolation ... containment ... symlink, hardlink ... protections,” but origin ownership follows metadata and report leaves using bare-f. A symlinked$FM_HOME/state/o.metato any foreign regular file letshold o kcreate a local captain hold becauseholdnever invokes the new safe-state boundary; post-teardown completion similarly accepts a foreign or linked report for a current-format active hold. Validate resolved roots and ordinary, single-linked ownership leaves inside the sharedorigin_exists_hereboundary before mutation or completion.🔧 Fix: Harden legacy migration and origin ownership boundaries
1 error still open:
bin/fm-decision-hold.sh:1312- Intent requires compatibility with existing metadata and says a durably resolved, pruned decision must not prevent a later review. However,migrate-legacyrequires<origin>.meta, which normal teardown deletes. An ambiguous pre-upgrade resolution with a surviving report, archive row, decision, and authorized identity mapping therefore cannot be migrated:complete --resolvedrejects the missing attestation, while migration fails before checking the mapping. Support migration from durable post-teardown ownership evidence or obtain explicit approval for permanent fail-closed containment; do not require recreating operational metadata.🔧 Fix: Enable post-teardown legacy decision migration
3 errors still open:
bin/fm-decision-hold.sh:837- Intent permits only a decision “later pruned by normal configured retention” and forbids artifact inference, but this rejects only an alias with the active backlog. tasks-axiupdate --archive-bodywrites Done snapshots tonote-archive.mdusing the same archive heading and task-row format. Withmarkdown.archive = "data/note-archive.md", a superseded resolution snapshot can satisfy completion after the live task is removed even though Done retention never pruned it. Reject the note-archive alias or require retention-specific provenance.bin/fm-decision-hold.sh:1256- Intent requires “missing ... active decision records must stop completion.” Released code could recreate an active hold after an older resolution with the same ID was pruned. The current positional completion accepts that active hold without detecting the archived duplicate; if it is later removed, repeatedcomplete --noneorverifyreaches this fallback and accepts the older resolution. Reject active/archive generation collisions at the shared active-verification boundary or persist generation provenance.bin/fm-decision-hold.sh:1087- Intent allows only normal retention history and forbids inference from artifacts, but archive mode remains enabled across###headings or other column-zero prose while no record is being captured. A resolution-shaped row under## Archived ...then### Notesis therefore accepted as pruned history. Reject unexpected top-level content within archive sections instead of continuing to scan it as canonical retention output.🔧 Fix: Harden retained decision archive provenance
1 error still open:
bin/fm-decision-hold.sh:1278- Intent requires “missing ... active decision records must stop completion” and completion to remain “idempotent, compatible with existing metadata.” A base-version home can resolve and archive key k, recreate and successfully inventory an active k, then lose that active row before upgrade; because metadata stores only k, this fallback accepts the older archive generation and verify/complete succeeds despite the missing active record. Conversely, line 1511 makescomplete origin kfail after k is answered, so an exact successful completion retry is no longer idempotent. Persist generation/current-vs-historical provenance at the shared completion metadata boundary and explicitly migrate ambiguous legacy inventories rather than inferring provenance from presence or absence.🔧 Fix: Persist decision completion generation provenance
4 issues (3 errors, 1 warning) still open:
bin/fm-decision-hold.sh:1730- Intent requires compatibility with existing metadata, butverifyrejects every nonempty pre-change inventory before examining its records. A normal upgraded origin with an exact active captain hold and no archived duplicate now fails teardown verification. Automatically classify source-verifiable active or retained-Done cases at the shared metadata boundary; require explicit reclassification only for genuinely ambiguous archive-only generations.bin/fm-decision-hold.sh:1674- Intent requires completion to remain idempotent. After normal teardown removes metadata, positionalcomplete origin keysucceeds for an active post-teardown review but persists no provenance here. Once the hold resolves, repeating that exact successful command reaches the active-only check at line 1630 and fails. Persist completion provenance in a durable shared owner for metadata-free reviews, or explicitly authorize this non-idempotent behavior.bin/fm-decision-hold.sh:478- The policy says a legacy identity remains compatible when it has exactly one valid origin/key decomposition, but this raw substring check counts invalid splits. A released key such asroute-decision-is valid and producesorigin-decision-route-decision-; the second marker leaves an empty key, so only the original decomposition is valid, yet verification fails as ambiguous. Enumerate only splits whose origin and key both satisfy the supported grammar..agents/skills/decision-hold-lifecycle/SKILL.md:47- The normative policy says--resolvedmay be used only after live metadata is gone, whilebin/fm-decision-hold.shrequires it to reclassify historical keys in a surviving released-version metadata file. Following the policy leaves those inventories permanently unverifiable. Permit--resolvedfor explicit live legacy reclassification as well.🔧 Fix: Preserve legacy and metadata-free decision completion provenance
2 issues (1 error, 1 warning) still open:
bin/fm-decision-hold.sh:1779- Intent requires completion and verification to remain “idempotent, compatible with existing metadata.” Released metadata stores only a union: after successfulcomplete o afollowed bycomplete o b, it containsa,b. Once both records are retained Done, this migration inventsa,bas the last invocation (also persisted at line 1948), so retrying the actual last commandcomplete o bis not considered exact and line 1804 incorrectly requiresbto be active. Preserve source-verifiable per-key generation provenance without synthesizing call grouping, and let a successful post-upgrade completion establish the actual last lists.docs/decision-hold-lifecycle.md:100- The section claims to show the test suite's exact summarized output, but it omits the two newly emitted results for source-verifiable legacy migration and metadata-free retry provenance. Add those lines so the recorded regression evidence matches the executable suite.🔧 Fix: Preserve exact legacy completion retry provenance
1 error still open:
bin/fm-decision-hold.sh:1891- Intent requires “Preserve durable ownership of every unresolved captain decision: missing ... active decision records must stop completion” and idempotent completion, but durable inventory is persisted only when metadata is already absent. Sequence: livecomplete origin arecordsaonly in metadata; normal teardown deletes that metadata; the activeahold disappears; a later report-backed review createsbandcomplete origin bsucceeds because no durable inventory remembersa. Likewise, resolvingaafter teardown makes the exact original completion retry fail. Persist and reconcile completion provenance at the shared successful-completion boundary even while live metadata exists, so teardown cannot erase it.🔧 Fix: Persist live decision completion provenance durably
2 errors still open:
bin/fm-decision-hold.sh:2002- Intent requires “Preserve durable ownership of every unresolved captain decision” and compatibility with existing metadata. A pre-upgrade inventory containing active keyacan be source-classified by teardown’sverify, but this writes provenance only back to metadata; teardown then deletes that metadata. Ifalater disappears, a report-backed review can complete new keybbecause no durable inventory remembersa. Merge and persist the classified per-key provenance in the durable completion inventory before verification permits teardown.bin/fm-decision-hold.sh:1908- Intent requires durable ownership and deterministic completion, but live metadata is marked reviewed before the durable inventory write at line 1913. If that later write fails (for example, staging or replacement runs out of space),completereturns failure while teardown’sverifystill trusts the newly appended metadata and can delete it, losing the required durable provenance. Persist the durable owner first, then publish the ephemeral metadata attestation, or make both updates transactionally recoverable.🔧 Fix: Persist decision provenance before metadata teardown
2 issues (1 error, 1 warning) still open:
bin/fm-decision-hold.sh:960- Intent permits only history “later pruned by normal configured retention” and forbids inference from “artifacts,” but this alias check compares path strings only. On a common case-insensitive macOS filesystem, configuringdata/NOTE-ARCHIVE.mdaliases tasks-axi'sdata/note-archive.mdwhile passing this check; a superseded--archive-bodysnapshot can then prove a removed decision without Done retention ever pruning it. Reject physical aliases of the backlog and note archive (for example with inode/-efchecks once either leaf exists), not just identical spellings.bin/fm-decision-hold.sh:1439- Each active or retained key invokesreject_archived_generation_collision, which reparses the entire configured archive, while complete/verify iterate the monotonically growing durable key union. This makes the target long-lived-review path O(keys × archive size). Parse and validate the archive once per invocation and reuse an indexed result for collision and historical lookups.🔧 Fix: Reject archive aliases and cache retained-history scans
2 errors still open:
bin/fm-decision-hold.sh:973- Intent requires history to be “later pruned by normal configured retention” and forbids inference from artifacts, but this rejects only path/inode aliases tonote-archive.md. A valid resolved snapshot produced bytasks-axi update --archive-bodycan be copied todata/history.md, configured asmarkdown.archive, and accepted after the live row is removed. Bind historical proof to retention-specific provenance rather than any canonical-looking file currently configured as the archive.bin/fm-decision-hold.sh:950- The required compatibility with normal configured retention is broken for valid tasks-axi homes whose contained backlog is not exactly<FM_DATA_OVERRIDE>/backlog.md. For example, the supported/defaultpath = "backlog.md"witharchive = "done-archive.md"is rejected before any hold or completion can run. Use the effective contained backlog path and derive its archive owners instead of forcing the Firstmate default layout.🔧 Fix: Bind retained decisions to effective backlog archives
3 issues (1 error, 2 warnings) still open:
bin/fm-decision-hold.sh:1749- Required: historical proof must come from “normal configured retention,” and copied artifacts must not qualify. This code creates legacy bindings after reading an unbound archive row, while note-copy detection at line 1629 is only byte-for-byte comparison. A resolved note snapshot can be appended under a canonical archive heading with only its title changed; the preexisting owner marker/binding then makescompleteaccept it without any retention prune. Conversely, an honestly pruned row identical to a note snapshot is rejected. Bind each record digest to provenance emitted at the actual tasks-axi prune boundary rather than self-attesting on read or comparing content.bin/fm-decision-hold.sh:1158- Bindings are named only by hold ID but embed the absolute backlog/archive paths. After an active hold is verified, changing to another valid contained tasks-axi backlog or archive creates the new owner marker, then this comparison hard-fails against the old binding; the source-verifiable active hold can no longer complete or resolve. Namespace bindings by retention owner and allow a new owner binding only while the exact active or retained-Done source remains verifiable.bin/fm-decision-hold.sh:1133- The marker is appended under a private owner lock rather than tasks-axi's effective-backlog lock. If this overlaps a tasks-axi prune whose later backlog persistence fails, tasks-axi rolls the archive back to its captured length and can remove the marker after the owner file was committed, permanently failing subsequent owner validation. Publish provenance through the same transaction or supported shared lock as archive retention.🔧 Fix: Bind decision history to actual retention transitions
3 errors still open:
bin/fm-decision-hold.sh:1734- Required: completion must be “compatible with existing metadata,” and an older decision “later pruned by normal configured retention may remain pruned.” Every archive produced before this change lacks the newly invented HMAC marker, so this unconditional check rejects the exact already-pruned history the fix targets; evenmigrate-legacycalls this boundary first and cannot migrate it. Provide a safely authorized compatibility/migration path or obtain explicit approval for permanent fail-closed containment.bin/fm-decision-hold.sh:1121- Required: history pruned by “normal configured retention” must remain usable. Provenance is emitted only while tasks-axi runs through this opt-in NODE_OPTIONS wrapper; the still-supported directtasks-axi done,prune, or terminal public-followup paths can normally prune a resolved decision without a marker, after which line 1734 permanently rejects it. Put provenance at the shared tasks-axi prune transaction boundary rather than relying on callers to select a parallel wrapper.bin/fm-decision-hold.sh:1005- On a case-insensitive filesystem, configuringNOTE-ARCHIVE.mdwhile neither archive exists passes this check becausepaths_physically_aliasrequires both leaves to exist. The first retention transition then creates the shared note/Done archive successfully; subsequent completion detects the now-existing alias and permanently rejects the genuinely pruned records. Detect destination aliases before leaf creation, preserving the required artifact separation and usable normal retention.🔧 Fix: Harden retained-history provenance and legacy migration
2 errors still open:
bin/tasks-axi:44- Required normally pruned resolutions must remain usable. In supported environments where the projectbindirectory is first on PATH,fm-decision-hold.shalready has a retention hook active when this wrapper re-enters it fordone/prune. The nested command inherits the firstNODE_OPTIONShook and installs a second; during a decision archive append, one hook parses the other hook's added marker as malformed task bytes, causing retention to fail or roll back. When provenance is already active, invoke the resolved real tasks-axi binary instead of nesting the owner again.bin/fm-decision-hold.sh:957- Required: “Do not weaken ... symlink ... protections.”destination()treats ENOENT from a dangling symlink as though the link name itself were simply absent. Withnote-archive.md -> history.mddangling andhistory.mdconfigured as Done retention, preflight accepts the paths; the first prune createshistory.md, after which every completion or verification rejects the now-visible alias. Resolve dangling links with lstat/readlink before accepting future destinations.🔧 Fix: Harden nested retention and dangling archive alias handling
2 errors still open:
bin/tasks-axi:48- Required history “later pruned by normal configured retention” must remain usable, but provenance is added only when callers explicitly select this new wrapper. The repository still directs routine work to baretasks-axi, no session prependsbin/to PATH, and directtasks-axi prune/doneremains executable after this change; such a prune creates an unmarked archive row that later completion rejects as pre-boundary history. Route every supported retention command through this boundary or emit provenance in tasks-axi's shared prune transaction.bin/fm-decision-hold.sh:2844- Afteranswer,decline, orresolvesucceeds and normal retention later archives the hold, an exact retry fails becauseverify_hold_resolvedchecks onlytasks-axi showin the active backlog. With validdone_keep=0, this line even reports the first close as failed after it already committed and archived the decision. Validate exact proven archive history at the shared resolved-record boundary so close and channel-delivery retries remain idempotent.🔧 Fix: Preserve pruned decision close retries
1 error still open:
bin/fm-decision-hold.sh:2143- This contradicts “missing ... active decision records must stop completion” and “do not weaken current decision ownership.” A valid sequence remains: an old generation is pruned into configured archive A; configuration moves to contained archive B; the same key is recreated and completed as current; that active row disappears; configuration returns to A. The durable inventory stores only the key/classification, so this fallback accepts A’s older resolution andverifysucceeds despite the current generation being missing. Line 2108 similarly lets close retries consume that older generation. Bind each current generation to its verified retention owner or exact record provenance at the shared completion boundary, and only allow historical fallback when that generation’s matching retention transition is proven.🔧 Fix: Bind current decisions to retention generations
1 error still open:
bin/fm-decision-hold.sh:2659- The required invariant says “missing ... active decision records must stop completion,” but a previously current key supplied via--resolvedis checked only byverify_hold_historical. Sequence: inventory a replacement generation under archive owner B, remove its active row, switch back to owner A containing an older archived generation, then runcomplete origin --none --resolved key; this branch accepts A’s history, skips the current-key durable check below, and succeeds despite the missing B generation. Reject--resolvedfor already-current provenance or validate it through the generation-bound durable boundary.🔧 Fix: Enforce generation ownership for resolved decision keys
1 error still open:
bin/fm-decision-hold.sh:2660- Policy says--resolvedcarries an older key with exact durable resolution, but this branch callsverify_hold_durable, which also accepts a queued active hold. Sequence: successfully runcomplete origin keywhile the hold is active, then runcomplete origin --none --resolved keybefore answering it; completion succeeds and records the key in the resolved invocation even though it remains unresolved. Require generation-bound resolved-state verification here rather than the active-or-resolved durable check.🔧 Fix: Require resolved state for current decision provenance
1 error still open:
bin/fm-decision-hold.sh:2192- Required: “missing ... active decision records must stop completion” and “do not weaken current decision ownership.” An older retained-Done generation can still replace a missing current generation: retain resolved key k in backlog owner A, switch to owner B, recreate/inventory k, remove B’s active row, then switch back to A and runcomplete --none --resolved k.verify_hold_resolvedfinds A’s old Done row and overwrites the B generation binding here before line 2207 checks it, so completion succeeds. Validate an existing generation binding before mutation; permit owner migration only when continuity with the exact bound active generation is verifiable.🔧 Fix: Prevent retained Done generation rebinding
2 issues (1 error, 1 warning) still open:
bin/fm-decision-hold.sh:2286- Intent requires that “missing ... active decision records must stop completion” and forbids weakening current decision ownership. A queued record can still overwrite an existing generation binding without validation: keep old key k active in backlog A, switch to backlog B, recreate and complete k (binding it to B), remove B’s row, switch back to A, then retrycomplete origin kor runverify.verify_hold_durableaccepts A’s older queued row and rewrites the binding here, so the missing B generation is silently replaced. Check existing generation ownership before every queued-record persistence at the shared verification boundary, allowing migration only when exact continuity is verifiable.docs/decision-hold-lifecycle.md:103- This block claims to contain the test suite’s exact summarized output, but it omits the newly emitted results “current generations cannot fall back to older archive owners” and “current generations cannot fall back to older retained Done owners.” Add both lines to keep the recorded regression evidence synchronized.🔧 Fix: Prevent queued decision generation rebinding
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
bash tests/fm-decision-hold-lifecycle.test.shPublicbin/fm-decision-hold.shend-to-end fixture: create/resolve an old decision, prune it through configured retention, create a later decision, repeatcompleteandverify, and confirm archive/backlog hashes and the 10-row Done bound remain unchanged.Missing-record counterfactual:fm-decision-hold.sh complete review-live missing-currentwas rejected while the valid later hold remained queued and held.Base commit64d61aereproduction in an isolated fixture: later completion failed after the older resolved decision was normally archived, demonstrating the original defect.✅ **Document** - passed
✅ No issues found.
🔧 **Lint** - 1 issue found → auto-fixed (2) ✅
🔧 Fix: Fix shell lint diagnostics
1 warning still open:
🔧 Fix: Verify shell and workflow lint passes
✅ Re-checked - no issues remain.
✅ **Push** - passed
✅ No issues found.