Enforce Architectural Guard in MicroVM sessions
Epic 5896 (Architectural Guard), finish-phase gap. PRD R3.3 says the guard covers every session Jaah launches, runners AND console sessions, on every in-scope project. Sessions on a MicroVM node get no guard at all today.
## What I observed (read-only, develop @ 4edf65443)
- Static runners: `apps/api/app/lease_drain.py:1235` puts `guard=guard_runtime.lease_stanza(session, project_id)` on the `lease_slot` intent. Console: `apps/api/app/routes/console.py:385-390` and `apps/api/app/console_direct.py:710`. The node turns that stanza into a bundle file plus env (`apps/jaahd/guard_bundle.py::prepare_launch`, called from `fleet_dispatch.py`, `console.py` and `clauded_wt.py`). Hooks only enforce when `JAAH_GUARD_BUNDLE` is set.
- MicroVM: `apps/api/app/microvm_placement.py:267` `admit()` composes the prompt and mints `provision` and `launch` (`_model_launch`). It never calls `guard_runtime.lease_stanza`. Nothing under `apps/jaahd/microvm_*.py` (`microvm_session.py`, `microvm_console.py`, `microvmd.py`) mentions `guard_bundle` or `JAAH_GUARD_*`. Task 5931's report confirms it: "MicroVM runners do not receive the stanza yet".
- Effect: in an org with Compliance and Advanced Compliance both on, a task or console session placed on a MicroVM node passes no per-write check, no shell-commit check and no pre-push guard step. The rules sit in an Antigravity prompt at most. That breaks the epic's "absolute, unyielding" ask without any error.
## Fix (reuse what exists; add no new surface)
1. CP: put the same `guard` stanza (`guard_runtime.lease_stanza`) on the MicroVM model-runner launch and on the MicroVM console launch. When the guard is unavailable, the payload must stay byte-identical.
2. Node and guest: when the stanza is present, run the same `guard_bundle` fetch and refresh for the VM session, so the guest sees `JAAH_GUARD_BUNDLE`, `JAAH_GUARD_RUNTIME`, `JAAH_GUARD_BASE` and `JAAH_GUARD_STATE`. Wire the same `fleet_launch` lifecycle chain entries with `guard=True`. Prefer the mechanism project-memory bundles already use to reach a VM, if one exists. Decisions use the existing `guard.record` call or the spool.
3. Keep G-1: a miss is fail-open and recorded as `error`.
## Out of scope
- Do not change static-pool or console behaviour, the guard engine or rule kinds, the waiver or escalation flow, or the MicroVM egress, sizing and retention rules.
- No new UI. No merge-time block.
## Done means
- Unit test: a MicroVM admit for a guard-available project carries `guard: {project_id}`. An unavailable org's admit payload is unchanged (byte-identical golden).
- Unit/contract test: the VM session's env carries `JAAH_GUARD_BUNDLE` only when the stanza is present.
- Dev oracle: in org-b on a MicroVM node, a Claude coder task told to write `import requests` into a `*.py` file under the "Use httpx" gate rule (or an equivalent gate rule) is denied. A Guard activity row appears with the task number, and the httpx retry lands.
- `./dc check full` green; Jaah Deploy to dev succeeds.
Outcome
MicroVM runner and console sessions now get the Architectural Guard (CP stanza plus a VM bundle, env and hooks, hardened against guest planting). PR #5226 merged, dev deploy OK. Not verified: the live dev oracle (no org has both the guard and a MicroVM node). Deferred: per-write hooks for no-number console tabs.
Artifacts
Activity
- Ready
- Runner started
- Runner ended (done)