carlos / platform Public

docs: cohort-1 runbook: fourth session: 7.1 PASS, publish-floor refused by signer IAM (status/consoles), closed

console-report published, then publish-floor's census could not read
status/consoles/dev-console.json with the signer role. Nothing written:
no floor, home or key binding. Session torn down, CloudTrail clean.
Also adds the guard directory check after every guard-arm (6.2, 6.3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev c15ce69db83bd322ceec1ebfbae345aded0ad19f parent 2a58af0
2 files changed, +53 −1
  • docs/superpowers/notes/claim-trial-inputs/signing-host.md +1 −0
  • docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md +52 −1

CI passed — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.012s
?   	amadan.net/carlos/platform.git/launchctx/controlname	[no test files]
?   	amadan.net/carlos/platform.git/launchctx/wireid	[no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m
ok  	github.com/carlosframework/platform	302.686s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	3.005s
ok  	github.com/carlosframework/platform/cmd/moveprobe	93.165s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.703s
ok  	github.com/carlosframework/platform/internal/activator	252.100s
ok  	github.com/carlosframework/platform/internal/agentsock	0.017s
ok  	github.com/carlosframework/platform/internal/alerting	0.003s
ok  	github.com/carlosframework/platform/internal/analytics	0.021s
ok  	github.com/carlosframework/platform/internal/appconfig	2.454s
ok  	github.com/carlosframework/platform/internal/apperr	0.071s
ok  	github.com/carlosframework/platform/internal/applog	0.133s
ok  	github.com/carlosframework/platform/internal/awsinv	0.005s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.004s
ok  	github.com/carlosframework/platform/internal/boxid	0.727s
?   	github.com/carlosframework/platform/internal/boxid/boxidtest	[no test files]
?   	github.com/carlosframework/platform/internal/boxid/internal/signtest	[no test files]
ok  	github.com/carlosframework/platform/internal/boxvolumes	0.008s
ok  	github.com/carlosframework/platform/internal/caps	0.013s
ok  	github.com/carlosframework/platform/internal/certwarm	0.002s
ok  	github.com/carlosframework/platform/internal/cgroup	0.691s
ok  	github.com/carlosframework/platform/internal/changelog	0.304s
ok  	github.com/carlosframework/platform/internal/claimactivation	0.023s
ok  	github.com/carlosframework/platform/internal/clamd	0.054s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
ok  	github.com/carlosframework/platform/internal/console	403.074s
?   	github.com/carlosframework/platform/internal/controlname	[no test files]
?   	github.com/carlosframework/platform/internal/controlsock	[no test files]
ok  	github.com/carlosframework/platform/internal/dbdoor	5.847s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.008s
ok  	github.com/carlosframework/platform/internal/dbverify	0.004s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.004s
ok  	github.com/carlosframework/platform/internal/docsite	0.005s
ok  	github.com/carlosframework/platform/internal/domains	2.386s
ok  	github.com/carlosframework/platform/internal/doorkey	0.006s
ok  	github.com/carlosframework/platform/internal/economics	0.004s
ok  	github.com/carlosframework/platform/internal/edge	44.891s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.008s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.131s
ok  	github.com/carlosframework/platform/internal/gate	1.654s
ok  	github.com/carlosframework/platform/internal/genselect	0.014s
ok  	github.com/carlosframework/platform/internal/geo	0.016s
ok  	github.com/carlosframework/platform/internal/geoip	0.032s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.152s
ok  	github.com/carlosframework/platform/internal/host	174.838s
ok  	github.com/carlosframework/platform/internal/hostactors	0.565s
ok  	github.com/carlosframework/platform/internal/hostbox	2.621s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.562s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.013s
ok  	github.com/carlosframework/platform/internal/hostexec	4.717s
ok  	github.com/carlosframework/platform/internal/hostfence	0.029s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.420s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.014s
ok  	github.com/carlosframework/platform/internal/hostguard	1.039s
ok  	github.com/carlosframework/platform/internal/hostops	1.619s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.012s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.013s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.448s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.148s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.013s
ok  	github.com/carlosframework/platform/internal/infraview	0.012s
ok  	github.com/carlosframework/platform/internal/instancectl	5.222s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.723s
ok  	github.com/carlosframework/platform/internal/instancefs	0.017s
ok  	github.com/carlosframework/platform/internal/instancekey	0.014s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.007s
ok  	github.com/carlosframework/platform/internal/layout	1.533s
ok  	github.com/carlosframework/platform/internal/lbhealth	0.002s
ok  	github.com/carlosframework/platform/internal/lease	9.809s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.005s
ok  	github.com/carlosframework/platform/internal/ledger	0.012s
ok  	github.com/carlosframework/platform/internal/mail	0.165s
ok  	github.com/carlosframework/platform/internal/maintenance	0.082s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.476s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.075s
ok  	github.com/carlosframework/platform/internal/metrics	0.004s
ok  	github.com/carlosframework/platform/internal/moves	20.937s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.796s
ok  	github.com/carlosframework/platform/internal/objstore	0.196s
ok  	github.com/carlosframework/platform/internal/ops	0.181s
ok  	github.com/carlosframework/platform/internal/park	0.201s
ok  	github.com/carlosframework/platform/internal/privatefile	0.004s
ok  	github.com/carlosframework/platform/internal/provision	17.663s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.006s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	5.391s
ok  	github.com/carlosframework/platform/internal/relay	7.218s
ok  	github.com/carlosframework/platform/internal/release	21.461s
ok  	github.com/carlosframework/platform/internal/rollout	3.620s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.003s
ok  	github.com/carlosframework/platform/internal/rootaws	0.007s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.057s
ok  	github.com/carlosframework/platform/internal/rootstate	0.026s
ok  	github.com/carlosframework/platform/internal/runs	0.577s
ok  	github.com/carlosframework/platform/internal/s3compat	0.005s
ok  	github.com/carlosframework/platform/internal/safefile	0.004s
ok  	github.com/carlosframework/platform/internal/schedule	2.480s
ok  	github.com/carlosframework/platform/internal/seeds	0.014s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	8.393s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	4.256s
ok  	github.com/carlosframework/platform/internal/status	0.037s
ok  	github.com/carlosframework/platform/internal/steering	0.005s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.243s
ok  	github.com/carlosframework/platform/internal/svcrole	0.002s
ok  	github.com/carlosframework/platform/internal/telemetry	0.004s
ok  	github.com/carlosframework/platform/internal/termfence	0.004s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.003s
ok  	github.com/carlosframework/platform/internal/unixdial	0.002s
ok  	github.com/carlosframework/platform/internal/updatewords	0.004s
ok  	github.com/carlosframework/platform/internal/usage	0.004s
ok  	github.com/carlosframework/platform/internal/version	0.002s
ok  	github.com/carlosframework/platform/internal/vet	1.537s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
linux/arm64 carlos binary: 48824482 bytes (ceiling 52000000)
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
  minio: cached, digest verified
  mc: cached, digest verified
  minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
  buckets and scoped user ready
=== RUN   TestDeliveredContractMinio
=== RUN   TestDeliveredContractMinio/put
=== RUN   TestDeliveredContractMinio/get_round-trips_the_body
=== RUN   TestDeliveredContractMinio/list_shows_the_key
=== RUN   TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN   TestDeliveredContractMinio/delete_removes_it
=== RUN   TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
    --- PASS: TestDeliveredContractMinio/put (0.00s)
    --- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
    --- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
    --- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
    --- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
    --- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok  	github.com/carlosframework/platform/internal/objstore	0.022s
=== RUN   TestT4StorageProbeMinio
2026/10/19 18:18:24 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18E001239444D6D8, HostID: 7c420053e0fbcd7f41653fdd92ffa2802c903d05303b1aa15adc2e8bb65d183f, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
--- PASS: TestT4StorageProbeMinio (0.00s)
PASS
ok  	github.com/carlosframework/platform/internal/host	0.020s
minio contract: PASS
diff --git a/docs/superpowers/notes/claim-trial-inputs/signing-host.md b/docs/superpowers/notes/claim-trial-inputs/signing-host.md
index c8ec211b7..e263e838a 100644
--- a/docs/superpowers/notes/claim-trial-inputs/signing-host.md
+++ b/docs/superpowers/notes/claim-trial-inputs/signing-host.md
@@ -79,6 +79,7 @@ Why each statement:
- **ListForNotFound:** without `s3:ListBucket`, S3 answers a missing key with 403 instead of 404, and the runner's first `initialize` reads a roster that doesn't exist yet.
- **WriteRosterAndFloor:** these are exactly the runner's write allowlist (`internal/claimactivation/runner.go:89-104`).
- **ReadControl:** covers the reads for invitations, box records and floor evidence. Confirm it with a `--dry-run` of every phase from the signer, and widen only on a refusal.
+ **Refused, 2026-10-19 (fourth session, runbook step 7.2):** `publish-floor` needs `s3:GetObject` on `status/consoles/dev-console.json`. Its census reads the console's own report (`ExactEvidence.ConsoleReport`, `internal/hostactors/census.go:349` at `f3bf1214`), and this policy does not allow that read. The policy above is unchanged. A wider one (that key in `ReadControl`, and `status/consoles/*` in `ListForNotFound`) needs Paul's approval before the next session.
- **ReadConsoleRelease and WriteIdentity** (added 2026-10-18 for step 6.1; on main 7c1beb2d, published (v0.28.2, tag on 7c1beb2d)): `publish-identity` reads the console's `canary/console` pointer, manifest and artefact under `apps/bab/console/`, and writes one object, the identity at `control/system/router/releases/v0.28.4.json` (second session; the first session's was `v0.28.2.json`, now published and permanent; no `v0.28.3.json` was ever published), create-only. `WriteIdentity` names that exact key, not a prefix: no wildcard, so the role can write no other release's identity, the v0.28.2 and v0.28.3 ones included. `sign-policy` writes nothing. The runner admits only that one identity key for the phase, whatever IAM allows. platform-1e accepted this scope as proposed on 2026-10-18; Paul approves it together with the signer host.
**The release-key deny doesn't block this role.** That deny is the inline policy `deny-release-key-reads` on the box roles only (`carlos-dev-host`, `carlos-dev-host-b`). Dev-uk's role is limited by its own eu-west-2 scope. There is no account-wide deny. A policy simulation of this `ReleaseKey` statement returns `allowed` (2026-10-18). Because any role granted `ssm:GetParameter` on that ARN can read the key, the role exists only for the trial and is deleted afterwards. CloudTrail is checked so that every `GetParameter` on `/carlos/release-key` comes from a `carlos-claim-signer` session.
diff --git a/docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md b/docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md
index 258276826..2a3ac022a 100644
--- a/docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md
+++ b/docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md
@@ -38,7 +38,7 @@ Final, 2026-10-19. Every value below was read or verified read-only on DOCS-T1 b
4. **Open the session** (`signing-host.md`, "Create"). platform-1e confirms with Paul first. This puts the release key on a host a second time. Stop: a checksum mismatch; `carlos version` not `carlos v0.28.4`; `claims help` not as quoted; a key file that is not root `600`. **DONE 2026-10-19 09:12Z to 09:14Z, PASS**: signer `i-006e3c79dfdf5d814`, SG `sg-0a0f98545441cf295`, run-id `cohort1-20261019T091304Z`, session start `2026-10-19T09:14:14Z` (record under "5.1 in the second session").
5. **5.1, resumed** ("5.1 in the second session"): the box re-check, then `refresh` only, never `initialize`. Stop: revision not 5, the listing not as recorded, or a staged file missing or changed. **DONE 2026-10-19 09:15Z, PASS**: revision 5, listing as recorded, every staged file unchanged.
6. **Step 6**: 6.1 `sign-policy` and `publish-identity` (new `--out` paths) and staging under new paths; 6.1a the `-h` check, then `install-channel --policy --identity` on leo, then sophie; 6.2 `guard-arm` on leo; 6.3 P9 (third run), `install-console`, `guard-arm --console`, `console-config`, the restart, and the wait for both boxes' `floor_guard_ready`. Stop: any refusal (6.1a lists its refusals, the `--identity` ones included); an identity value other than the expected ones above; any 6.1a output other than its expected lines; a P9 `SET` line or console-mail role policy; `console.service` not `active`. **2026-10-19: 6.1 and 6.1a PASS; 6.2 STOP; the session is closed** (record under "The second session's step 6"). `guard-arm` on leo refused with `hostfloor: authenticated floor required` because `/opt/carlos` is owned by `carlos:carlos` (host-init's `chown -R carlos:carlos /data /opt/carlos`), and `OpenProtected` needs every parent of `/opt/carlos/carlos` root-owned. Sophie has the same ownership. Held for a ruling on #250. **Third session, 2026-10-19 15:18Z to 15:25Z** (after the ownership fix): 6.2 PASS (leo armed, `floor_guard_ready` reported); 6.3 P9, `install-console`, `guard-arm --console` and `console-config` PASS; **STOP at the console restart**: `203/EXEC` `Permission denied` on `/opt/carlos/system/guard/carlos`, because the 6.3 block's `umask 077` made sophie's guard directory `700` and the console runs as `carlos`. The dev console is down (502) and held; sophie's edge fails the same way on its next restart. Session closed, CloudTrail clean (record under "The third session").
-7. **Step 7**: readers first (P3), then the dennis guard check (7.1), then **`console-report` on sophie, followed IMMEDIATELY by `publish-floor` on the signer** (the console's evidence lasts only until the agent's next status tick, about 5 minutes, #312), then home, leo's key, the `jq -e` check and the console runtime. Stop: a reader below v0.28.4; an armed policy on dennis; `publish-floor` refused after one fresh `console-report`; `jq -e` not `true`.
+7. **Step 7**: readers first (P3), then the dennis guard check (7.1), then **`console-report` on sophie, followed IMMEDIATELY by `publish-floor` on the signer** (the console's evidence lasts only until the agent's next status tick, about 5 minutes, #312), then home, leo's key, the `jq -e` check and the console runtime. Stop: a reader below v0.28.4; an armed policy on dennis; `publish-floor` refused after one fresh `console-report`; `jq -e` not `true`. **Fourth session, 2026-10-19 17:49Z to 17:55Z:** 7.1 PASS; `console-report` PASS; `publish-floor` refused by IAM (the signer role cannot read `status/consoles/dev-console.json`): STOP before any write. No floor, home or key binding. Session closed, CloudTrail clean (record under "The fourth session"). Resuming needs Paul's approval of the wider signer policy, then a fifth session.
8. **Close the session**, always allowed and done at any stop: shred the key on the signer; terminate the instance; remove the exchange objects under `claim-signer/<run-id>/` by exact key; delete the role, instance profile, inline and managed policy attachments and security group. Then the CloudTrail check from DOCS-T1 ("Closing the signing session"): every `GetParameter` on `/carlos/release-key` since the session's start comes from `assumed-role/carlos-claim-signer/<instance id>`, one per key fetch; no instance tagged `purpose=claim-signer` pending or running; `NoSuchEntity` for the role. Anything else goes to #250 before any further step.
**Open:** nothing. Every TO-CONFIRM is cleared.
@@ -1280,8 +1280,12 @@ set -a
set +a
/opt/carlos/carlos system guard-arm --policy /root/claim-trial/policy.v0.28.4.json \
--identity /root/claim-trial/identity.v0.28.4.json --enrollment /root/claim-trial/leo-climbing-enrollment.json
+stat -c '%a %U:%G' /opt/carlos/system/guard /opt/carlos/system/guard/identities
+sudo -u carlos test -x /opt/carlos/system/guard/carlos
```
+**The guard directory check, straight after every `guard-arm`** (here and in 6.3). `stat` must print `755 root:root` twice, and `sudo -u carlos test -x` must pass (under `set -e` a failure ends the block). Anything else is a STOP. Why: `guard-arm` creates `/opt/carlos/system/guard` and `guard/identities` with `os.MkdirAll(dir, 0755)`, which the umask reduces. The edge and the console run as `carlos` through `guard/carlos`, so a `700` guard directory takes them down at their next start (the third session's 6.3 stop). Keep `umask 077` in blocks that write secret files; this check is what catches its effect on the guard. (platform-1e is building the code fix, `guard-arm` setting the directory modes to `0755` explicitly, for the next release.)
+
### 6.3 Install the protected console, arm sophie and start the guarded console
The console path is fixed: `/opt/carlos/system/console/console`, with `console.prev` beside it, both created by `carlos system claims install-console` on sophie. Its `--source` is the console build 5.0a promoted, at `/opt/carlos/versions/f3bf1214/console`, the path 5.0a's `readlink` showed. Sophie's own `carlos` and `carlos.prev` must both be v0.28.4 (6.1a) before `guard-arm --console`.
@@ -1315,6 +1319,8 @@ CONSOLE_BINARY=/opt/carlos/system/console/console
/opt/carlos/carlos system guard-arm --policy /root/claim-trial/policy.v0.28.4.json \
--identity /root/claim-trial/identity.v0.28.4.json --enrollment /root/claim-trial/sophie-knitting-enrollment.json \
--console "$CONSOLE_BINARY"
+stat -c '%a %U:%G' /opt/carlos/system/guard /opt/carlos/system/guard/identities
+sudo -u carlos test -x /opt/carlos/system/guard/carlos
/opt/carlos/carlos system claims console-config --roster /root/claim-trial/roster.json --expect-revision "$REV" \
--bucket carlos-dev-640012953448 --console-id "$CID" --public-key "$PUB" --dry-run
/opt/carlos/carlos system claims console-config --roster /root/claim-trial/roster.json --expect-revision "$REV" \
@@ -1323,6 +1329,8 @@ systemctl restart console.service
systemctl is-active console.service
```
+The two lines after `guard-arm --console` are 6.2's guard directory check: `755 root:root` twice and the `test -x` passing, or STOP before `console-config`.
+
`console-report` is not run here. It opens step 7's report-and-floor pair, because its evidence lasts only until the agent's next status tick ("Hard ordering" under "The signing session").
Then wait for the agent to report the armed guard. `console-report` refuses until sophie's agent has reported it, and `publish-floor` needs leo's report too. The agent's first tick comes about a minute after the edge starts, then every 5 minutes plus jitter, so allow up to about 6 minutes after `guard-arm`. From DOCS-T1, read-only:
@@ -1390,6 +1398,49 @@ Closing the signing session, 15:23Z to 15:25Z, in full:
Left as they are ("Stop and hold"): leo armed (guard, `host-actor.json`, edge drop-in), reporting `floor_guard_ready`; sophie armed with `--console`, its protected console and `.prev`, `console-ready.json`, both `90-host-floor.conf` drop-ins, `claim-console.env` and `91-claim-runtime.conf`, and `console.service` in its restart loop; the guard directory modes; the roster at revision 5; no floor published (`control/moves-protocol.json` 404); the published v0.28.4 identity; every staged file. No host.env or flag changed beyond what `console-config` wrote. Getting the console back, and making sophie's guard directories reachable by the carlos user (and fixing the 6.3 block's `umask`), need a ruling on #250 and Paul's go. Step 7 then needs a fourth signing session; 6.2, `install-console` and `guard-arm --console` are done and one-way.
+### The fourth session: step 7 (v0.28.4, 2026-10-19)
+
+Run by the executor for the controller, on Paul's go given directly to the controller ("Go."): a fourth signing session for step 7 only, with the teardown and the CloudTrail check. Commands went from DOCS-T1 (profile `kass`), one SSM `send-command` per block, the signer's blocks sourcing `/root/claim-trial/helpers.sh`. No secret went into this record. Before it, sophie's guard directories had been put back to `755` by `chmod` and the console restarted (17:34:45Z); that fix is not part of this record.
+
+**Step 7.1 PASS; 7.2 `console-report` PASS, then `publish-floor` refused by IAM: STOP. The session is closed. No floor, home or key binding was written.**
+
+Pre-checks, read-only, 17:45Z to 17:47Z, all PASS:
+- Bucket: `control/host-actors.json` 1967 bytes, etag `f3717d8d67f496ffd9525704c7a690b0` (revision 5, 2026-10-18 10:21:54Z). `control/moves-protocol.json` and `moves-protocol-auth.json` 404. The identity `control/system/router/releases/v0.28.4.json` is present: 1076 bytes, etag `47c51f83…`, sha256 `995e3c85`, 09:17:58Z. `releases/` also holds `v0.28.2.json`. Nothing under `claim-signer/`. `stable.json` and `canary.json` name v0.28.4, `736826e4…4b21`, with no `release_tag` and no `moves_protocol`.
+- Reports: leo 17:42:53Z, sophie 17:40:58Z; both `v0.28.4`, `736826e4`, `floor_guard_ready` `True`, `floor_sha256` `1a6a7e50`, 0 console processes.
+- The guard directory check, read-only on leo and sophie: `stat -c '%a %U:%G' /opt/carlos/system/guard /opt/carlos/system/guard/identities` printed `755 root:root` twice on each box, and `sudo -u carlos test -x /opt/carlos/system/guard/carlos` passed on both (on sophie also for `/opt/carlos/system/console/console`). The carlos user can read `guard/policy.json`, `guard/identities/v0.28.4.json` and `host-actor.json`. The shell umask over SSM is `0022`.
+- Ownership: on both boxes `/opt`, `/opt/carlos`, `/opt/carlos/system` and `system/router` are `root:root 755`. Under `/opt/carlos` there are 0 entries not `root:root`, 0 group- or other-writable (symlinks aside), and 0 set-id. `host.env` is `root:root 600`. `carlos`, `carlos.prev` and `guard/carlos` are all `736826e4`, `carlos v0.28.4`.
+- Services: leo's edge and instance-control `active` since 07:51:53Z; sophie's since 07:50:46Z. Sophie's `console.service` `active` since 17:34:45Z, 0 restarts, `ExecStart` through `guard/carlos`, main process `/opt/carlos/system/console/console` running as `carlos`.
+- Serving, 17:46:27Z: the dev console's `/` and `/healthz` 200 with `x-carlos-version: f3bf1214`; wakecheck 200 (sophie); claimtrial 200 via `--resolve` to leo (`host=ip-172-31-19-180`). AccessDenied lines since 17:00Z: leo 0, sophie 2 (both the known Cost Explorer ones). Dennis: guard and `host-actor.json` absent, `carlos v0.28.4`. No `carlos-claim-signer` role, profile, security group or instance.
+- Staged files unchanged, root:root `600`, one link, in root `700` directories, with the sha256s recorded in the third session. Sophie's `/root/claim-trial` also holds the third session's `roster.json` (`6fc74d75`), `rev`, `inspect.json` (`c9143bdf`, `644`), `cc-dry.json` and `cc-apply.json`.
+
+Opening, 17:48Z to 17:51Z: PASS.
+- Tools from the public releases: `openssl pkeyutl -verify` with the README's pinned key printed `Signature Verified Successfully`, and `sha256sum -c` printed `OK` for `carlos-linux-arm64`, `carlos-linux-amd64` and (with jq's `sha256sum.txt`) `jq-linux-arm64`. `SHA256SUMS.txt` is `b2dcd93c…cd2f` (704 bytes, final newline) and names only `carlos_0.28.4_amd64.deb` and `carlos_0.28.4_arm64.deb`. It is byte-equal to the local `dist/` copy. `.sig` `480ecfb1…d812`, carlos arm64 `736826e4…4b21`, amd64 `2d53162f…8548` (`carlos v0.28.4`), jq `4dd2d8a0…b4a5`. `release.pub` came from leo's staged copy, checked by sha256 (`960d03ff`). `floor.json` `881acf53…37a9`. `helpers.sh` `208ee7d9`, the same bytes as the earlier sessions'.
+- Created: role `carlos-claim-signer` with `AmazonSSMManagedInstanceCore` and inline policy `claim-signer`, extracted verbatim from `signing-host.md` (the `WriteIdentity` check printed `ok`); instance profile `carlos-claim-signer`; security group `sg-06bf9d7e21f0e8956` (no inbound, egress TCP 443 only); instance `i-06dcff9bf6386e406` (launched 17:50:02Z on the first try, IMDSv2 required, hop limit 1, root volume `DeleteOnTermination` true, SSM `Online` 17:50:30Z). Exchange run-id `cohort1-20261019T175002Z`, holding `carlos-linux-arm64`, `jq-linux-arm64`, `SHA256SUMS.txt`, `SHA256SUMS.txt.sig`, `release.pub`, `floor.json` and `helpers.sh`.
+- On the signer: both `sha256sum -c` lines `OK`, `carlos v0.28.4`, `jq-1.7.1`, every input `OK` against its sha256 and root `600` in a root `700` `/root/claim-trial`, no swap, `/run` tmpfs. `claims help` hashed to `a909f5ef`, the same as the verified amd64 CLI's and the third session's.
+- Session start `2026-10-19T17:51:02Z`, recorded before the key fetch. Key at `/run/claim-signer/release.key`: `root 600 1 45`, tmpfs, in a root `700` directory.
+
+5.1, `refresh` only, 17:51Z: PASS. `REV=5`, etag `f3717d8d…`. The listing matches the earlier sessions (printed with enrollments redacted): region `ie` (namespace `platform`, pools `cohort1` and `console`); `leo-climbing` pool `cohort1`, `i-046c7fe43620944bc`, `active`; `sophie-knitting` pool `console`, `i-05498eada6f0afd23`, `active`; `dev-console` with compute `sophie-knitting`; `holds` and `activations` empty; `home_region` and `publication` unset; `request_limit_ns` 120000000000. `inspect.json` `c9143bdf` and `roster.json` `6fc74d75`, the same bytes as before.
+
+7.1, 17:51Z: PASS.
+- P3: `carlos system status --console https://platform.dev.carlosframework.com`, run with the verified v0.28.4 amd64 CLI: both channels at v0.28.4; `dennis-stenciling` and `leo-climbing` on `stable`, `sophie-knitting` on `canary`, all `v0.28.4 current`. The console header is `x-carlos-version: f3bf1214`. The box lines: leo `carlos v0.28.4` twice, sophie `carlos v0.28.4` twice, dennis `carlos v0.28.4` with `.prev` `carlos v0.28.3` (passes, per platform-1e's ruling). The `carlos` on DOCS-T1's `PATH` is v0.28.0. Nothing in the session used it.
+- Dennis, 17:51:57Z, the exact pass: `Success`, `absent /opt/carlos/system/guard/policy.json`, `absent /etc/carlos/host-actor.json`, `carlos v0.28.4`.
+
+7.2, from 17:52:29Z, when sophie's report moved:
+1. `box_inspect "$SOPHIE"`, 17:52:47Z to 17:52:59Z: `staged` twice, `rev` `5`.
+2. `console-report` on sophie, the block exactly as quoted: SSM `Success`. The dry run and the apply printed key `status/boxes/sophie-knitting.json`, `expected_roster_revision` 5, console `dev-console`, `binary_sha256` `8d49199f…5e9b`, `guard_floor_sha256` `1a6a7e50…4ab3`, and `all_launch_paths_guarded` `false`. Its output also carries an `enrollment` value; future runs should print it redacted.
+3. `publish-floor` on the signer, sent the moment `console-report` returned `Success` (nothing between them), 17:53:08Z: **refused, STOP.** SSM `Failed`, exit 1, in `phase`'s refresh or dry run, before any apply. The error was `AccessDenied` on `s3:GetObject` for `arn:aws:s3:::carlos-dev-640012953448/status/consoles/dev-console.json`, as `assumed-role/carlos-claim-signer/i-06dcff9bf6386e406`, "because no identity-based policy allows the s3:GetObject action". Afterwards (17:53:31Z): roster etag still `f3717d8d…`, `moves-protocol.json` and `moves-protocol-auth.json` 404. `status/consoles/dev-console.json` exists (687 bytes, 17:49:46Z, the console's own report).
+
+Cause, read from `f3bf1214`: `publish-floor`'s census reads the console's own report through `ExactEvidence.ConsoleReport` (`internal/hostactors/census.go:349`), at `status/consoles/<id>.json`. The signer's `ReadControl` statement (`signing-host.md`) grants `GetObject` only on `control/*` and `status/boxes/*`, and `ListForNotFound` lists only `control/*`, `status/boxes/*`, `claim-signer/*` and `apps/bab/console/*`. The root-runner rehearsal ran the phase with other credentials, so it never met this. This is not the #312 race the runbook's one retry is for: a second `console-report` and `publish-floor` would be refused the same way. So the retry was not run, and the policy was not widened, because that is outside the go. Resuming needs Paul's approval of a `signing-host.md` policy that also allows `s3:GetObject` on `status/consoles/dev-console.json` (and `status/consoles/*` in `ListForNotFound`), then a fifth signing session. Every other phase's reads are untested from the signer: `home` and `bind-key` never ran.
+
+Closing the signing session, 17:53Z to 17:55Z, in full:
+1. The key was shredded on the signer and `/run/claim-signer` removed (`shredded`, `dir gone`), 17:53:43Z.
+2. `i-06dcff9bf6386e406` was `terminated` at 17:54:22Z. The 7 exchange objects under `claim-signer/cohort1-20261019T175002Z/` were removed by exact key, after the listing was checked to equal the keys recorded at upload. Nothing remains under `claim-signer/`. Then the role was taken out of the profile, the profile deleted, the inline policy deleted, the managed policy detached, the role deleted, and `sg-06bf9d7e21f0e8956` deleted (17:54:47Z).
+3. CloudTrail since `2026-10-19T17:51:02Z`, read at 17:54:55Z and again at 18:10:38Z: one `GetParameter` on `/carlos/release-key`, at 17:51:04Z, from `arn:aws:sts::640012953448:assumed-role/carlos-claim-signer/i-06dcff9bf6386e406`. That is the session's one key fetch. No instance tagged `purpose=claim-signer` pending or running; `NoSuchEntity` for the role and the profile; no `carlos-claim-signer` security group.
+
+After the stop, 17:55Z: on leo and sophie the guard directories are still `755 root:root` and `sudo -u carlos test -x guard/carlos` passes. Edge and instance-control are `active` on both, unrestarted, and sophie's console `active` since 17:34:45Z. No AccessDenied line on either box since 17:45Z. The dev console's `/` and `/healthz` are 200 (`f3bf1214`), wakecheck 200 (sophie), claimtrial 200 via `--resolve` to leo.
+
+Left as they are ("Stop and hold"): both boxes armed and serving; sophie's console guarded; the roster at revision 5; no floor, home or key binding; the published v0.28.4 identity; every staged file. `box_inspect` rewrote sophie's `inspect.json`, `roster.json` and `rev` with the same bytes (`c9143bdf`, `6fc74d75`, `f0b5c2c2`). No host.env or flag changed, and no other box file. The `console-report` entry in sophie's box report is gone at the agent's next tick (#312) and needs nothing done.
+
### The second session's step 6 (v0.28.4, 2026-10-19)
**2026-10-19: 6.1 PASS; 6.1a PASS on leo and sophie; 6.2 STOP. The session is closed.** Run by the executor for the controller, on Paul's go for step 6 (install-channel, guard-arm and the guarded console restart included), confirmed directly with the controller. Commands went over SSM from DOCS-T1, as in 5.1. No host.env or flag was changed. P9, `install-console`, `guard-arm --console`, `console-config` and the console restart were not run: the dev console is untouched (`f3bf1214` from 5.0a).