Per-invocation AC is the primitive for single governed ops.
Session mode is for scripts, pipelines, and interactive shells.
Per-invocation (unchanged):
gsh --exec "cmd" → 1 AC + 1 CR per command
Session mode (new):
eval "$(gsh session-start --scope shell:session)"
gsh --exec "cmd1" # reuses session AC
gsh --exec "cmd2"
eval "$(gsh session-end)"
Detection: GSAP_SESSION_AC in environment.
Subcommands: session-start, session-end, session-status
Known gap: broker currently marks AC consumed after first CR.
Session commands 2+ get 404 on CR. This is a broker-side fix
(needs session AC type). gsh handles it gracefully.
Tested against live fastapi-gsap Spoke on Hetzner.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
~200 lines of Rust. Every command: AC → exec → CR → CID.
Usage:
gsh --exec "echo hello"
gsh --exec "hcloud server list" --json
gsh --exec "ansible-playbook site.yml" --dry-run
Flow:
1. SHA-256 hash the command
2. POST /governance/authorize/ → AC ID
3. exec(sh, -c, command) → capture stdout/stderr/exit
4. POST /governance/complete/ → receipt + Chronicle CID
5. Print stdout (passthrough) or JSON (structured)
6. Exit with command's exit code
Environment:
GSAP_BROKER_URL http://fastapi-gsap:8000
GSAP_AGENT_DID did:web:bxnet.../agent/platform-ops
GSAP_TOKEN Bearer token (optional)
GSAP_CORPUS_CID sha256:{image_digest} (optional)
Tested against live fastapi-gsap Spoke broker on Hetzner:
dry-run: AC only ✓
live exec: stdout passthrough + CID ✓
JSON mode: ac_id + cr_id + chronicle_cid ✓
exit code: 42 passed through ✓
The command_hash in the AC request means the broker knows
WHAT will be executed before authorizing. Not just "was
this agent allowed" but "was this exact command authorized."
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>