carlos / platform Public

docs: cohort-1 runbook: step 6 artefacts on main 7c1beb2d; P9 console mail off

- every "pending v0.28.2 (branch claim-step6-artifacts @ c38a4ca9)"
  marker is now "on main 7c1beb2d, ships in v0.28.2 (not yet
  published)"; verbs and flags re-checked against 7c1beb2d (help,
  install-console, guard-arm --console unchanged)
- folded in the merge's fixes: --out is reserved before the phase can
  write and kept on failure (rerun with a new path); the sums must be
  the tag's own list (carlos_0.28.2_{amd64,arm64}.deb); the console must
  be stripped (bounded ELF check refuses compressed sections)
- new precheck P9 from the console invite delivery census ruling:
  sophie's console mail off (CARLOS_CONSOLE_MAIL_FROM and every
  CARLOS_MAIL_* name absent from the process, unit, drop-ins,
  environment files and host.env; no console-mail role policy; no
  console_mail in the dev root). Run read-only 2026-10-18 09:13Z: PASS.
  Re-run before step 6.3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev c2dff3ec24d92f60475c2ce22f6d6dc1ae0242d0 parent 65584bb
5 files changed, +67 −23
  • docs/superpowers/notes/claim-trial-inputs/README.md +10 −10
  • docs/superpowers/notes/claim-trial-inputs/identity.template.json +1 −1
  • docs/superpowers/notes/claim-trial-inputs/policy.template.json +1 −1
  • docs/superpowers/notes/claim-trial-inputs/signing-host.md +1 −1
  • docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md +54 −10

CI passed — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.011s
?   	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	309.396s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	2.932s
ok  	github.com/carlosframework/platform/cmd/moveprobe	93.165s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.700s
ok  	github.com/carlosframework/platform/internal/activator	250.960s
ok  	github.com/carlosframework/platform/internal/agentsock	0.026s
ok  	github.com/carlosframework/platform/internal/alerting	0.003s
ok  	github.com/carlosframework/platform/internal/analytics	0.019s
ok  	github.com/carlosframework/platform/internal/appconfig	2.470s
ok  	github.com/carlosframework/platform/internal/apperr	0.074s
ok  	github.com/carlosframework/platform/internal/applog	0.131s
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.752s
?   	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.011s
ok  	github.com/carlosframework/platform/internal/caps	0.022s
ok  	github.com/carlosframework/platform/internal/certwarm	0.003s
ok  	github.com/carlosframework/platform/internal/cgroup	0.692s
ok  	github.com/carlosframework/platform/internal/changelog	0.304s
ok  	github.com/carlosframework/platform/internal/claimactivation	0.022s
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	402.506s
?   	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	6.432s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.009s
ok  	github.com/carlosframework/platform/internal/dbverify	0.004s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.005s
ok  	github.com/carlosframework/platform/internal/docsite	0.004s
ok  	github.com/carlosframework/platform/internal/domains	2.399s
ok  	github.com/carlosframework/platform/internal/doorkey	0.006s
ok  	github.com/carlosframework/platform/internal/economics	0.005s
ok  	github.com/carlosframework/platform/internal/edge	44.847s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.011s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.132s
ok  	github.com/carlosframework/platform/internal/gate	1.660s
ok  	github.com/carlosframework/platform/internal/genselect	0.014s
ok  	github.com/carlosframework/platform/internal/geo	0.017s
ok  	github.com/carlosframework/platform/internal/geoip	0.031s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.152s
ok  	github.com/carlosframework/platform/internal/host	178.037s
ok  	github.com/carlosframework/platform/internal/hostactors	0.554s
ok  	github.com/carlosframework/platform/internal/hostbox	2.687s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.558s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.017s
ok  	github.com/carlosframework/platform/internal/hostexec	4.687s
ok  	github.com/carlosframework/platform/internal/hostfence	0.032s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.350s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.020s
ok  	github.com/carlosframework/platform/internal/hostguard	1.032s
ok  	github.com/carlosframework/platform/internal/hostops	1.648s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.018s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.017s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.456s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.144s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.017s
ok  	github.com/carlosframework/platform/internal/infraview	0.015s
ok  	github.com/carlosframework/platform/internal/instancectl	5.233s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.719s
ok  	github.com/carlosframework/platform/internal/instancefs	0.021s
ok  	github.com/carlosframework/platform/internal/instancekey	0.018s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.008s
ok  	github.com/carlosframework/platform/internal/layout	1.516s
ok  	github.com/carlosframework/platform/internal/lbhealth	0.002s
ok  	github.com/carlosframework/platform/internal/lease	9.791s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.005s
ok  	github.com/carlosframework/platform/internal/ledger	0.013s
ok  	github.com/carlosframework/platform/internal/mail	0.160s
ok  	github.com/carlosframework/platform/internal/maintenance	0.078s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.488s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.080s
ok  	github.com/carlosframework/platform/internal/metrics	0.009s
ok  	github.com/carlosframework/platform/internal/moves	20.842s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.787s
ok  	github.com/carlosframework/platform/internal/objstore	0.187s
ok  	github.com/carlosframework/platform/internal/ops	0.164s
ok  	github.com/carlosframework/platform/internal/park	0.195s
ok  	github.com/carlosframework/platform/internal/privatefile	0.003s
ok  	github.com/carlosframework/platform/internal/provision	16.643s
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.402s
ok  	github.com/carlosframework/platform/internal/relay	7.226s
ok  	github.com/carlosframework/platform/internal/release	21.156s
ok  	github.com/carlosframework/platform/internal/rollout	3.606s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.003s
ok  	github.com/carlosframework/platform/internal/rootaws	0.006s
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.569s
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.482s
ok  	github.com/carlosframework/platform/internal/seeds	0.014s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	8.360s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	4.245s
ok  	github.com/carlosframework/platform/internal/status	0.039s
ok  	github.com/carlosframework/platform/internal/steering	0.006s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.237s
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.006s
ok  	github.com/carlosframework/platform/internal/usage	0.004s
ok  	github.com/carlosframework/platform/internal/version	0.001s
ok  	github.com/carlosframework/platform/internal/vet	1.554s
=== 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/18 09:21:30 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DF9542A5C24417, 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.01s)
PASS
ok  	github.com/carlosframework/platform/internal/host	0.021s
minio contract: PASS
diff --git a/docs/superpowers/notes/claim-trial-inputs/README.md b/docs/superpowers/notes/claim-trial-inputs/README.md
index 7a70f68d4..043a20787 100644
--- a/docs/superpowers/notes/claim-trial-inputs/README.md
+++ b/docs/superpowers/notes/claim-trial-inputs/README.md
@@ -1,8 +1,8 @@
# Claim trial inputs, dev cohort 1 (draft for review)
-These are the concrete inputs the runbook's steps 5 to 7 say "the controller supplies and Paul approves" (`docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md`). They were drafted on 2026-10-18 from dev values read with read-only calls, then updated with platform-1e's rulings the same day. Nothing here is signed, and no secret is in any file. A value still open is marked `TO-CONFIRM`; one that waits on a platform fix is marked `BLOCKED`, or `pending v0.28.2` once the fix is built and waits only for the release.
+These are the concrete inputs the runbook's steps 5 to 7 say "the controller supplies and Paul approves" (`docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md`). They were drafted on 2026-10-18 from dev values read with read-only calls, then updated with platform-1e's rulings the same day. Nothing here is signed, and no secret is in any file. A value still open is marked `TO-CONFIRM`; one that waits on a platform fix is marked `BLOCKED`, or `on main 7c1beb2d, ships in v0.28.2 (not yet published)` once the fix is merged and waits only for the release.
-Builder findings from the review of platform-1e's branch `claim-step6-artifacts` (`c38a4ca9`, census ruled, pending v0.28.2) were added on 2026-10-18, drafted for Paul after v0.28.2: the console built with `version.Build=v0.28.2`, pointer digests for the console's channel (runbook step 5.0a), the fixed console path, and `publish-identity`'s verbatim inputs.
+Builder findings from the review of platform-1e's branch `claim-step6-artifacts` (`c38a4ca9`, census ruled; now on main 7c1beb2d, ships in v0.28.2 (not yet published)) were added on 2026-10-18, drafted for Paul after v0.28.2: the console built with `version.Build=v0.28.2`, pointer digests for the console's channel (runbook step 5.0a), the fixed console path, and `publish-identity`'s verbatim inputs.
Timing: step 5.0 (box identity records) is done. Everything after it, from 5.1 (roster and enrollment) on, waits for v0.28.2, which carries the fixes for gaps 1 and 2, and runs in one signing session (Paul's ruling, 2026-10-18).
@@ -46,16 +46,16 @@ Platform main at drafting: `b1118925` (region HA 2b). The box-record verb is on
| Artifact | Value | Status |
|---|---|---|
| Release for step 5 | v0.28.1, dev only (box-record verb), `3b69c2bd`, published 2026-10-18; on leo, sophie, dennis and the dev console. `carlos-linux-arm64` sha256 `2ae174da29b1d10fdcb009172ba830db5460b62e838bcecc7d9e17535f35adea` (signed `SHA256SUMS.txt`, verified) | confirmed |
-| Release for step 6 onwards | v0.28.2 (`claims run` phases `sign-policy` and `publish-identity`, `claims run --out`, `claims install-console`) | pending v0.28.2 (branch `claim-step6-artifacts` @ `c38a4ca9`) |
+| Release for step 6 onwards | v0.28.2 (`claims run` phases `sign-policy` and `publish-identity`, `claims run --out`, `claims install-console`) | on main 7c1beb2d, ships in v0.28.2 (not yet published) |
| Router linux-arm64 sha256 (v0.28.2) | the `carlos-linux-arm64` line of the v0.28.2 release's signed `SHA256SUMS.txt`, read by `publish-identity` from the sums text in its plan. Never typed | TO-CONFIRM when published |
-| Dev console build for v0.28.2 | built from the v0.28.2 tag with `-ldflags "-s -w -X github.com/carlosframework/platform/internal/version.Build=v0.28.2"`: the release tag, not the short sha. `publish-identity` reads the stamp and refuses any other; the console's floor report compares it with the identity's version, and the identity is create-only per tag. Shipped as version `<short sha of v0.28.2's commit>` and promoted to `canary/console` after pointer digests are on (runbook 5.0a). `roll-console.sh` can't build it: it stamps the short sha | TO-CONFIRM: built in 5.0a |
+| Dev console build for v0.28.2 | built from the v0.28.2 tag with `-ldflags "-s -w -X github.com/carlosframework/platform/internal/version.Build=v0.28.2"`: the release tag, not the short sha. `publish-identity` reads the stamp and refuses any other; the console's floor report compares it with the identity's version, and the identity is create-only per tag. Shipped as version `<short sha of v0.28.2's commit>` and promoted to `canary/console` after pointer digests are on (runbook 5.0a). `roll-console.sh` can't build it: it stamps the short sha. It must be stripped (`-s -w`): main's bounded ELF check refuses the compressed debug sections of an unstripped build | TO-CONFIRM: built in 5.0a |
| Console account, app and channel | `bab`, `console`, `canary/console` (pointer `apps/bab/console/channels/canary/console.json`) | TO-CONFIRM by runbook 5.0a 1a's read-only check. If the console's pointer is in any other account or channel, that is a STOP: the values are not adapted, it is reported on #250. Flagship's console is `bab`, `canary/console`, and direct-bucket ships default to the ops account. Moving the console to its own account is out of scope for the trial |
| Pointer digests for `bab` | off today; must be on, and `canary/console`'s pointer must carry digests, before `publish-identity`. `carlos channels digests on --account-id bab`, then a fresh console promote (runbook 5.0a, its own Paul's go, outside any roll). It covers every `bab` app on dev; platform-1e accepts that in principle and Paul decides at the go, with 5.0a's listing of every `bab` app's pointers and every writer. A writer older than v0.26.0 is a STOP | TO-DO: 5.0a |
| Console linux-arm64 sha256 (v0.28.2) | read by `publish-identity`: the `canary/console` pointer's signed manifest digest for the console version, then that manifest's `linux-arm64` sha256, then the artefact itself, hashed and stamp-checked. Never typed by hand, and never taken from `SHA256SUMS.txt`: the phase refuses a console sha256 that is any file in the sums | TO-CONFIRM: equals the `sha256sum` of 5.0a's build |
| Router linux-arm64 sha256, v0.28.0 (current, for comparison) | `3ca7c74d5356a3995ecf677d142bc32fafaf790a2cc1bc0b628f3eec1b7ea731`, from the signed dev `control/system/router/stable.json` | confirmed |
-| Console executable on sophie | fixed at `/opt/carlos/system/console/console`, with `console.prev` beside it (same bytes at arming), both root:root `0755`, created by `carlos system claims install-console` on sophie from the build 5.0a promoted (`--source /opt/carlos/versions/<CV>/console`). `guard-arm --console` takes this path. Replaces today's `/opt/carlos/versions/26065378/console`, which has no `.prev` | pending v0.28.2 (branch `claim-step6-artifacts` @ `c38a4ca9`) |
-| `policy.json` | an armed `hostfloor.Policy` with the floor bytes and a root-signed `moves-protocol` authorization, written byte-exact by `claims run` phase `sign-policy` with `--out` on the signer and read directly; no `jq` or other parser ever touches it | pending v0.28.2 (branch `claim-step6-artifacts` @ `c38a4ca9`) |
-| `identity.json` | the signed system identity for v0.28.2 at `control/system/router/releases/v0.28.2.json`, published create-only by `claims run` phase `publish-identity` on the signer and written byte-exact to `--out` as read back; read directly, no `jq` or other parser ever touches it | pending v0.28.2 (branch `claim-step6-artifacts` @ `c38a4ca9`) |
+| Console executable on sophie | fixed at `/opt/carlos/system/console/console`, with `console.prev` beside it (same bytes at arming), both root:root `0755`, created by `carlos system claims install-console` on sophie from the build 5.0a promoted (`--source /opt/carlos/versions/<CV>/console`). `guard-arm --console` takes this path. Replaces today's `/opt/carlos/versions/26065378/console`, which has no `.prev` | on main 7c1beb2d, ships in v0.28.2 (not yet published) |
+| `policy.json` | an armed `hostfloor.Policy` with the floor bytes and a root-signed `moves-protocol` authorization, written byte-exact by `claims run` phase `sign-policy` with `--out` on the signer and read directly; no `jq` or other parser ever touches it | on main 7c1beb2d, ships in v0.28.2 (not yet published) |
+| `identity.json` | the signed system identity for v0.28.2 at `control/system/router/releases/v0.28.2.json`, published create-only by `claims run` phase `publish-identity` on the signer and written byte-exact to `--out` as read back; read directly, no `jq` or other parser ever touches it | on main 7c1beb2d, ships in v0.28.2 (not yet published) |
## publish-identity's plan
@@ -63,7 +63,7 @@ Platform main at drafting: `b1118925` (region HA 2b). The box-record verb is on
| Field | Value | Verbatim | Source |
|---|---|---|---|
-| `sha256sums` | the whole text of `SHA256SUMS.txt`, final newline included | yes, byte for byte | the v0.28.2 public release, `https://github.com/carlosframework/releases/releases/download/v0.28.2/SHA256SUMS.txt` |
+| `sha256sums` | the whole text of `SHA256SUMS.txt`, final newline included | yes, byte for byte | the v0.28.2 public release, `https://github.com/carlosframework/releases/releases/download/v0.28.2/SHA256SUMS.txt`. It must be v0.28.2's own list: it names `carlos_0.28.2_amd64.deb` and `carlos_0.28.2_arm64.deb` and no other `carlos_*.deb`, or the phase refuses it |
| `sha256sums_sig` | the whole text of `SHA256SUMS.txt.sig` | yes, byte for byte | the same release |
| `console.account` | `bab` | yes | checked in 5.0a 1a; a STOP if it differs |
| `console.channel` | `canary/console` | yes | the channel 5.0a promoted to, checked in 5.0a 1a; a STOP if it differs |
@@ -76,7 +76,7 @@ The sums files are downloaded on DOCS-T1 with the signer's CLI, passed to the si
## Gaps found while drafting
-platform-1e confirmed gaps 1 and 2 on main on 2026-10-18 and built the fixes, with Paul's go, on branch `claim-step6-artifacts` (`c38a4ca9`, census ruled): (a) the `claims run` phase `sign-policy`, which signs the armed policy for a floor on the trusted host and writes nothing to the bucket; (b) the `claims run` phase `publish-identity`, which publishes the signed system identity (router and console) at `control/system/router/releases/<tag>.json` from the trusted host (not the release workflow); and (c) `claims install-console`, the protected console install with its `.prev`. All ship in v0.28.2, before step 6.
+platform-1e confirmed gaps 1 and 2 on main on 2026-10-18 and built the fixes, with Paul's go, on branch `claim-step6-artifacts` (census ruled), now on main 7c1beb2d, ships in v0.28.2 (not yet published): (a) the `claims run` phase `sign-policy`, which signs the armed policy for a floor on the trusted host and writes nothing to the bucket; (b) the `claims run` phase `publish-identity`, which publishes the signed system identity (router and console) at `control/system/router/releases/<tag>.json` from the trusted host (not the release workflow); and (c) `claims install-console`, the protected console install with its `.prev`. All ship in v0.28.2, before step 6.
1. **No tool signs the armed policy offline.** `guard-arm` (step 6) refuses anything but `mode: "armed"` with a floor and a valid authorization: a root envelope over `{schema 1, purpose "moves-protocol", deployment_bucket, key "control/moves-protocol.json", sha256 of the floor bytes}`. On main, that authorization is signed only inside `hostfloor.Publish`, which writes it to the bucket, and `PublishFloor` (step 7) refuses unless every actor already reports `FloorGuardReady` for that floor digest, which needs the armed policy. The only other producer, `joinroot.go`, reads an already published floor. So step 6 needs a reviewed way to sign the policy without publishing: for example, a `claims run` phase that signs and prints it on the trusted host. Fix: (a) above.
2. **No system identity for any release exists in the dev bucket.** `guard-arm --identity` takes a release-workflow signed pointer with `current.release_tag` and `current.moves_protocol >= 1`, stored at `control/system/router/releases/<tag>.json`. The dev bucket holds only `control/system/router/{canary,stable}.json`, whose payloads carry neither field, and `release.PublishSystemIdentity` has no production caller on main. Fix: (b) above.
@@ -97,7 +97,7 @@ Known now: v0.28.1 is published and on dev; the carlos v0.28.1 and jq 1.7.1 sha2
TO-DO, its own Paul's go, after v0.28.2 and before the signing session: runbook 5.0a, pointer digests on for `bab` and a fresh promote of the console built with `version.Build=v0.28.2` (gap 4).
-Pending v0.28.2 (built by platform-1e on branch `claim-step6-artifacts` @ `c38a4ca9`):
+On main 7c1beb2d, ships in v0.28.2 (not yet published) (built by platform-1e on branch `claim-step6-artifacts`):
5. `policy.json`: the signed armed policy, from `sign-policy --out` (gap 1).
6. `identity.json`: the signed v0.28.2 system identity, from `publish-identity --out` (gap 2).
7. Sophie's protected console at `/opt/carlos/system/console/console` and its `.prev`, from `install-console` (gap 3; `.prev` equals current at arming).
diff --git a/docs/superpowers/notes/claim-trial-inputs/identity.template.json b/docs/superpowers/notes/claim-trial-inputs/identity.template.json
index 1af62c740..6e8cd2257 100644
--- a/docs/superpowers/notes/claim-trial-inputs/identity.template.json
+++ b/docs/superpowers/notes/claim-trial-inputs/identity.template.json
@@ -23,5 +23,5 @@
},
"spread": "0s"
},
- "note": "Shape only, pending v0.28.2 (branch claim-step6-artifacts @ c38a4ca9). The real file is the root-signed identity ({payload, sig}) that the claims run phase publish-identity publishes create-only at control/system/router/releases/v0.28.2.json and writes byte-exact to --out. The channel is always stable and is read by nothing that checks an identity. No sha256 is typed: publish-identity reads both"
+ "note": "Shape only, on main 7c1beb2d, ships in v0.28.2 (not yet published). The real file is the root-signed identity ({payload, sig}) that the claims run phase publish-identity publishes create-only at control/system/router/releases/v0.28.2.json and writes byte-exact to --out. The channel is always stable and is read by nothing that checks an identity. No sha256 is typed: publish-identity reads both"
}
diff --git a/docs/superpowers/notes/claim-trial-inputs/policy.template.json b/docs/superpowers/notes/claim-trial-inputs/policy.template.json
index 7cf66dc56..386a16c0d 100644
--- a/docs/superpowers/notes/claim-trial-inputs/policy.template.json
+++ b/docs/superpowers/notes/claim-trial-inputs/policy.template.json
@@ -6,5 +6,5 @@
"min": 1,
"min_release": "v0.28.2"
},
- "authorization": "Pending v0.28.2 (branch claim-step6-artifacts @ c38a4ca9): root-signed envelope of {schema: 1, purpose: \"moves-protocol\", deployment_bucket: \"carlos-dev-640012953448\", key: \"control/moves-protocol.json\", sha256: <sha256 of the exact floor bytes>}, signed on the trusted host by the claims run phase sign-policy, which writes nothing to the bucket and writes the policy byte-exact to --out"
+ "authorization": "On main 7c1beb2d, ships in v0.28.2 (not yet published): root-signed envelope of {schema: 1, purpose: \"moves-protocol\", deployment_bucket: \"carlos-dev-640012953448\", key: \"control/moves-protocol.json\", sha256: <sha256 of the exact floor bytes>}, signed on the trusted host by the claims run phase sign-policy, which writes nothing to the bucket and writes the policy byte-exact to --out"
}
diff --git a/docs/superpowers/notes/claim-trial-inputs/signing-host.md b/docs/superpowers/notes/claim-trial-inputs/signing-host.md
index b1b64892a..24b45cbb2 100644
--- a/docs/superpowers/notes/claim-trial-inputs/signing-host.md
+++ b/docs/superpowers/notes/claim-trial-inputs/signing-host.md
@@ -79,7 +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.
-- **ReadConsoleRelease and WriteIdentity** (added 2026-10-18 for step 6.1, pending v0.28.2, branch `claim-step6-artifacts` @ `c38a4ca9`): `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.2.json`, create-only. `WriteIdentity` names that exact key, not a prefix: no wildcard, so the role can write no other release's identity. `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.
+- **ReadConsoleRelease and WriteIdentity** (added 2026-10-18 for step 6.1; on main 7c1beb2d, ships in v0.28.2 (not yet published)): `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.2.json`, create-only. `WriteIdentity` names that exact key, not a prefix: no wildcard, so the role can write no other release's identity. `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 880c53d28..17e295072 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
@@ -1,6 +1,6 @@
# Host claim activation, dev cohort 1: mechanics trial runbook
-Draft, first written 2026-10-16, revised 2026-10-17 against main at 26065378. Step 5.0a and step 6 were revised on 2026-10-18 against platform-1e's branch `claim-step6-artifacts` at `c38a4ca9` (pending v0.28.2). It authorizes nothing. Every change below waits for Paul's go at its own checkpoint. Neither the approved copy quoted in step 8 nor the activation tools' tests authorize running anything.
+Draft, first written 2026-10-16, revised 2026-10-17 against main at 26065378. Step 5.0a and step 6 were revised on 2026-10-18 against platform-1e's branch `claim-step6-artifacts` at `c38a4ca9`, and re-checked against main at `7c1beb2d` after it merged (ships in v0.28.2, not yet published). It authorizes nothing. Every change below waits for Paul's go at its own checkpoint. Neither the approved copy quoted in step 8 nor the activation tools' tests authorize running anything.
Dev cohort 1 is a mechanics trial (Paul, 2026-10-16, relayed by platform-1e):
- The scope is one box, `carlos-dev-ie-b` (leo-climbing). `carlos-dev-uk` is not part of this trial; it stays a later option.
@@ -189,6 +189,48 @@ Pass: all twelve are root-owned regular files in `/etc/systemd/system`; the edge
- `jq`: absent on leo, sophie and dennis (re-run today). Nothing is installed for the trial; every `jq` line runs on the trusted signing host (H9).
- Kernel 6.12.105, `/data` on ext4, 17G free, 927 MB of memory, systemd 257 (2026-10-16 and today).
+### P9. Sophie's console has console mail off
+
+Why: a census ruling on branch `design-console-invite-delivery` (not merged; tip `5aef01db` on 2026-10-18) says that once it lands, the console's own mailer (the invite fallback) bypasses the claim mail gate when HostClaim is on. Sophie's console becomes a claim actor in step 6, so its console mail must be off: the region-host variable `console_mail_from` unset, and no console mail environment.
+
+What turns console mail on. From that branch's `cmd/console/main.go` (`consoleMailer`) and `internal/console/consolemail.go` (`ConsoleMailFrom`): the console sends mail when it has a From, which is `CARLOS_CONSOLE_MAIL_FROM` or, if that is unset, `noreply@console-mail.bab.<CARLOS_MAIL_PLATFORM_SUFFIX>`, and also a mail provisioner, which `CARLOS_MAIL_REGION` creates. The send grant is the role policy `<iam_name>-console-mail` (resource `host_console_mail`), which region-host creates only when `console_mail_from` is set. Main at `7c1beb2d` has no `CARLOS_CONSOLE_MAIL_FROM` and no `console_mail_from` yet. The console mail names main's console reads are `CARLOS_MAIL_REGION`, `CARLOS_MAIL_PLATFORM_SUFFIX`, `CARLOS_MAIL_DMARC_RUA`, `CARLOS_MAIL_SNS_TOPIC_ARN`, `CARLOS_MAIL_OPERATORS` and `CARLOS_MAIL_LEGACY_SWEEP` (`cmd/console/main.go`; `internal/console` and `internal/mail` read no other mail names). The console reads no config file of its own: everything comes from its environment. The check therefore covers all seven names, `CARLOS_CONSOLE_MAIL_FROM` included, in every place the running console's environment can come from: the process itself, the unit's `Environment=`, the unit file, its drop-ins, every `EnvironmentFile`, and `/etc/carlos/host.env`. It prints names and file paths only, never a value.
+
+```bash
+$TMPDIR/cohort1/ssm.sh eu-west-1 i-05498eada6f0afd23 '
+names="CARLOS_CONSOLE_MAIL_FROM CARLOS_MAIL_REGION CARLOS_MAIL_PLATFORM_SUFFIX CARLOS_MAIL_DMARC_RUA CARLOS_MAIL_SNS_TOPIC_ARN CARLOS_MAIL_OPERATORS CARLOS_MAIL_LEGACY_SWEEP"
+u=console.service
+pid=$(systemctl show -p MainPID --value $u)
+files=$( { echo /etc/carlos/host.env; systemctl show -p FragmentPath --value $u; systemctl show -p DropInPaths --value $u | tr " " "\n"; systemctl show -p EnvironmentFiles --value $u | awk "{print \$1}"; } | grep . | sort -u | tr "\n" " ")
+hits=0
+for n in $names; do
+ if [ "$pid" -gt 0 ] && tr "\0" "\n" < /proc/$pid/environ | grep -q "^$n="; then echo "SET $n in the running console (pid $pid)"; hits=$((hits+1)); fi
+ if systemctl show -p Environment --value $u | tr " " "\n" | grep -q "^$n="; then echo "SET $n in $u Environment="; hits=$((hits+1)); fi
+ for f in $files; do
+ [ -f "$f" ] || continue
+ if grep -qE "(^|[[:space:]\"=])$n=" "$f"; then echo "SET $n in $f"; hits=$((hits+1)); fi
+ done
+done
+echo "checked: the running console (pid $pid), $u Environment=, and $(echo $files | wc -w) files: $files"
+for f in $files; do [ -e "$f" ] || echo "missing: $f"; done
+if [ $hits -eq 0 ]; then echo "console mail: OFF (none of the 7 names is set)"; else echo "console mail: $hits setting(s) found: STOP"; fi'
+aws --profile kass iam list-role-policies --role-name carlos-dev-host --query PolicyNames --output text
+git -C platform-infrastructure grep -n console_mail origin/main -- dev/
+```
+
+Pass: status `Success`, no `SET` line and no `missing:` line, the last box line `console mail: OFF (none of the 7 names is set)`, no `carlos-dev-host-console-mail` among sophie's role policies, and no `console_mail` line in the dev root (after a `git fetch` of `platform-infrastructure`). After step 6's `console-config` the file list also includes `/etc/carlos/claim-console.env` and `91-claim-runtime.conf`; the same pass applies. Any `SET` line, a console-mail role policy, or a dev root that sets `console_mail_from` is a STOP: report on #250, change nothing.
+
+2026-10-18 09:13Z, run read-only once from DOCS-T1 (SSM command `cohort1 read-only console mail check`): PASS.
+
+```
+Success checked: the running console (pid 567693), console.service Environment=, and 5 files: /etc/carlos/host.env /etc/carlos/release.env /etc/systemd/system/console.service /etc/systemd/system/console.service.d/apps-domain.conf /etc/systemd/system/console.service.d/release-key.conf
+console mail: OFF (none of the 7 names is set)
+carlos-dev-host-buckets carlos-dev-host-metrics deny-release-key-reads
+```
+
+The dev root grep, at `platform-infrastructure` main `a0b3f5a`, printed nothing.
+
+Re-run P9 right before step 6 (step 6.3), inside the signing session and before `install-console`, because 5.0a restarts the console on a new build and the invite delivery branch may have landed by then.
+
## Blockers
1. **Release.** Resolved: v0.28.0, cut from 26065378 (Plan 14 Parts 2 and 3, the activation tools, e8e961dd), is fully rolled on dev (2026-10-18). Step 1. Two dev-only releases follow, each rolled to dev with Paul's go: v0.28.1 (the box-record verb), rolled 2026-10-18 and used by step 5.0 (done), and v0.28.2 (gap 1 and 2 fixes), which everything past 5.0 waits for. The v0.28.2 dev roll moves the boxes only: leave out step 1's console item, because step 5.0a ships and promotes the dev console's v0.28.2 build after pointer digests are on.
@@ -197,7 +239,7 @@ Pass: all twelve are root-owned regular files in `/etc/systemd/system`; the edge
4. **Box identity records.** Resolved 2026-10-18 (step 5.0 done: `control/boxes/leo-climbing.json` and `control/boxes/sophie-knitting.json`). Step 5's enrollment needs each box's signed AWS identity record at `control/boxes/<box>.json`. None exists, and none has ever been written: the publisher (`internal/host/boxrecord.go`, `boxRecordLoop`) runs only with `CARLOS_REGION_HA` on, which is off on every dev box. Leo's and sophie's records must be published before step 5. Paul ruled a reviewed one-off publish, with region HA staying off.
The verb `carlos system box publish-record` (branch `box-publish-record`, merging now) ships in v0.28.1, dev only, and is the supported way. It replaces the agent's region-HA-only publisher for this trial; step 5.0 runs it once per box. The record is AWS-signed (the EC2 instance identity document and AWS's signature over it), not signed by the box or the operator, and grants nothing until enrollment reads it.
-5. **Signed armed policy and system identity (everything past 5.0).** Step 6's `guard-arm` needs an armed `policy.json` (the floor plus a root-signed `moves-protocol` authorization) and a signed system identity at `control/system/router/releases/<tag>.json`. On main, nothing signs the policy without publishing it, and nothing publishes a system identity; the dev bucket has none. platform-1e confirmed both gaps on 2026-10-18 and built the fixes, with Paul's go, on branch `claim-step6-artifacts` (`c38a4ca9`, census ruled; pending v0.28.2): (a) the `claims run` phase `sign-policy`, which signs the armed policy for a floor on the trusted host and writes nothing to the bucket; (b) the `claims run` phase `publish-identity`, which publishes the release's signed identity (router and console) from the trusted host, not from the release workflow; (c) `claims install-console`, which puts the protected console and its `.prev` at `/opt/carlos/system/console/console` on sophie. All three ship in v0.28.2, so the floor's `min_release` is v0.28.2. `publish-identity` also needs the dev console's channel pointer to carry signed manifest digests and a console built with `version.Build=v0.28.2` (step 5.0a). By Paul's ruling the signing session (5.1 to 7) opens only after v0.28.2, so this blocker gates everything past 5.0. Details in `docs/superpowers/notes/claim-trial-inputs/README.md`.
+5. **Signed armed policy and system identity (everything past 5.0).** Step 6's `guard-arm` needs an armed `policy.json` (the floor plus a root-signed `moves-protocol` authorization) and a signed system identity at `control/system/router/releases/<tag>.json`. On main, nothing signs the policy without publishing it, and nothing publishes a system identity; the dev bucket has none. platform-1e confirmed both gaps on 2026-10-18 and built the fixes, with Paul's go, on branch `claim-step6-artifacts` (census ruled), now on main 7c1beb2d, ships in v0.28.2 (not yet published): (a) the `claims run` phase `sign-policy`, which signs the armed policy for a floor on the trusted host and writes nothing to the bucket; (b) the `claims run` phase `publish-identity`, which publishes the release's signed identity (router and console) from the trusted host, not from the release workflow; (c) `claims install-console`, which puts the protected console and its `.prev` at `/opt/carlos/system/console/console` on sophie. All three ship in v0.28.2, so the floor's `min_release` is v0.28.2. `publish-identity` also needs the dev console's channel pointer to carry signed manifest digests and a console built with `version.Build=v0.28.2` (step 5.0a). By Paul's ruling the signing session (5.1 to 7) opens only after v0.28.2, so this blocker gates everything past 5.0. Details in `docs/superpowers/notes/claim-trial-inputs/README.md`.
## Accepted trial gaps
@@ -378,7 +420,7 @@ Any other answer is a stop. The `invite-box` dry run in 5.1 then checks the same
### 5.0a Pointer digests and the release-stamped dev console
-Builder finding from the review of platform-1e's branch `claim-step6-artifacts` (`c38a4ca9`), drafted for Paul after v0.28.2.
+Builder finding from the review of platform-1e's branch `claim-step6-artifacts` (`c38a4ca9`, now on main 7c1beb2d, ships in v0.28.2 (not yet published)), drafted for Paul after v0.28.2.
Runs after v0.28.2 is on dev's boxes and checked, and before the signing session opens. Paul's go: separate, for this step alone. It is not part of any roll. The roll notes from v0.26.0 to v0.28.0 say "don't run `carlos channels digests on`" while rolling, so this step runs on its own, with no roll in progress. It changes nothing claim-related: leo's claims stay off and the roster is not touched.
@@ -463,7 +505,7 @@ go version -m console-linux-arm64 | grep -- -ldflags
sha256sum console-linux-arm64
```
-Expected: the `-ldflags` line holds `-X github.com/carlosframework/platform/internal/version.Build=v0.28.2` exactly once, and no other `version.Build`. `publish-identity` refuses an unstamped console, one stamped twice, or one stamped with anything but the tag. Don't use `roll-console.sh`: it stamps the short sha.
+Expected: the `-ldflags` line holds `-X github.com/carlosframework/platform/internal/version.Build=v0.28.2` exactly once, and no other `version.Build`. `publish-identity` refuses an unstamped console, one stamped twice, or one stamped with anything but the tag. Keep `-s -w`: main at `7c1beb2d` runs a bounded ELF check (`release.CheckELFBounds`) before it reads the stamp, and it refuses compressed sections, which an unstripped Go build's debug sections are. Don't use `roll-console.sh`: it stamps the short sha.
**3. Turn digests on for `bab`.** The step's one go covers 3 and 4 together.
@@ -533,7 +575,7 @@ For commands that write, add --dry-run to validate and print the planned operati
Run signing phases on the trusted operator host. Run box installers as root.
```
-Pending v0.28.2 (branch `claim-step6-artifacts` @ `c38a4ca9`), the help changes in three places, quoted from that branch's `internal/host/activation_command.go`:
+On main 7c1beb2d, ships in v0.28.2 (not yet published): the help changes in three places, quoted from main's `internal/host/activation_command.go` at `7c1beb2d`:
```
run --plan FILE --signing-key FILE --public-key K --aws-region R [--out FILE]
@@ -671,13 +713,13 @@ On failure: hold. A partly applied phase is resumed from the latest authenticate
## Step 6. Arm leo's guard and start the guarded console
-Inside the signing session, after 5.1. Waits for v0.28.2 on dev and for 5.0a. Everything this step adds to main is pending v0.28.2 (branch `claim-step6-artifacts` @ `c38a4ca9`): the `claims run` phases `sign-policy` and `publish-identity`, `claims run --out`, and `claims install-console`. Never hand-make any of their files. The builder findings from the review of that branch (the release-tag stamp, 5.0a, the fixed console path and the verbatim identity inputs) are drafted here for Paul after v0.28.2.
+Inside the signing session, after 5.1. Waits for v0.28.2 on dev and for 5.0a. Everything this step adds is on main 7c1beb2d, ships in v0.28.2 (not yet published): the `claims run` phases `sign-policy` and `publish-identity`, `claims run --out`, and `claims install-console`. Never hand-make any of their files. The builder findings from the review of that branch (the release-tag stamp, 5.0a, the fixed console path and the verbatim identity inputs) are drafted here for Paul after v0.28.2.
Paul's go: once for step 6. It restarts `console.service` on sophie, which briefly interrupts the dev console.
### 6.1 Sign the policy and publish the identity (signer)
-`policy.json` and `identity.json` are byte-exact files, read directly. Nothing parses or rewrites them: no `jq`, no reformatting, no copy and paste. Each phase writes its file to `--out`: a new absolute path in a root-owned directory that no group or other user can write. The phase creates it `0600`, reads it back, and refuses a path that already exists. The dry run needs `--out` too; it checks the path and writes nothing. `apply_plan` passes no `--out`, so add this helper beside `phase` on the signer:
+`policy.json` and `identity.json` are byte-exact files, read directly. Nothing parses or rewrites them: no `jq`, no reformatting, no copy and paste. Each phase writes its file to `--out`: a new absolute path in a root-owned directory that no group or other user can write. On main at `7c1beb2d` the phase creates the file `0600` before it can write to the bucket, fills it, and reads it back; it refuses a path that already exists. If the phase fails after that, the file is kept and the error says so (and whether the phase had already written to the bucket): run it again with a new `--out` path, never by deleting the old file. The dry run needs `--out` too; it checks the path and creates nothing. `apply_plan` passes no `--out`, so add this helper beside `phase` on the signer:
```bash
# phase_out <phase> <input> <out file> <result file>: phase, for the two phases that write a file to --out.
@@ -715,7 +757,7 @@ Expected: the dry run, then the apply, succeed; `writes` is empty, because the p
| `identity.release_tag` | `v0.28.2` | the release tag |
| `identity.moves_protocol` | `1` | `moves.ProtocolVersion` on main, the floor's `min` |
-Verbatim means the two sums files reach the plan as JSON strings holding their bytes unchanged. Download them on DOCS-T1 with the signer's CLI (`signing-host.md`), pass them to the signer through the exchange prefix with `aws s3 cp`, and read them with `jq --rawfile`. Never retype, trim or re-save them. The phase verifies `SHA256SUMS.txt` against its signature with the vendor release key the carlos binary embeds, so one changed byte is refused. The console values are copied from 5.0a's record, not from memory. No sha256 is typed anywhere: the router's comes from the signed sums (`carlos-linux-arm64`), and the console's from the channel pointer's signed manifest digest, the manifest and the artefact. The console's sha256 may not be any file listed in the sums.
+Verbatim means the two sums files reach the plan as JSON strings holding their bytes unchanged. Download them on DOCS-T1 with the signer's CLI (`signing-host.md`), pass them to the signer through the exchange prefix with `aws s3 cp`, and read them with `jq --rawfile`. Never retype, trim or re-save them. The phase verifies `SHA256SUMS.txt` against its signature with the vendor release key the carlos binary embeds, so one changed byte is refused. It also checks that the list is v0.28.2's own: it must name `carlos_0.28.2_amd64.deb` and `carlos_0.28.2_arm64.deb`, and no other `carlos_*.deb`, so another release's genuine list is refused under this tag. The console values are copied from 5.0a's record, not from memory. No sha256 is typed anywhere: the router's comes from the signed sums (`carlos-linux-arm64`), and the console's from the channel pointer's signed manifest digest, the manifest and the artefact. The console's sha256 may not be any file listed in the sums.
```bash
CV=<the console ship version recorded in 5.0a>
@@ -758,7 +800,9 @@ set +a
### 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 (pending v0.28.2, branch `claim-step6-artifacts` @ `c38a4ca9`). Its `--source` is the console build 5.0a promoted, at `/opt/carlos/versions/<CV>/console`, the path 5.0a's `readlink` showed.
+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 (on main 7c1beb2d, ships in v0.28.2 (not yet published)). Its `--source` is the console build 5.0a promoted, at `/opt/carlos/versions/<CV>/console`, the path 5.0a's `readlink` showed.
+
+First, from DOCS-T1, re-run P9 (sophie's console mail is off). Any `SET` line or a console-mail role policy is a STOP: close the session.
On the trusted host, stage sophie's current roster and revision:
@@ -1711,6 +1755,6 @@ For platform-1e:
2. For a later teardown out of hold: how do writes made under the claim reach the claims-off restore path?
3. Answered 2026-10-18: leo's object-version actions are applied for the trial; the box-records bucket policy goes with the fence-writer role in platform-infrastructure#46.
4. Box identity records: `carlos system box publish-record` ships in v0.28.1, dev only (step 5.0). The v0.28.1 dev roll comes before step 5.
-6. Answered 2026-10-18: platform-1e builds the armed policy signing phase and the published system identity with the protected console install, in v0.28.2 (blocker 5). Built on branch `claim-step6-artifacts` (`c38a4ca9`, census ruled): `sign-policy`, `publish-identity` and `install-console`. Step 6 waits for it, and for 5.0a; step 5.0 did not.
+6. Answered 2026-10-18: platform-1e builds the armed policy signing phase and the published system identity with the protected console install, in v0.28.2 (blocker 5). Built on branch `claim-step6-artifacts` (census ruled), on main 7c1beb2d, ships in v0.28.2 (not yet published): `sign-policy`, `publish-identity` and `install-console`. Step 6 waits for it, and for 5.0a; step 5.0 did not.
7. Still open in `docs/superpowers/notes/claim-trial-inputs/README.md`: the console enrollment token, the v0.28.1 and v0.28.2 artifacts, and the signing host values. Namespace `platform`, sophie's pool `console`, console ID `dev-console` and operator `paul@keymail.dev` are confirmed.
5. The console's mutation classifier lets it delete under `control/boxes/` and `control/box-volumes/`, which the new box-records bucket policy refuses: amadan carlos/platform#302.