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>
2 files changed,
+53
−1
docs/superpowers/notes/claim-trial-inputs/signing-host.md+1 −0docs/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). |