# S1 Handoff Gate `make s1-handoff` is the operator-facing gate from host substrate (S1) to the cluster layer (S2). It validates inventory and baseline contracts, requires a clean checkout, runs the applicable Goss profile for every selected host, and emits a metadata-only JSON receipt under `reports/`. A passing receipt pins: - source revision and inventory digest - every selected host and its baseline profile - observed time and 24-hour default freshness boundary - per-host exit status - SHA-256 digests of the resulting TAP evidence Any failed host fails the aggregate. A passing receipt without host evidence is invalid. `make s1-handoff-dry-run` validates local inputs but records `status: not-run`; it cannot authorize S2 handoff. The current profiles are: - `Railiance01`: `ufw-managed` - `CoulombCore`: `external-firewall`, which verifies the declared iptables INPUT default-drop replacement control The gate is read-only on managed hosts. Before connecting, `scripts/handoff_contract.py` proves that its playbook contains only remote `stat`, `assert`, and the fixed Goss validation command, and pins the Goss assertion commands themselves to a reviewed read-only set. It renders each selected profile locally and fails if the installed `/etc/goss/baseline.yaml` digest differs, instead of refreshing it implicitly. TAP and the aggregate receipt are written only beneath the controller's ignored `reports/` path. If a surface is stale, the operator must review it and separately approve `make verify-refresh-host`; that change cannot be smuggled through a handoff run. The handoff command does not run Terraform, change provider resources, rotate credentials, converge a role, write remote files, or change services. Receipts validate with: ```bash python3 scripts/s1_receipt.py reports/s1-handoff-*.json ``` S2 should accept only `status: pass` receipts whose source and inventory match the intended handoff and whose `fresh_until` has not elapsed.