Saphan StudioDocs
Reference/Command reference

saphan teardown

Archive a stream's channel files, then record the teardown gate accept (one verb, refusal-first)

saphan teardown

Archive a stream's channel files, then record the teardown gate accept (one verb, refusal-first)

saphan teardown <stream> [flags]

Archive a stream's gitignored channel family (_MASTER_ORDER.md, _EXEC_STATUS.md, _REVIEW_STATUS.md, any other _* file at the worktree root) to .saphan/work/<stream>/channel-archive/, verified byte-exact by a generated .teardown-SHA256SUMS manifest, then record the teardown gate decision — the existing §3.2 preconditions (projection merged, git's own untracked-guard, every return dispositioned) run unchanged. This is the ONE road to that decision: "saphan gate --gate teardown" records nothing and refuses, printing this command with your own arguments folded in, because the row belongs on top of the archive below.

Archiving refuses first, named and ledgered, if the archive cannot be written or a re-verified hash does not match — nothing is recorded on that refusal. The DECISION stays a human act — invoking this verb IS that act — but the MECHANICS no longer do: "saphan teardown <stream> --verify-archive" now removes the worktree itself once the gate is in the log and every archived byte re-verifies, and prints a re-stat of the path rather than a command to run. That one guarded line is what prints on success. A far (transport=ssh) worktree is removed over the SAME transport this verb already dials: its residue — the far channel beyond what "saphan return" fetched, and its .provision/ — is brought home in a digest-verified pack and re-hashed into the archive FIRST, then the removal sequence runs measured step by step, then a second status-probe records what is true afterwards. A step that fails is a named refusal saying which one.

saphan dry-run teardown runs the SAME gate validation the real write runs (routing.CheckGate, the identical seam RecordGate calls) and writes NOTHING: no channel-archive directory, no file, no fleet_gate row, no signature, nothing ledgered. An inadmissible act refuses with the same violation reasons a real write would print; an admissible act prints the channel files that WOULD be archived (names and SHA256 sums), the gate row that WOULD be recorded, and the guarded removal line that would then be legal — then exits 0. Like every other verb, it still reconciles the channel first (§2.4), which DOES ingest fresh channel activity into fleet_event/fleet_session, the same way the real write's own preamble would.

Options

OptionTypeDefaultDescription
--actorstringunsetthe human tearing down (falls back to env SAPHAN_ACTOR, then ~/.saphan/config's identity.actor; the engine is never an actor)
--notestringunsetfree-form audit detail (names and references only, §1.6)
--provisioned-fromstringArray[]repeatable: <cited-dest>=<absolute path to a known-good copy> — the corroborated way to SATISFY provisioned-sha-must-be-resolvable when the bytes exist but neither this teardown's archive nor the repo's history can reach them (a worktree that died before its teardown). The supplied bytes are hashed and must PREFIX-MATCH the sha the run record already cites for that dest; anything else refuses by sha. Never a bypass: the invariant is unchanged, this is one more place it may look — and no longer the only one you can reach by hand: the run record's own remembered source (migration 48) is consulted automatically, so an ordinary teardown of a provisioned stream needs no flag from you at all
--returnstringArray[]repeatable: <manifest-file>=<landed[:<path outside the worktree>]|consumed|discarded-by-decision> — a bare landed is recorded as an explicit unverified claim (§2.1); landed:<path> is corroborated (hash-matched against the worktree's own copy) before it is trusted, and an unverifiable one refuses. When the worktree is already GONE or is REMOTE the hash-comparison has no reachable second half, so the claim degrades honestly instead of refusing: the named local path is read (it must exist) and recorded as (uncorroborated: worktree absent) or (uncorroborated: worktree remote) — a wrong path still refuses
--verify-archiveboolfalseTHE REMOVAL, guarded: refuse unless the teardown gate is recorded and every archived channel byte still matches its teardown manifest — then restore the owner-write bit on the Go module cache the spawn planted inside the workdir (mode 555), remove the worktree the record names for this stream, and re-stat the path to say what is actually there. A removal that does not complete is a named refusal with the by-hand sequence beside it, never an exit 0. A far (transport=ssh) stream is removed over the transport this verb already dials, in three measured acts: its residue is archived home in a digest-verified pack BEFORE anything is destroyed, the removal sequence runs with every step's exit code named, and a second status-probe measures the result — a failure names the step, and refs/incoming/<stream>/* is proven gone in both far repositories

On this page