Force one boot at the compaction boundary - #737
Conversation
A compacted FO session keeps its session id and transcript but never re-reads
mods/gates/state, because Startup believes it is a continuation. status --boot
now writes a one-line per-session receipt (.spacedock/boot/{session_id}); the
three authority verbs (gate record, gate consume, merge guard) refuse (exit 4,
BOOT_STALE) when the receipt is missing or older than the session's latest
compact_boundary record, naming the exact remedy (re-run status --boot). No
hook, no escape hatch; every degraded condition (no identity, no transcript,
a malformed receipt) fails OPEN, never closed, so a false refusal can never
outrank the guard's real job of stopping authority from landing on stale
bindings.
The Claude-specific transcript-path glob moved into internal/claudeteam's
existing TranscriptProbe seam (mirroring TeamStateProbe) to satisfy the
internal/status host-neutrality invariant. Along the way, fixed a latent
hermeticity gap in three existing test files (merge_test.go,
gate_ceremony_count_test.go, recorded_gate_lifecycle_test.go) that forwarded
the live process's CLAUDE_CODE_SESSION_ID into fixtures with no boot receipt —
harmless before this guard existed, but a spurious failure source once it did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Validation cycle 2 found one material bug: bootGuardVerdict refused on EVERY
os.ReadFile error, so an unwritable .spacedock/boot (permission denied, a
read-only mount, the receipt path existing as a directory) read as "never
booted" and refused gate record/gate consume/merge guard permanently, while
status --boot exited 0 and silently swallowed the failed write. The guard's
own promised remedy ("one cheap idempotent boot") could never clear it.
Fix: refuse only on errors.Is(err, fs.ErrNotExist); every other read error
fails open with a stderr warning, matching the direction the design already
uses for an unreadable transcript. status --boot now surfaces a failed
receipt write (create or write) as a stderr warning instead of swallowing it,
so a stuck operator can see why re-running boot never helps. Both refusal
messages now name the resolved receipt path.
Also folded in the two one-token deferred risks flagged alongside the main
finding: booted_at is now written and compared at RFC3339Nano precision (a
compaction completing in the same wall-clock second as boot no longer
truncates backwards past it), and the receipt line is split with SplitN
instead of Fields (a transcript path containing a space no longer silently
disables detection). Each has a regression test proven to fail against the
pre-fix code and pass against the fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cycle 3 of force-boot-at-compaction-boundary: addresses the captain's
validation rejection on receipt LOCATION only. The receipt moves from
{gitRoot}/.spacedock/boot/{session_id} to host scratch at
/tmp/spacedock-boot/{session_id}-{repo identity token}, mirroring the
existing internal/dispatch dispatchFileDir convention. This deletes the
.gitignore entry, fixes the read-only-project-root write failure, and
eliminates the cwd/workflow git-root divergence deferred risk by keying
the receipt on the repo's shared git-common-dir rather than a
per-worktree git root.
Also completes a half-finished piece of the inherited work: the
internal/cli wiring tests still wrote fresh receipts at the old in-repo
path, which made all three guarded-verb wiring tests fail after the
relocation. Exported status.BootReceiptPath so the wiring test can
locate the real production path without duplicating the repo-identity
hashing scheme.
|
Closing unmerged. The captain rejected the mechanism, not the diagnosis. The receipt-plus-guard design was chosen after ideation rejected a host hook on three grounds. Two do not hold: "produced nothing at the incident boundary" is circular, because no hook was installed at the incident; and "PreCompact false-positives on refused compactions" is true of That leaves "advisory-only" as the sole real objection, against the standing evidence that the FO's deferred-load contract is entirely advisory and fires reliably at its triggers. Re-doing this as a |
A compacted First Officer session keeps its narrative and loses its bindings. It does not know it is a new session, so it never re-reads durable state. Six failures in one session traced to that, including a bypassed merge mod and four gates reported as presented that were never prepared.
What changed
status --boot.gate record,gate consume, andmerge guardwhen a compaction postdates the receipt.compact_boundaryrecord only.Evidence
go test ./...and-race: green, one pre-existing environment-local failure reproduced identically onmain.The mechanism was chosen after capture disproved two alternatives. A
PreCompacthook fires on compactions the host then refuses. Session identity survives compaction, so an identity check can never fire.Three deferred risks are recorded in the entity with promote conditions, including two one-line hardenings (
O_NOFOLLOW,0o700) recommended without blocking.4c
🤖 Generated with Claude Code