Command artifacts
Signed control-plane messages a supervisor sends to a running ship. Ship in v0.9.0 as primitives; consumed by Witness today, exposed via CLI in later releases.
A Session Receipt records what the agent did. A command artifact records what someone with authority told the agent to do — kill the session, approve a pending action, tighten the mandate, adjust the budget, ask for a graceful shutdown.
Both ride the same DSSE envelope. Both are signed Ed25519 artifacts. Both end up in the same audit trail. The difference is only direction: receipts flow up from the ship, commands flow down to it.
v0.9.0 ships the schemas and a single library helper. The CLI surfaces that issue and consume commands ship in later releases (the approval loop in v0.10.0, kill / terminate in v1.0). Witness consumes them today via the library.
The five command types
| Type | payloadType | Use |
|---|---|---|
KillCommand | application/vnd.treeship.command.kill.v1+json | Issuer demands an immediate halt. No drain, no grace period. Receipt cuts at the kill point. |
ApprovalDecision | application/vnd.treeship.command.approval_decision.v1+json | Approver's signed approve / reject on a pending approval-required artifact. |
MandateUpdate | application/vnd.treeship.command.mandate_update.v1+json | Replace the ship's bounded_actions and forbidden lists, optionally with an expiry. |
BudgetUpdate | application/vnd.treeship.command.budget_update.v1+json | Add or revoke tokens against the ship's running budget (signed delta). |
TerminateSession | application/vnd.treeship.command.terminate_session.v1+json | Graceful shutdown. The ship drains in-flight work and emits a final receipt. |
KillCommand and TerminateSession are deliberately separate. Kill is kill -9; Terminate is SIGTERM. The distinction matters when reading an audit trail.
Verifying a command artifact
use treeship_core::artifacts::{verify_command, CommandType};
let result = verify_command(&envelope, &authorized_keys)?;
match result.command {
CommandType::Kill(k) => { /* halt now, log k.reason */ }
CommandType::ApprovalDecision(d) => { /* unblock the wrap, log d.reason */ }
CommandType::MandateUpdate(m) => { /* replace mandate */ }
CommandType::BudgetUpdate(b) => { /* apply b.token_limit_delta */ }
CommandType::TerminateSession(t) => { /* drain, then close */ }
}authorized_keys is the (key_id -> Ed25519 public key) map of issuers permitted to send commands to this ship. Any signature on the envelope from a key outside this set is ignored; if no in-set key produced a valid signature, verification fails. This mirrors verify_any semantics on regular Treeship envelopes — same wire format, same crypto, same registry pattern.
The result also carries:
artifact_id— the content-addressed ID re-derived during verification, suitable for chaining and dedupverified_key_ids— the subset ofauthorized_keysthat actually signed (usually one)command.kind()— a stable&'static strlabel for logs ("kill", "approval_decision", etc.)
Why DSSE again
Command artifacts deliberately reuse the DSSE envelope so:
- They sit inside the existing audit trail (Hub, preview.html, Merkle tree) with zero special casing.
- The same Verifier that checks an
actionchecks akill_command. - The same content-addressing rule produces stable artifact IDs.
- The same key-rotation story works (a ship updates its
authorized_keysregistry the same way it rotates any other trust root).
A separate transport for control-plane messages would have meant a second crypto path, a second audit trail, and a second class of regressions. DSSE keeps them on the main road.
What's not in v0.9.0
Schemas only. v0.9.0 does not ship:
treeship approver add / list / remove— those are v0.10.0 (approval loop release).--require-approvalflag ontreeship wrap— v0.10.0.- Hub endpoints that accept and serve approval-required artifacts — v0.10.0.
- A CLI for emitting a
KillCommandorTerminateSession— v1.0; the Hub-level kill switch is Witness territory until then.
The schemas are stable in v0.9.0 because Witness needs to start consuming them now without waiting for the CLI surface to land. If the schema needs to evolve before v1.0, it gets bumped via the schema_version field on the parent artifact and the verifier branches accordingly. See schema versioning.
If you're building a supervisor (Witness or otherwise) that needs to issue commands to a Treeship ship today, depend on treeship_core::artifacts and produce DSSE envelopes signed with a key the ship has already added to its authorized issuer registry. The CLI catches up in v0.10.0; the wire format does not change.