Task — engineering-spec@1

"memory-append --dry-run — rehearse an append, write nothing"

doneTASK-IMP-146
module improvement · class product · priority p2 · created 2026-07-25 · shipped null
depends on none · blocks none

TASK-IMP-146: memory-append --dry-run

Summary

memory-append.mjs append has no rehearsal mode: the only way to learn whether a status_overridden payload will be accepted is to append it, and the chain is append-only, so a probe row is permanent. Add --dry-run, which runs every refusal the real path runs and reports the seq, chain hash and memory path the append would produce — touching no byte of the store.

Problem

The gated-flip path (backlog-mutate flip … --verdict-by --verdict-evidence) appends one status_overridden row per HITL verdict. Its payload has five required non-empty string fields, a safe-token constraint on task_id, and preconditions on the store itself (lease free, chain verifies, HEAD agrees with the rows). All of those are checked inside cmdAppend, and the only way to exercise them is to perform the append.

Batch/8 shipping did exactly that and left probe rows on the live BRAIN store, which the post-1.2.0 plan called out as Wave 2 work ("optional: memory-append --dry-run; avoid probe rows on the live store"). TASK-CUO-305 folded the other four friction items into ship-tasks.md and did not carry this one. Doctrine §6.5 forbids tail rewrites, so the operator's only alternative today is to copy the whole store and probe the copy — which also drops the one precondition that matters most, the live store's own state.

Proposed Solution

Add a --dry-run flag to append. In dry-run mode cmdAppend performs, in order: the same closed-kind / JSON-object / status_overridden-field / safe-token refusals it always performs before any write; a lease inspection that refuses with the same exit 3 when the lease is held (without minting one); the full chain walk and HEAD/tip reconciliation (reporting, never re-publishing, a one-behind HEAD); and then the seq, chain hash, prev_chain and memory path it would write. It creates no store root, writes no .lock, bootstraps no scaffold, sweeps no tmp litter, appends no frame, publishes no HEAD and rebuilds no peaks.bin. Against a store that does not exist, it reports the bootstrap it would perform instead of performing it.

The lease check is factored out of acquireLease into an inspectLease helper so the dry-run and the real path share one refusal rule rather than growing a second copy of it.

Alternatives Considered

Success Metrics

Scope

In scope: the flag, the inspectLease refactor, help/header documentation, suite coverage, CHANGELOG.

Out of scope / Non-Goals

Dependencies

None. TASK-IMP-141 (MMR sync) is done and its syncMmrPeaks call is one of the writes the dry run must skip.

AI Authorship Disclosure

1. Description

Acceptance Criteria

Test plan

  1. bash tools/install/tests/test_memory_append.sh
  2. bash tools/install/tests/test_hitl_lock.sh
  3. bash scripts/tests/run_all.sh
  4. bash .cyberos/cuo/gates/run-gates.sh

3. Edge cases