Saphan StudioDocs
Reference/Command reference

saphan gate

Record a HUMAN gate decision for a stream (the only fleet_gate writer, §3.2)

saphan gate

Record a HUMAN gate decision for a stream (the only fleet_gate writer, §3.2)

saphan gate <stream> [flags]

Record one human gate decision for a stream, appended to the fleet_gate audit log (§1.1). The engine transports the decision, it never makes one: there is no default decision, no engine actor, and no "merged" decision — merged is only ever OBSERVED from a real merge commit (§1.7). A decision that would skip a gate in the §3.1 order is refused with a STOP list and exit 2; nothing is written on refusal.

The §3.1 order binds BACKWARD too: a gate sitting EARLIER in that order than one this stream has already resolved is refused by the named class gate-out-of-order, because recording it asserts an event that never happened — a true act under a wrong label. A deliberate late correction is still possible, and it must say so in the record: pass --out-of-order "<why>". The reason is the flag's own VALUE, so a flag with no ground is not representable; the ground is folded into the recorded row's detail, inside the signed bytes.

saphan dry-run gate runs the SAME validation the real write runs (CheckGate, the identical seam RecordGate calls — never fleet next's state-only projection, which cannot see the act's arguments) and writes NOTHING TO fleet_gate: no row, no signature. It is NOT a read-only invocation of the engine as a whole — like every other verb, it reconciles the channel first (§2.4), which DOES ingest fresh channel activity into fleet_event/ fleet_session exactly as the real write's own preamble would (review F-3: an earlier version of this text claimed "nothing written" outright, which a first invocation on a fresh workspace measurably contradicts). An inadmissible act refuses with the IDENTICAL reason text a real write would print; an admissible act prints the row that would be inserted and the stream's next state, then exits 0.

The teardown gate (adr-0018) is NOT recorded here. It records that a human may now remove the stream's worktree — the engine NEVER removes anything — and that decision belongs to saphan teardown, which writes the IDENTICAL signed row on top of the archive commandment 01 requires first: the channel family, the return manifest, .provision/ and the provisioned bytes, plus the provisioned-sha-must-be-resolvable check over the run record's citations. This verb archives none of that, so --gate teardown refuses here and prints the equivalent saphan teardown command with the same --actor, --note and --return arguments already folded in. It is one decision with one road, not a capability withdrawn: everything the gate verb enforced for it — merged projection, git's untracked-guard over non-ignored strays, a --return disposition for every "- artifacts:" manifest entry, the named refusal of a status still using the pre-fix "- return:" key — is the same routing.RecordGate check running inside that verb.

The review gate (order verdict-in-record-v1) records the reviewer's judgment — accept/conditions/reject — as a review-class record fact: JUDGMENT, never the owner's own decision (§1.10 — a recommendation, not a gate the reviewer advances themself) and never mechanical follow-through either. It never advances the stream lifecycle by itself; saphan merge consults the INGESTED _REVIEW_STATUS verdict only for its PRESENCE: a non-empty verdict means the review lane is engaged. Once engaged, merge refuses unless the latest recorded fleet_gate review decision is accept OR conditions (conditions is not a refusal, it is more work owed, and still counts as the human's gate); reject, or the absence of any review-gate row at all, refuses — the raw token's value never substitutes for this recorded row.

Subcommands

SubcommandWhat it does
saphan gate batchRecord many explicit owner gate decisions after one fleet reconcile

Options

OptionTypeDefaultDescription
--actorstringunsetthe human deciding (falls back to env SAPHAN_ACTOR, then ~/.saphan/config's identity.actor; the engine is never an actor)
--decisionstringunsetthe decision: accept | conditions | reject | ready-to-merge | closed-without-merge (stop1/stop2 only — closes the leg WITHOUT a merge, §1.1) (required, no default)
--gatestringunsetwhich gate: stop1 | stop2 | merge | promotion | review (required, no default; the teardown gate is recorded by 'saphan teardown', which writes the same row on top of the archive it requires first)
--notestringunsetfree-form audit detail (names and references only, §1.6)
--out-of-orderstringunsetdeliberately record a gate that sits EARLIER in the §3.1 order than one this stream already resolved: the flag's VALUE is the required ground, folded into the row's detail (a blank value is no flag at all — the act is then refused as gate-out-of-order)
--refstringunsetrecommendation reference: path/id of the review return the decision gates
--returnstringArray[]retained for the refusal's own remedy: this flag belongs to 'saphan teardown', and a --gate teardown act named here is refused with these dispositions folded into the printed command

saphan gate batch

Record many explicit owner gate decisions after one fleet reconcile

saphan gate batch <decisions.json> [flags]

Record one owner gesture as N separate, signed fleet_gate rows after ONE channel reconcile. The input is one JSON object with a non-empty "decisions" array. Every item must explicitly carry stream, gate, decision, and a non-empty note; recommendation_ref and out_of_order are per-stream optional fields. Only owner-class gates are admitted by this owner surface.

The batch is intentionally not transactional. Every item is attempted in file order. Integrity refusals are named per stream and do not hide successful rows: the final outcome is "partial" when some rows landed and some refused, "refused" when none landed, and "complete" only when every item succeeded. An unexpected engine/store error names the current item and every remaining item as not-attempted. Re-running is safe because already-recorded decisions are reported explicitly.

saphan dry-run gate batch uses routing.CheckGate for EVERY item, previews each admissible row, reports each refusal, and writes no fleet_gate rows. It still performs the one shared reconcile, exactly like the real batch and the singular gate verb.

Options

OptionTypeDefaultDescription
--actorstringunsetthe human deciding (falls back to env SAPHAN_ACTOR, then identity.actor)

Examples

Everything below this line is written by hand and is not regenerated.

Accepting a return

saphan gate add-rate-limits --gate stop2 --decision accept --workspace /srv/saphan

Recording a reviewer's verdict, with the artifact it points at

saphan gate add-rate-limits --gate review --decision conditions \
  --ref /srv/saphan/.saphan/state/review-artifacts/add-rate-limits/<digest>/_REVIEW_STATUS.md \
  --workspace /srv/saphan

--ref is the durable path the delivery minted, not a file name you compose. It exists only after the verdict has been brought home, which is why the review gate cannot be recorded before that step.

Closing a stream that will never merge

saphan gate probe-the-new-lane --gate stop2 --decision closed-without-merge --workspace /srv/saphan

Not every stream ends in a merge, and a stream that ends without one still needs a closing decision on the record — that is the normal shape for a review or a probe, not an error.

Rehearse before you record. Prefixing the same line with dry-run performs every read the real act performs and writes nothing, so you see the refusal — or the state change it would make — before the record moves.

On this page