carlos / platform Public

docs: cohort-1 runbook moves to v0.28.4 (install-channel --identity)

Paul chose option (b) for the untagged channel pointers: main f3bf1214
adds a required --identity to install-channel --policy. v0.28.4 is
built from it (with console invite delivery, 91a651b3); not yet
published or rolled.

- 6.1a: install-channel --policy <policy> --identity <identity>, the
  -h text checked against the local v0.28.4 CLI, the identity stop
  points (incl. "no release identity is published"), untagged pointer
  now expected.
- v0.28.4 values: identity key releases/v0.28.4.json (v0.28.2 stays;
  no v0.28.3 identity exists), floor min_release v0.28.4, floor_sha256
  1a6a7e50, signer grant on exactly that key, new --out paths, 5.0a
  promote-only of console 8d49199f, ship version f3bf1214, rollback
  7c1beb2d.
- P9: note on invite delivery (CARLOS_MAIL_REGION, PLATFORM_SUFFIX,
  CARLOS_HOST_CLAIM refuses the mailer).
- Blocker 7, open question 9, H11, inputs updated.
- Tag commit, published sums and CLI/router sha256 stay TO-CONFIRM
  until "v0.28.4 on dev".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 8d663a51b99fae987cd23a6b1452fe391c7f0286 parent ccaea17
6 files changed, +181 −165
  • docs/superpowers/notes/claim-trial-inputs/README.md +20 −16
  • docs/superpowers/notes/claim-trial-inputs/floor.json +1 −1
  • docs/superpowers/notes/claim-trial-inputs/identity.template.json +8 −8
  • docs/superpowers/notes/claim-trial-inputs/policy.template.json +2 −2
  • docs/superpowers/notes/claim-trial-inputs/signing-host.md +10 −9
  • docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md +140 −129

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	316.483s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	2.763s
ok  	github.com/carlosframework/platform/cmd/moveprobe	92.237s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.708s
ok  	github.com/carlosframework/platform/internal/activator	251.753s
ok  	github.com/carlosframework/platform/internal/agentsock	0.016s
ok  	github.com/carlosframework/platform/internal/alerting	0.004s
ok  	github.com/carlosframework/platform/internal/analytics	0.023s
ok  	github.com/carlosframework/platform/internal/appconfig	2.501s
ok  	github.com/carlosframework/platform/internal/apperr	0.073s
ok  	github.com/carlosframework/platform/internal/applog	0.123s
ok  	github.com/carlosframework/platform/internal/awsinv	0.004s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.004s
ok  	github.com/carlosframework/platform/internal/boxid	0.835s
?   	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.014s
ok  	github.com/carlosframework/platform/internal/certwarm	0.003s
ok  	github.com/carlosframework/platform/internal/cgroup	0.704s
ok  	github.com/carlosframework/platform/internal/changelog	0.318s
ok  	github.com/carlosframework/platform/internal/claimactivation	0.025s
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	408.069s
?   	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.924s
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.004s
ok  	github.com/carlosframework/platform/internal/docsite	0.005s
ok  	github.com/carlosframework/platform/internal/domains	2.375s
ok  	github.com/carlosframework/platform/internal/doorkey	0.006s
ok  	github.com/carlosframework/platform/internal/economics	0.007s
ok  	github.com/carlosframework/platform/internal/edge	45.110s
?   	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.135s
ok  	github.com/carlosframework/platform/internal/gate	1.657s
ok  	github.com/carlosframework/platform/internal/genselect	0.015s
ok  	github.com/carlosframework/platform/internal/geo	0.017s
ok  	github.com/carlosframework/platform/internal/geoip	0.033s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.155s
ok  	github.com/carlosframework/platform/internal/host	177.651s
ok  	github.com/carlosframework/platform/internal/hostactors	0.752s
ok  	github.com/carlosframework/platform/internal/hostbox	2.720s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.584s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.013s
ok  	github.com/carlosframework/platform/internal/hostexec	5.030s
ok  	github.com/carlosframework/platform/internal/hostfence	0.043s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.532s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.013s
ok  	github.com/carlosframework/platform/internal/hostguard	1.049s
ok  	github.com/carlosframework/platform/internal/hostops	1.649s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.013s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.015s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.460s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.140s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.013s
ok  	github.com/carlosframework/platform/internal/infraview	0.013s
ok  	github.com/carlosframework/platform/internal/instancectl	5.226s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.757s
ok  	github.com/carlosframework/platform/internal/instancefs	0.020s
ok  	github.com/carlosframework/platform/internal/instancekey	0.017s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.022s
ok  	github.com/carlosframework/platform/internal/layout	1.556s
ok  	github.com/carlosframework/platform/internal/lbhealth	0.013s
ok  	github.com/carlosframework/platform/internal/lease	9.826s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.010s
ok  	github.com/carlosframework/platform/internal/ledger	0.028s
ok  	github.com/carlosframework/platform/internal/mail	0.188s
ok  	github.com/carlosframework/platform/internal/maintenance	0.095s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.516s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.072s
ok  	github.com/carlosframework/platform/internal/metrics	0.008s
ok  	github.com/carlosframework/platform/internal/moves	21.205s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.805s
ok  	github.com/carlosframework/platform/internal/objstore	0.177s
ok  	github.com/carlosframework/platform/internal/ops	0.168s
ok  	github.com/carlosframework/platform/internal/park	0.202s
ok  	github.com/carlosframework/platform/internal/privatefile	0.004s
ok  	github.com/carlosframework/platform/internal/provision	37.728s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.007s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	5.444s
ok  	github.com/carlosframework/platform/internal/relay	7.243s
ok  	github.com/carlosframework/platform/internal/release	22.639s
ok  	github.com/carlosframework/platform/internal/rollout	3.882s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.004s
ok  	github.com/carlosframework/platform/internal/rootaws	0.008s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.058s
ok  	github.com/carlosframework/platform/internal/rootstate	0.026s
ok  	github.com/carlosframework/platform/internal/runs	0.579s
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.513s
ok  	github.com/carlosframework/platform/internal/seeds	0.010s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	9.963s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	4.604s
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.328s
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.004s
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.005s
ok  	github.com/carlosframework/platform/internal/version	0.001s
ok  	github.com/carlosframework/platform/internal/vet	1.587s
=== 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.025s
=== RUN   TestT4StorageProbeMinio
2026/10/18 20:48:47 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DFBAC3FD350DDD, 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 e246e0277..628e46d70 100644
--- a/docs/superpowers/notes/claim-trial-inputs/README.md
+++ b/docs/superpowers/notes/claim-trial-inputs/README.md
@@ -8,25 +8,27 @@ Timing: step 5.0 (box identity records) is done. Everything after it, from 5.1 (
Platform main at drafting: `b1118925` (region HA 2b). The box-record verb is on branch `box-publish-record` (`6040bb1c`), merging for v0.28.1.
-## Second signing session (v0.28.3)
+## Second signing session (v0.28.4)
-The first session (2026-10-18) did 5.0a, 5.1 and 6.1 on v0.28.2, then stopped at 6.2: `guard-arm` checks both `carlos` and `carlos.prev` against the identity, and leo's `.prev` was v0.28.1. The fix, `carlos system install-channel --policy`, is on main `50f5b268` and ships in v0.28.3 (gap 5 below). The runbook's Go package, 5.0a, "5.1 in the second session" and step 6 carry the v0.28.3 procedure. Its inputs:
+The first session (2026-10-18) did 5.0a, 5.1 and 6.1 on v0.28.2, then stopped at 6.2: `guard-arm` checks both `carlos` and `carlos.prev` against the identity, and leo's `.prev` was v0.28.1. The fix, `carlos system install-channel --policy`, shipped in v0.28.3 (`50f5b268`, gap 5), which was published and rolled to dev. Its pointers carry no `release_tag`, and v0.28.3's `install-channel --policy` needed one (gap 6). Paul chose option (b): `install-channel --policy` takes a required `--identity FILE` (main `f3bf1214`), in v0.28.4. The runbook's Go package, 5.0a, "5.1 in the second session" and step 6 carry the v0.28.4 procedure. Its inputs:
| Input | Value | Status |
|---|---|---|
-| Release | v0.28.3, tag on main `50f5b268` (forge `v0.28.3^{}`) | published; on leo, sophie and dennis (`.prev` v0.28.2), confirmed read-only 2026-10-18 |
-| Router `carlos-linux-arm64` sha256 | `6154afb458a35183e470f5480ea08c09817400e6e67465530477e5204af2def0`, from v0.28.3's signed `SHA256SUMS.txt`, equal to the local build and to dev's `stable.json` and `canary.json` | confirmed 2026-10-18 |
-| CLI `carlos-linux-amd64` sha256 | `82a60203b1790771b5f3da16bc5d342327131ed30a1aae59a520f42d253369b2`, from the same sums, equal to the local build | confirmed 2026-10-18 |
-| `SHA256SUMS.txt` and `.sig` | `SHA256SUMS.txt` 704 bytes, sha256 `0a0e5e9287b5f9560c26b20771d8106b12d77668cc40e5d9c0786f35ca3b1038`, naming `carlos_0.28.3_amd64.deb` and `carlos_0.28.3_arm64.deb` and no other deb; `.sig` 89 bytes, sha256 `38cd1cf78075cd8284b9df803ae9d43776ee135413ae3a4641d1cbd29abfa476`; signature verified with the README's pinned key; carried verbatim by `publish-identity` | confirmed 2026-10-18 |
-| Console build | `~/.cache/release-v0.28.3/console-linux-arm64`, 47055010 bytes, sha256 `3660b44c13080f0e9ead5f70754a11ec267d70eaab2a89e88ce256ab3437ec74`; stripped aarch64, `cmd/console`, `-ldflags="-s -w -X github.com/carlosframework/platform/internal/version.Build=v0.28.3"` once (checked with `file` and `go version -m`, never run). Never rebuild it | confirmed (built, not shipped) |
-| Console ship version | `50f5b268`, the short sha of v0.28.3's commit; `apps/bab/console/releases/50f5b268/` absent | confirmed 2026-10-18 |
+| Release | v0.28.4, built on DOCS-T1 from main `f3bf1214` (also carries console invite delivery, `91a651b3`) | built; not published or rolled on 2026-10-18 |
+| Tag commit | expected `f3bf1214` | TO-CONFIRM until platform-1e posts "v0.28.4 on dev" |
+| Router `carlos-linux-arm64` sha256 | from v0.28.4's signed `SHA256SUMS.txt`; the local build is `736826e40d5c1de399678e8bf82bbd15c0a5123f766089d5b42d0539f2ea4b21` | TO-CONFIRM; a published value that differs is a stop |
+| CLI `carlos-linux-amd64` sha256 | from the same sums; local build `2d53162fa28accc724a0571f8c7ff59ca36c9050201446e3f33faf4a6f608548` | TO-CONFIRM; a published value that differs is a stop |
+| `SHA256SUMS.txt` and `.sig` | v0.28.4's own, naming `carlos_0.28.4_amd64.deb` and `carlos_0.28.4_arm64.deb` and no other deb; signature verified with the README's pinned key; carried verbatim by `publish-identity` | TO-CONFIRM |
+| Console build | `~/.cache/release-v0.28.4/console-linux-arm64`, 47186082 bytes, sha256 `8d49199f433662ab183a98ce7a5ebeb18f36bc02af5689752580a596bc565e9b`; stripped aarch64, `cmd/console`, `-ldflags="-s -w -X github.com/carlosframework/platform/internal/version.Build=v0.28.4"` once (checked with `file` and `go version -m`, never run). Never rebuild it | confirmed (built, not shipped) |
+| Console ship version | `f3bf1214`, the build's source commit; `apps/bab/console/releases/f3bf1214/` absent | confirmed against the build; must equal the tag commit |
| Pointer digests for `bab` | on since 2026-10-18T10:11:25Z (first 5.0a); not changed again | confirmed |
-| Floor | `{"min": 1, "min_release": "v0.28.3"}`; expected `floor_sha256` `07d2b63e711af4d97fad48713096447ce3c81ff92db0e956a98543478484aa09` (sha256 of the compact form) | confirmed; `sign-policy`'s output is the value of record |
-| Identity | `control/system/router/releases/v0.28.3.json`, new and create-only. The v0.28.2 identity (`v0.28.2.json`, `2d22f05f`) stays as it is, unused | to publish in 6.1 |
-| `policy` and `identity` files | `/root/claim-trial/policy.v0.28.3.json` and `/root/claim-trial/identity.v0.28.3.json` on the signer, leo and sophie, new paths. The first session's `policy.json` (`dfac87e6`) and `identity.json` (`2d22f05f`) stay staged on both boxes, unused | to make in 6.1 |
-| Signer write grant | exactly `control/system/router/releases/v0.28.3.json`, no prefix (`signing-host.md`) | to create, after platform-1e confirms with Paul |
-| Roster | revision 5, etag `f3717d8d67f496ffd9525704c7a690b0`, resumed with `refresh`, never re-initialized | confirmed read-only 2026-10-18 14:30Z |
-| Channel pointers | dev's `stable.json` and `canary.json` name v0.28.3 with no `release_tag`, which `install-channel --policy` needs (runbook Go package, "Open") | STOP, open: platform-1e rules before the session |
+| Floor | `{"min": 1, "min_release": "v0.28.4"}` (`floor.json` `881acf53…37a9`); expected `floor_sha256` `1a6a7e50f1081e1fdebe07026a9ad1cd0b27e9e36851176c5f510185832a4ab3` (sha256 of the compact form) | confirmed; `sign-policy`'s output is the value of record |
+| Identity | `control/system/router/releases/v0.28.4.json`, new and create-only. The v0.28.2 identity (`v0.28.2.json`, `2d22f05f`) stays as it is, unused; no v0.28.3 identity was published | to publish in 6.1 |
+| `policy` and `identity` files | `/root/claim-trial/policy.v0.28.4.json` and `/root/claim-trial/identity.v0.28.4.json` on the signer, leo and sophie, new paths. The first session's `policy.json` (`dfac87e6`) and `identity.json` (`2d22f05f`) stay staged on both boxes, unused | to make in 6.1 |
+| 6.1a | `install-channel --policy /root/claim-trial/policy.v0.28.4.json --identity /root/claim-trial/identity.v0.28.4.json`; help checked against the local v0.28.4 CLI | ready |
+| Signer write grant | exactly `control/system/router/releases/v0.28.4.json`, no prefix (`signing-host.md`) | to create, after platform-1e confirms with Paul |
+| Roster | revision 5, etag `f3717d8d67f496ffd9525704c7a690b0`, resumed with `refresh`, never re-initialized | confirmed read-only 2026-10-18 |
+| Channel pointers | name the release with no `release_tag` before a floor is published | expected: `--identity` replaces the tag (gap 6) |
## Files
@@ -57,7 +59,7 @@ The first session (2026-10-18) did 5.0a, 5.1 and 6.1 on v0.28.2, then stopped at
| Console ID | `dev-console` | platform-1e ruling 2026-10-18 | confirmed |
| Console enrollment | 32 lowercase hex characters | `hostactors` token rule `^[0-9a-f]{32}$`; chosen once at approval and kept | TO-CONFIRM (generated at approval) |
| Console deployment bucket | `carlos-dev-640012953448` | dev deployment bucket | confirmed |
-| Floor | `{"min": 1, "min_release": "v0.28.3"}` (`floor.json`, sha256 `c7063389…303f`; was v0.28.2 in the first session) | `moves.ProtocolVersion` is `1` on main; v0.28.3 is the first release with `install-channel --policy` (gap 5), which arming an updated box needs | confirmed for the second session; no floor is published yet, so the move is free |
+| Floor | `{"min": 1, "min_release": "v0.28.4"}` (`floor.json`, sha256 `881acf53…37a9`; was v0.28.2 in the first session) | `moves.ProtocolVersion` is `1` on main; v0.28.4 is the first release with `install-channel --policy --identity` (gaps 5 and 6), which arming an updated box over an untagged pointer needs | confirmed for the second session; no floor is published yet, so the move is free |
| Home region | `ie` | step 7 | confirmed |
| Release public key | the dev deployment root key, fingerprint `960d03ff` (sha256 of the host.env value) | `CARLOS_RELEASE_PUBKEY` on every dev box | confirmed (public; copy from a box) |
@@ -104,7 +106,9 @@ platform-1e confirmed gaps 1 and 2 on main on 2026-10-18 and built the fixes, wi
3. **The console needs a root-owned path and a `.prev`.** No hand-made files. Fix: (c) above, at the fixed path `/opt/carlos/system/console/console`.
4. **The console's channel pointer has no digests, and the console is stamped with the short sha.** Found reviewing the branch. `publish-identity` takes the console's sha256 only through the `canary/console` pointer's signed manifest digest, and refuses a console whose `version.Build` is not the release tag. Fix: runbook 5.0a (digests on for `bab`, then a fresh promote of a console built with `version.Build=v0.28.2`), with its own Paul's go, outside any roll.
-5. **An updated box's `carlos.prev` is the release before.** Found at the first session's 6.2 stop. `guard-arm` needs the identity's release in both `/opt/carlos/carlos` and `carlos.prev`, and nothing put it in `.prev`. Fix: `carlos system install-channel --policy FILE` on main `50f5b268` (branch `claim-prev-rehearsal`, census ruled, copy approved), in v0.28.3: runbook step 6.1a, on leo and on sophie, before `guard-arm`.
+5. **An updated box's `carlos.prev` is the release before.** Found at the first session's 6.2 stop. `guard-arm` needs the identity's release in both `/opt/carlos/carlos` and `carlos.prev`, and nothing put it in `.prev`. Fix: `carlos system install-channel --policy FILE` on main `50f5b268` (branch `claim-prev-rehearsal`, census ruled, copy approved), first in v0.28.3 and, with gap 6's `--identity`, in v0.28.4: runbook step 6.1a, on leo and on sophie, before `guard-arm`.
+
+6. **The channel pointers carry no release tag before a floor is published.** Found after the v0.28.3 roll: `carlos system update` writes `stable.json` and `canary.json` without `release_tag`, and v0.28.3's `install-channel --policy` found the identity through that tag. Fix (Paul's option (b)): `install-channel --policy` takes a required `--identity FILE` on main `f3bf1214` (branch `claim-untagged-pointer`, census ruled, nine strings approved), in v0.28.4. The file is checked against the published identity and the release key, the pointer's artefact sha256 must equal the identity's, a tag on the pointer must name it, and the pointer is not changed.
## Open items
diff --git a/docs/superpowers/notes/claim-trial-inputs/floor.json b/docs/superpowers/notes/claim-trial-inputs/floor.json
index 172011317..7f9196527 100644
--- a/docs/superpowers/notes/claim-trial-inputs/floor.json
+++ b/docs/superpowers/notes/claim-trial-inputs/floor.json
@@ -1 +1 @@
-{"min": 1, "min_release": "v0.28.3"}
+{"min": 1, "min_release": "v0.28.4"}
diff --git a/docs/superpowers/notes/claim-trial-inputs/identity.template.json b/docs/superpowers/notes/claim-trial-inputs/identity.template.json
index d3aacc24d..31d7c37e0 100644
--- a/docs/superpowers/notes/claim-trial-inputs/identity.template.json
+++ b/docs/superpowers/notes/claim-trial-inputs/identity.template.json
@@ -4,24 +4,24 @@
"component": "router",
"channel": "stable",
"current": {
- "version": "v0.28.3",
- "release_tag": "v0.28.3",
+ "version": "v0.28.4",
+ "release_tag": "v0.28.4",
"moves_protocol": 1,
"artifacts": {
"linux-arm64": {
- "key": "bootstrap/router/v0.28.3/carlos-linux-arm64",
- "sha256": "6154afb458a35183e470f5480ea08c09817400e6e67465530477e5204af2def0"
+ "key": "bootstrap/router/v0.28.4/carlos-linux-arm64",
+ "sha256": "TO-CONFIRM: the carlos-linux-arm64 line of v0.28.4's signed SHA256SUMS.txt; the local build is 736826e40d5c1de399678e8bf82bbd15c0a5123f766089d5b42d0539f2ea4b21"
}
},
"console_artifacts": {
"linux-arm64": {
- "key": "bootstrap/console/v0.28.3/carlos-linux-arm64",
- "sha256": "3660b44c13080f0e9ead5f70754a11ec267d70eaab2a89e88ce256ab3437ec74"
+ "key": "bootstrap/console/v0.28.4/carlos-linux-arm64",
+ "sha256": "8d49199f433662ab183a98ce7a5ebeb18f36bc02af5689752580a596bc565e9b"
}
},
- "source": "release:v0.28.3"
+ "source": "release:v0.28.4"
},
"spread": "0s"
},
- "note": "Shape only, for the second session: v0.28.3, tag on 50f5b268, published and verified 2026-10-18 (SHA256SUMS.txt 0a0e5e92, signature verified). 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.3.json and writes byte-exact to --out (/root/claim-trial/identity.v0.28.3.json). The v0.28.2 identity at control/system/router/releases/v0.28.2.json stays as it is, unused. The channel is always stable and is read by nothing that checks an identity. No sha256 is typed: publish-identity reads both. The values shown are the expected ones (2026-10-18): the router from v0.28.3's signed SHA256SUMS.txt, the console from the prebuilt file runbook 5.0a ships"
+ "note": "Shape only, for the second session: v0.28.4, built from main f3bf1214, not yet published on 2026-10-18. 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.4.json and writes byte-exact to --out (/root/claim-trial/identity.v0.28.4.json). The v0.28.2 identity at control/system/router/releases/v0.28.2.json stays as it is, unused; no v0.28.3 identity was published. install-channel --policy --identity (6.1a) and guard-arm read this file. The channel is always stable and is read by nothing that checks an identity. No sha256 is typed: publish-identity reads both. The values shown are the expected ones (2026-10-18): the router from v0.28.4's signed SHA256SUMS.txt, the console from the prebuilt file runbook 5.0a ships"
}
diff --git a/docs/superpowers/notes/claim-trial-inputs/policy.template.json b/docs/superpowers/notes/claim-trial-inputs/policy.template.json
index 0001e2886..a6f06f00c 100644
--- a/docs/superpowers/notes/claim-trial-inputs/policy.template.json
+++ b/docs/superpowers/notes/claim-trial-inputs/policy.template.json
@@ -4,7 +4,7 @@
"mode": "armed",
"floor": {
"min": 1,
- "min_release": "v0.28.3"
+ "min_release": "v0.28.4"
},
- "authorization": "On main 7c1beb2d (v0.28.2) and 50f5b268 (v0.28.3): 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. For the second session (v0.28.3) the floor is min_release v0.28.3, so the bytes differ from the first session's policy.json; --out /root/claim-trial/policy.v0.28.3.json"
+ "authorization": "On main 7c1beb2d (v0.28.2) and f3bf1214 (v0.28.4): 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. For the second session (v0.28.4) the floor is min_release v0.28.4, so the bytes differ from the first session's policy.json; --out /root/claim-trial/policy.v0.28.4.json"
}
diff --git a/docs/superpowers/notes/claim-trial-inputs/signing-host.md b/docs/superpowers/notes/claim-trial-inputs/signing-host.md
index 2c175fa26..4690489e4 100644
--- a/docs/superpowers/notes/claim-trial-inputs/signing-host.md
+++ b/docs/superpowers/notes/claim-trial-inputs/signing-host.md
@@ -39,7 +39,7 @@ The signer reuses the AMI, type, subnet and launch options. It gets its **own**
| Access | SSM Session Manager only. No SSH key, no inbound port |
| Metadata | IMDSv2 required, hop limit 1 |
| Storage | default root volume, deleted on termination. No swap (Debian cloud default), so a key in `/run` stays in memory |
-| Lifetime | first created after v0.28.2 was on dev, for a single signing session covering runbook 5.1, 6 and 7 (Paul's ruling, 2026-10-18); that session stopped at 6.2 and was torn down. Created a second time for the v0.28.3 session, after v0.28.3 is on dev and 5.0a has passed, once platform-1e has confirmed with Paul. Nothing else touches the signer during a session. Torn down at its end, or at any stop |
+| Lifetime | first created after v0.28.2 was on dev, for a single signing session covering runbook 5.1, 6 and 7 (Paul's ruling, 2026-10-18); that session stopped at 6.2 and was torn down. Created a second time for the v0.28.4 session, after v0.28.4 is on dev and 5.0a has passed, once platform-1e has confirmed with Paul. Nothing else touches the signer during a session. Torn down at its end, or at any stop |
| Tags | `Name=carlos-claim-signer`, `purpose=claim-signer` |
## IAM role `carlos-claim-signer`
@@ -61,7 +61,7 @@ Trust: `ec2.amazonaws.com`. Managed policy: `AmazonSSMManagedInstanceCore` (Sess
{"Sid": "ReadConsoleRelease", "Effect": "Allow", "Action": "s3:GetObject",
"Resource": "arn:aws:s3:::carlos-dev-640012953448/apps/bab/console/*"},
{"Sid": "WriteIdentity", "Effect": "Allow", "Action": "s3:PutObject",
- "Resource": "arn:aws:s3:::carlos-dev-640012953448/control/system/router/releases/v0.28.3.json"},
+ "Resource": "arn:aws:s3:::carlos-dev-640012953448/control/system/router/releases/v0.28.4.json"},
{"Sid": "WriteRosterAndFloor", "Effect": "Allow", "Action": "s3:PutObject",
"Resource": ["arn:aws:s3:::carlos-dev-640012953448/control/host-actors.json",
"arn:aws:s3:::carlos-dev-640012953448/control/host-actor-invitations/*",
@@ -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; 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.3.json` (second session; the first session's was `v0.28.2.json`, now published and permanent), 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 one 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.
+- **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.
@@ -101,11 +101,12 @@ The key goes from the SSM response straight into a `0600` file on tmpfs (`/run`)
Known values (2026-10-18):
- carlos v0.28.1 `carlos-linux-arm64`: `2ae174da29b1d10fdcb009172ba830db5460b62e838bcecc7d9e17535f35adea`. It comes from the published release's `SHA256SUMS.txt`, whose signature verified with the README's pinned key (`openssl pkeyutl -verify`: "Signature Verified Successfully"). It is the same binary leo and sophie run.
- jq 1.7.1 `jq-linux-arm64`: `4dd2d8a0661df0b22f1bb9a1f9830f06b6f3b8f7d91211a1ef5d7c4f06a8b4a5` (1709616 bytes). It was computed on DOCS-T1 from the file downloaded from `github.com/jqlang/jq/releases/download/jq-1.7.1/`, and matches that release's `sha256sum.txt`.
-- carlos v0.28.3 `carlos-linux-arm64` (the second session's CLI): `6154afb458a35183e470f5480ea08c09817400e6e67465530477e5204af2def0`, from v0.28.3's `SHA256SUMS.txt` (sha256 `0a0e5e9287b5f9560c26b20771d8106b12d77668cc40e5d9c0786f35ca3b1038`; `.sig` `38cd1cf78075cd8284b9df803ae9d43776ee135413ae3a4641d1cbd29abfa476`), whose signature verified with the README's pinned key on 2026-10-18. It equals the local build and is the binary leo, sophie and dennis run.
+- carlos v0.28.4 `carlos-linux-arm64` (the second session's CLI): **TO-CONFIRM** from v0.28.4's signed `SHA256SUMS.txt`, verified the same way, until platform-1e posts "v0.28.4 on dev". The local build (`~/.cache/release-v0.28.4/dist/carlos-linux-arm64`, from main `f3bf1214`) is `736826e40d5c1de399678e8bf82bbd15c0a5123f766089d5b42d0539f2ea4b21`; a published value that differs is a stop.
+- carlos v0.28.3 `carlos-linux-arm64` (published, not used by the trial): `6154afb458a35183e470f5480ea08c09817400e6e67465530477e5204af2def0`, from v0.28.3's `SHA256SUMS.txt` (sha256 `0a0e5e9287b5f9560c26b20771d8106b12d77668cc40e5d9c0786f35ca3b1038`; `.sig` `38cd1cf78075cd8284b9df803ae9d43776ee135413ae3a4641d1cbd29abfa476`), whose signature verified with the README's pinned key on 2026-10-18. It equals the local build and is the binary leo, sophie and dennis run.
- carlos v0.28.2 `carlos-linux-arm64` (the first session's CLI): `7141e037d61b0d95ae554252af1bb9155653a9954ad0dcaffb35c7047850f492`, from v0.28.2's `SHA256SUMS.txt` (sha256 `9c29b3d305c464cf981afffa0bc51f1654015883c02717477f38357709d46db2`), whose signature verified with the README's pinned key on 2026-10-18 ("Signature Verified Successfully", `sha256sum -c` OK). It is the binary leo, sophie and dennis run.
On DOCS-T1, before launching:
-1. Download the carlos CLI `carlos-linux-arm64` (v0.28.3 for the second session: 6.1a's `install-channel --policy` is first in v0.28.3, and the identity and floor are v0.28.3's), with the release's `SHA256SUMS.txt` and `SHA256SUMS.txt.sig`, from the public release. Verify the signature as the README's "Releasing the platform" verification does, then `sha256sum -c`. Record the sha256. Keep both sums files exactly as downloaded: step 6.1's `publish-identity` plan carries them verbatim.
+1. Download the carlos CLI `carlos-linux-arm64` (v0.28.4 for the second session: 6.1a's `install-channel --policy --identity` is first in v0.28.4, and the identity and floor are v0.28.4's), with the release's `SHA256SUMS.txt` and `SHA256SUMS.txt.sig`, from the public release. Verify the signature as the README's "Releasing the platform" verification does, then `sha256sum -c`. Record the sha256. Keep both sums files exactly as downloaded: step 6.1's `publish-identity` plan carries them verbatim.
2. Download jq 1.7.1 `jq-linux-arm64` and the release's `sha256sum.txt` from `github.com/jqlang/jq/releases/tag/jq-1.7.1`, then check it with `sha256sum -c`. Record the sha256.
3. Upload the carlos CLI, jq, `SHA256SUMS.txt` and `SHA256SUMS.txt.sig` to `s3://carlos-dev-640012953448/claim-signer/<run-id>/` with `aws s3 cp`, which copies bytes unchanged.
@@ -114,13 +115,13 @@ On the signer, as root:
```bash
aws s3 cp s3://carlos-dev-640012953448/claim-signer/<run-id>/carlos-linux-arm64 /usr/local/bin/carlos
aws s3 cp s3://carlos-dev-640012953448/claim-signer/<run-id>/jq-linux-arm64 /usr/local/bin/jq
-printf '%s %s\n' 6154afb458a35183e470f5480ea08c09817400e6e67465530477e5204af2def0 /usr/local/bin/carlos \
+printf '%s %s\n' <v0.28.4 carlos-linux-arm64 sha256, TO-CONFIRM> /usr/local/bin/carlos \
4dd2d8a0661df0b22f1bb9a1f9830f06b6f3b8f7d91211a1ef5d7c4f06a8b4a5 /usr/local/bin/jq | sha256sum -c
chmod 0755 /usr/local/bin/carlos /usr/local/bin/jq
/usr/local/bin/carlos version
```
-Expected: both `OK`, then `carlos v0.28.3`. The v0.28.1 and v0.28.2 values above are kept for reference only.
+Expected: both `OK`, then `carlos v0.28.4`. The v0.28.1, v0.28.2 and v0.28.3 values above are kept for reference only.
## Create (Paul's go: opening the signing session)
@@ -137,10 +138,10 @@ Paul approves the signer host and its role policy (including the exact identity
}
```
-`signer-policy.json`: the inline policy `claim-signer` exactly as in "IAM role `carlos-claim-signer`" above, with `ReadConsoleRelease` on `apps/bab/console/*` and `WriteIdentity` on exactly `control/system/router/releases/v0.28.3.json`. Check it before use:
+`signer-policy.json`: the inline policy `claim-signer` exactly as in "IAM role `carlos-claim-signer`" above, with `ReadConsoleRelease` on `apps/bab/console/*` and `WriteIdentity` on exactly `control/system/router/releases/v0.28.4.json`. Check it before use:
```bash
-python3 -c 'import json,sys;d=json.load(open("signer-policy.json"));w=[s for s in d["Statement"] if s["Sid"]=="WriteIdentity"][0]["Resource"];assert w=="arn:aws:s3:::carlos-dev-640012953448/control/system/router/releases/v0.28.3.json", w;print("ok")'
+python3 -c 'import json,sys;d=json.load(open("signer-policy.json"));w=[s for s in d["Statement"] if s["Sid"]=="WriteIdentity"][0]["Resource"];assert w=="arn:aws:s3:::carlos-dev-640012953448/control/system/router/releases/v0.28.4.json", w;print("ok")'
```
`signer-user-data.sh`, the root runner's (Debian ships no SSM agent):
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 2029cdc3d..b11ee48f2 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,47 +1,43 @@
# 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`, and re-checked against main at `7c1beb2d` after it merged (published (v0.28.2, tag on 7c1beb2d)). Revised again on 2026-10-18 for the second signing session on v0.28.3, against main at `50f5b268` (`install-channel --policy`, from branch `claim-prev-rehearsal`) and the root-runner rehearsal of steps 6 and 7. 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.
-
-## Go package (for Paul): the v0.28.3 session
-
-Final, 2026-10-18. Every value below was read or verified read-only on DOCS-T1 between 15:30Z and 15:50Z; nothing was changed on any box or bucket. The first session's package (v0.28.2) is in this file's history at `72b5ab3e`.
-
-**Where things stand.** The first signing session (2026-10-18) did 5.0a (digests on for `bab`, console `7c1beb2d`), 5.1 (roster revision 5: leo in pool `cohort1`, sophie in pool `console`, `dev-console` enrolled) and 6.1 (the v0.28.2 identity published). It stopped at 6.2, because `guard-arm` checks both `/opt/carlos/carlos` and `carlos.prev` against the identity and leo's `.prev` was v0.28.1, and the session was torn down (CloudTrail clean). The fix, `carlos system install-channel --policy <policy.json>`, is on main as `50f5b268` and is in v0.28.3, now published and rolled to dev's boxes (platform-1e, "v0.28.3 on dev", verified below). The dev console is still on `7c1beb2d`, waiting for 5.0a. The roster is at revision 5 (etag `f3717d8d67f496ffd9525704c7a690b0`), no floor is published (`control/moves-protocol.json` 404), `control/system/router/releases/v0.28.3.json` does not exist, and nothing is under `claim-signer/`.
-
-**One STOP is open: the channel pointers carry no release tag** (found 15:40Z, read-only; details under "Open" below). As the code stands, step 6.1a refuses on both boxes. Do not open the session until platform-1e rules on it.
-
-**v0.28.3 values (all confirmed)**
-- Tag: `v0.28.3` on `50f5b26845fb0a23109078654ec5d59b3723d9c4`. `git ls-remote --tags` on the platform forge: tag object `0f2955fa…`, peeled `v0.28.3^{}` = `50f5b268…`. The releases repo has its own `v0.28.3` tag (`9b84ff9f…`), a commit in that repo.
-- `SHA256SUMS.txt`: sha256 `0a0e5e9287b5f9560c26b20771d8106b12d77668cc40e5d9c0786f35ca3b1038`, 704 bytes, ends in a newline; it lists `carlos_0.28.3_amd64.deb` and `carlos_0.28.3_arm64.deb` and no other deb. `SHA256SUMS.txt.sig`: sha256 `38cd1cf78075cd8284b9df803ae9d43776ee135413ae3a4641d1cbd29abfa476`, 89 bytes. Both downloaded from `https://github.com/carlosframework/releases/releases/download/v0.28.3/`; `openssl pkeyutl -verify` with the README's pinned key printed "Signature Verified Successfully". `publish-identity` carries both files verbatim.
-- carlos `linux-arm64` (the router artefact, the signer's CLI and the box binary): `6154afb458a35183e470f5480ea08c09817400e6e67465530477e5204af2def0`, from the signed sums. It equals the local build, the `artifacts.linux-arm64` of dev's signed `stable.json` and `canary.json` (key `bootstrap/router/v0.28.3/carlos-linux-arm64`), and `/opt/carlos/carlos` on leo, sophie and dennis.
-- carlos `linux-amd64` (the operator CLI for 5.0a): `82a60203b1790771b5f3da16bc5d342327131ed30a1aae59a520f42d253369b2`, from the signed sums, equal to the local build. `sha256sum -c --ignore-missing` against the local `dist/` passed for every listed file.
-- Console: `~/.cache/release-v0.28.3/console-linux-arm64`, 47055010 bytes, sha256 `3660b44c13080f0e9ead5f70754a11ec267d70eaab2a89e88ce256ab3437ec74`. Checked without running it: `file` says stripped aarch64; `go version -m` shows `path github.com/carlosframework/platform/cmd/console` and `-ldflags="-s -w -X github.com/carlosframework/platform/internal/version.Build=v0.28.3"`, the stamp exactly once; its sha256 is not in v0.28.3's sums (count `0`). Never rebuild it.
-- Console ship version `CV`: `50f5b268`, the tag's commit. `apps/bab/console/releases/50f5b268/` does not exist yet, so the ship is new.
-- Identity key: `control/system/router/releases/v0.28.3.json`, a new create-only key. The v0.28.2 identity stays at `control/system/router/releases/v0.28.2.json` (1076 bytes, `2d22f05f`) exactly as it is: permanent, unused, never deleted or rewritten. Expected identity: `version` and `release_tag` `v0.28.3`, `moves_protocol` `1`, router `bootstrap/router/v0.28.3/carlos-linux-arm64` `6154afb4…def0`, console `bootstrap/console/v0.28.3/carlos-linux-arm64` `3660b44c…ec74`.
-- Floor: `{"min": 1, "min_release": "v0.28.3"}` (`floor.json`, sha256 `c7063389c0bd78a7cd7b400cf6a548cf527320fd562dd6c26ded1a0ecc0f303f`). Expected `floor_sha256` `07d2b63e711af4d97fad48713096447ce3c81ff92db0e956a98543478484aa09`, the sha256 of the compact form (the same rule gives v0.28.2's `3a361ab8…`). `sign-policy`'s output is the value of record.
-- Policy and identity files: new bytes, new paths, `/root/claim-trial/policy.v0.28.3.json` and `/root/claim-trial/identity.v0.28.3.json` on the signer, leo and sophie. The first session's `policy.json` (`dfac87e6`) and `identity.json` (`2d22f05f`) stay staged on leo and sophie, unused.
-- Signer: write grant on exactly `control/system/router/releases/v0.28.3.json`, no prefix; CLI carlos v0.28.3 `linux-arm64` `6154afb4…def0`; jq 1.7.1 `4dd2d8a0…b4a5`.
+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 (published (v0.28.2, tag on 7c1beb2d)). Revised again on 2026-10-18 for the second signing session on v0.28.3, against main at `50f5b268` (`install-channel --policy`, from branch `claim-prev-rehearsal`) and the root-runner rehearsal of steps 6 and 7. Moved to v0.28.4 the same day, against main at `f3bf1214` (`install-channel --policy --identity`, from branch `claim-untagged-pointer`). 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.
+
+## Go package (for Paul): the v0.28.4 session
+
+Prepared on 2026-10-18 for platform-1e to present. The values below were read read-only on DOCS-T1; nothing was changed on any box or bucket. The v0.28.3 package is in this file's history at `ccaea17f`, the v0.28.2 one at `72b5ab3e`.
+
+**Where things stand.** The first signing session (2026-10-18) did 5.0a (digests on for `bab`, console `7c1beb2d`), 5.1 (roster revision 5: leo in pool `cohort1`, sophie in pool `console`, `dev-console` enrolled) and 6.1 (the v0.28.2 identity published). It stopped at 6.2 on leo's v0.28.1 `carlos.prev`, and was torn down (CloudTrail clean). v0.28.3 (`50f5b268`, `install-channel --policy`) was published and rolled to dev's boxes, but its pointers carry no `release_tag`, and v0.28.3's `install-channel --policy` would have refused them (blocker 7). Paul chose option (b): `install-channel --policy` gains a required `--identity FILE` (main `f3bf1214`). v0.28.4 is built from `f3bf1214` and also carries platform-87's console invite delivery (`91a651b3`). It is not yet published or rolled. Read on 2026-10-18: leo, sophie and dennis run v0.28.3 (`.prev` v0.28.2); the dev console runs `7c1beb2d`; the roster is at revision 5 (etag `f3717d8d67f496ffd9525704c7a690b0`); no floor is published; `control/system/router/releases/` holds only `v0.28.2.json`; `apps/bab/console/releases/` has no `50f5b268` or `f3bf1214`.
+
+**v0.28.4 values**
+- Release: v0.28.4, built on DOCS-T1 from main `f3bf1214` (`~/.cache/release-v0.28.4/src` at `f3bf1214ab92a47ecb626400e7bd1b35cd7a112c`, main's tip). Tag commit: **TO-CONFIRM** (the forge has no v0.28.4 tag yet; it should be `f3bf1214`).
+- carlos `linux-arm64` (router artefact, signer CLI, box binary): **TO-CONFIRM** from v0.28.4's signed `SHA256SUMS.txt`. Local build: `736826e40d5c1de399678e8bf82bbd15c0a5123f766089d5b42d0539f2ea4b21`.
+- carlos `linux-amd64` (operator CLI for 5.0a): **TO-CONFIRM**. Local build: `2d53162fa28accc724a0571f8c7ff59ca36c9050201446e3f33faf4a6f608548`.
+- `SHA256SUMS.txt` and `SHA256SUMS.txt.sig`: **TO-CONFIRM**, sha256 of each, signature verified with the README's pinned key. The list must name `carlos_0.28.4_amd64.deb` and `carlos_0.28.4_arm64.deb` and no other `carlos_*.deb`. Any published sha256 that differs from the local build stops the release block.
+- Console: `~/.cache/release-v0.28.4/console-linux-arm64`, 47186082 bytes, sha256 `8d49199f433662ab183a98ce7a5ebeb18f36bc02af5689752580a596bc565e9b`. Checked without running it: `file` says stripped aarch64; `go version -m` shows `path github.com/carlosframework/platform/cmd/console` and `-ldflags="-s -w -X github.com/carlosframework/platform/internal/version.Build=v0.28.4"`, the stamp exactly once. Never rebuild it.
+- Console ship version `CV`: `f3bf1214` (the build's source commit; must equal the tag's commit). `apps/bab/console/releases/f3bf1214/` does not exist, so the ship is new.
+- Identity key: `control/system/router/releases/v0.28.4.json`, new and create-only. The v0.28.2 identity (`v0.28.2.json`, `2d22f05f`) stays as it is, unused. No v0.28.3 identity exists; none is published now, and one that ever appears is left as it is.
+- Floor: `{"min": 1, "min_release": "v0.28.4"}` (`floor.json`, sha256 `881acf53c6f3753c685656bba62f632ed29a2fff75be476c58df93737a6b37a9`). Expected `floor_sha256` `1a6a7e50f1081e1fdebe07026a9ad1cd0b27e9e36851176c5f510185832a4ab3`, the sha256 of the compact `{"min":1,"min_release":"v0.28.4"}` (the same rule gives v0.28.2's `3a361ab8…`). `sign-policy`'s output is the value of record.
+- Policy and identity files: `/root/claim-trial/policy.v0.28.4.json` and `/root/claim-trial/identity.v0.28.4.json`, new paths on the signer, leo and sophie, each checked absent before `box_put`. The first session's `policy.json` (`dfac87e6`) and `identity.json` (`2d22f05f`) stay staged on leo and sophie, unused.
+- 6.1a: `install-channel --policy /root/claim-trial/policy.v0.28.4.json --identity /root/claim-trial/identity.v0.28.4.json`. Its `-h` output, checked against the local v0.28.4 CLI, is quoted in 6.1a. The `claims` help is unchanged.
+- Signer: write grant on exactly `control/system/router/releases/v0.28.4.json`, no prefix; CLI carlos v0.28.4 `linux-arm64` (TO-CONFIRM above); jq 1.7.1 `4dd2d8a0…b4a5`.
- Console rollback version: `7c1beb2d`, promoted with digests on.
-**State read today (read-only, over SSM)**
-- Leo and sophie: `carlos v0.28.3` (`6154afb4`), `.prev` `carlos v0.28.2` (`7141e037`); `applied.json` names v0.28.3 (leo `stable`, sophie `canary`); edge and instance-control active since the roll (leo 15:44:47Z, sophie 15:43:57Z); no `host-actor.json`, guard, `channel-identity.json` or `/opt/carlos/system/console`. Staged from 5.1, unchanged: leo `release.pub` `960d03ff`, `leo-climbing-enrollment.json` `800b181f`; sophie `release.pub` `960d03ff`, `sophie-knitting-enrollment.json` `85380180`, `console-id` `cfacdf55`; both `policy.json` `dfac87e6`, `identity.json` `2d22f05f`.
-- Dennis (the 7.1 check): `Success`, `absent /opt/carlos/system/guard/policy.json`, `absent /etc/carlos/host-actor.json`, `carlos v0.28.3`; `.prev` `carlos v0.28.2` (`7141e037`). PASS.
-- P9 on sophie, 15:48Z: PASS (record under P9).
-
-**Paul's go.** It is said to cover the merge, the release, the dev roll, and the new session from 5.1's leftovers through step 7. Paul's go on 2026-10-18 named "publish-identity and the console promote redone for v0.28.3", so 5.0a's promote-only step is approved, with `7c1beb2d` as its rollback (relayed by platform-1e). platform-1e confirms with Paul before the signer is created.
+**Paul's go.** It is said to cover the merge, the release, the dev roll, and the new session from 5.1's leftovers through step 7. Paul's go on 2026-10-18 named "publish-identity and the console promote redone for v0.28.3", so 5.0a's promote-only step is approved (relayed by platform-1e; carried to v0.28.4 by the coordinator), with `7c1beb2d` as its rollback. Paul chose option (b) for the untagged pointer. platform-1e confirms with Paul before the signer is created.
**The blocks, in order, each with its stop points**
-1. **Release v0.28.3. DONE** (platform-1e; verified above). Its stop rule stands for any re-check: the signature does not verify; the tag is not on `50f5b268`; the sums list other debs; any published sha256 differs from the local build.
-2. **Dev roll, boxes only. DONE** (platform-1e: 0 refusing, no panics, wakecheck 200; box state verified above). Stop rule for any re-check: a box not `current` at v0.28.3, `refused` or `rolled-back`; instance-control not restarted (H4).
-3. **5.0a, promote-only** (approved): ship and promote `50f5b268` to `canary/console` with the v0.28.3 amd64 CLI. Stop: the console's sha256 is not `3660b44c…ec74`; a pointer row `written after digests were turned on`; the header or `readlink` not `50f5b268`. Rollback: promote `7c1beb2d`.
-4. **Open the session** (`signing-host.md`, "Create"). Not before the open STOP below is ruled on. 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.3`; `claims help` not as quoted; a key file that is not root `600`.
+1. **Release v0.28.4** (README, "Releasing the platform"): tag `f3bf1214`, publish, verify from the public side, fill in the TO-CONFIRM values. Stop: the signature does not verify; the tag is not on `f3bf1214`; the sums list other debs; any published sha256 differs from the local build.
+2. **Dev roll, boxes only** (step 1's commands with `--version v0.28.4`, `stable` then `canary`; no console item). Stop: a box not `current` at v0.28.4, `refused` or `rolled-back`; instance-control not restarted (H4). Expected after: each box `carlos v0.28.4`, `.prev` `carlos v0.28.3`, and pointers without `release_tag` (expected).
+3. **5.0a, promote-only** (approved): ship and promote `f3bf1214` to `canary/console` with the v0.28.4 amd64 CLI. Stop: the console's sha256 is not `8d49199f…5e9b`; a pointer row `written after digests were turned on`; the header or `readlink` not `f3bf1214`. Rollback: promote `7c1beb2d`. Then P9 again: the build carries invite delivery.
+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`.
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.
-6. **Step 6**: 6.1 `sign-policy` and `publish-identity` (new `--out` paths) and staging under new paths; 6.1a `install-channel --policy` on leo, then sophie; 6.2 `guard-arm` on leo; 6.3 P9, `install-console`, `guard-arm --console`, `console-config`, the restart, and the wait for both boxes' `floor_guard_ready`. Stop: any refusal; 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`.
-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.3; an armed policy on dennis; `publish-floor` refused after one fresh `console-report`; `jq -e` not `true`.
+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, `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 four `--identity` ones included); an identity value other than 6.1's expected ones; any 6.1a output other than its expected lines; a P9 `SET` line or console-mail role policy; `console.service` not `active`.
+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`.
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**
-1. **STOP: the channel pointers carry no `release_tag`, so 6.1a refuses as the code stands.** Dev's signed `control/system/router/stable.json` and `canary.json` (written 15:43Z and 15:44Z by the v0.28.3 roll) have `current` `{version v0.28.3, source release:v0.28.3, released_at …}` and the arm64 artefact, but no `release_tag` and no `moves_protocol`. On main `50f5b268`, `install-channel` (`checkFloor`, `internal/host/installchannel.go`) takes the identity key from the pointer's `release_tag`; with none it refuses: "the stable channel names v0.28.3 with no release tag, so nothing was installed" (`canary` on sophie). The console's promote path adds `release_tag` only once a floor is published (`reserveHostUpdate` in `internal/console/hostactivation.go` returns before tagging in legacy mode), and here the floor comes last, in step 7. The rehearsal did not meet this: its witness promoted with `release.PromoteSystem` and `ReleaseTag: "v1.0.0"` set directly (`activation_console_rootrunner_linux_test.go:294`). The delta's precondition, "each box's channel pointer must name vX.Y.Z with tag vX.Y.Z", is not met on dev. platform-1e rules on the remedy before the signer is created. Until then, block 4 does not start; 5.0a (block 3) is unaffected and may run.
+**TO-CONFIRM, open until platform-1e posts "v0.28.4 on dev"**
+1. The v0.28.4 tag's commit (expected `f3bf1214`), by `git ls-remote --tags` on the forge.
+2. From v0.28.4's signed `SHA256SUMS.txt`, signature verified with the README's pinned key: the `carlos-linux-arm64` and `carlos-linux-amd64` sha256 (each must equal the local build), and the sha256 of `SHA256SUMS.txt` and `.sig`. Fill them in here, in 5.0a, in 6.1's expected identity and in `claim-trial-inputs/`.
+3. The dev roll: leo, sophie and dennis `carlos v0.28.4`, `.prev` `carlos v0.28.3`, `current`; dennis's guard still unarmed (7.1's check); P9 re-run on sophie once 5.0a has put the invite-delivery console there.
Later, each with its own go and outside this package: 7a.2 (fence-writer credentials), step 8 (leo's offline window), steps 9 and 10.
@@ -158,7 +154,7 @@ carlos version
Run the box line on sophie and dennis too. Pass: before step 7, every box, `.prev` image, console and operator CLI is on the trial release.
-For the v0.28.3 session (from the prev-rehearsal): every box line has two lines, `carlos` and `carlos.prev`. After the v0.28.3 dev roll, `.prev` is v0.28.2. On leo and sophie, step 6.1a's `install-channel --policy` makes it v0.28.3, so before step 7 both their slots read `carlos v0.28.3`. Each box's channel pointer must name v0.28.3, tag v0.28.3, with the same `carlos-linux-arm64` sha256 as the identity: leo follows `stable`, sophie `canary` (`carlos system status`). Dennis's `.prev` stays v0.28.2, and that passes. platform-1e's ruling (2026-10-18): P3 is about each roster reader's version being at least the floor, not about `.prev`. Dennis is not enrolled and is outside the census scope (region `uk`, pools not `cohort1`: the `required` test at `internal/hostactors/census.go:100` on main `50f5b268`), so its `.prev` is not checked. Do NOT run `install-channel` on dennis: it would destroy its rollback copy for no benefit. What step 7 checks on dennis instead is that its guard is unarmed (7.1). In the v0.28.3 session P3 runs at the start of step 7, before `console-report`, never between `console-report` and `publish-floor`.
+For the v0.28.4 session (from the prev-rehearsal and the untagged-pointer fix): every box line has two lines, `carlos` and `carlos.prev`. After the v0.28.4 dev roll, `.prev` is v0.28.3 (the release before). On leo and sophie, step 6.1a's `install-channel --policy --identity` makes it v0.28.4, so before step 7 both their slots read `carlos v0.28.4`. Each box's channel pointer must name v0.28.4 with the same `carlos-linux-arm64` sha256 as the identity: leo follows `stable`, sophie `canary` (`carlos system status`). The pointer carries no `release_tag` before a floor is published, and that is expected (6.1a). Dennis's `.prev` stays the release before, and that passes. platform-1e's ruling (2026-10-18): P3 is about each roster reader's version being at least the floor, not about `.prev`. Dennis is not enrolled and is outside the census scope (region `uk`, pools not `cohort1`: the `required` test at `internal/hostactors/census.go:100` on main `50f5b268`), so its `.prev` is not checked. Do NOT run `install-channel` on dennis: it would destroy its rollback copy for no benefit. What step 7 checks on dennis instead is that its guard is unarmed (7.1). In the v0.28.4 session P3 runs at the start of step 7, before `console-report`, never between `console-report` and `publish-floor`.
2026-10-17, re-run today: leo v0.27.3 (`.prev` v0.27.2), sophie v0.27.3 (`.prev` v0.27.2), dennis v0.27.3 (no `.prev`), console `e15ad6be` (v0.27.3), local CLI v0.27.2. `system status`: leo and dennis on `stable`, sophie on `canary`, all `current`. v0.27.3 is still the latest release, and it contains neither 26065378 (Plan 14 Parts 2 and 3 and the activation tools) nor e8e961dd (the empty-app writer fix). Step 1 fixes that. Every one of these builds already contains Plan 13, so the home designation order is safe once they are all on the trial release.
@@ -280,6 +276,8 @@ 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.
+**Note for v0.28.4 (console invite delivery).** The invite delivery branch has landed: it is on main as `91a651b3` and ships in v0.28.4, so 5.0a's promote puts it on sophie. With it, the console's own mailer can start itself when `CARLOS_MAIL_REGION` is set (that creates the mail provisioner; the From then comes from `CARLOS_CONSOLE_MAIL_FROM`, or from `CARLOS_MAIL_PLATFORM_SUFFIX`). P9's check covers both names, and `CARLOS_MAIL_PLATFORM_SUFFIX` is among the seven names it checks. platform-87 confirms sophie has none of them, and with `CARLOS_HOST_CLAIM` on the console refuses the mailer anyway (`consoleMailer` takes `hostClaim`, `cmd/console/main.go` at `f3bf1214`). The check and its pass are otherwise unchanged.
+
## 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. v0.28.2 was published on 2026-10-18 (tag on `7c1beb2d`) and rolled to leo, sophie and dennis (current, 0 refusing; each reads `carlos v0.28.2`, `.prev` v0.28.1). The dev console is held at `3b69c2bd` on purpose until 5.0a.
@@ -290,6 +288,7 @@ Re-run P9 right before step 6 (step 6.3), inside the signing session and before
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` (census ruled), now on main 7c1beb2d, published (v0.28.2, tag on 7c1beb2d): (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 gated everything past 5.0. The code part is resolved: v0.28.2 is published and on every dev box. What remains is 5.0a and the signing session itself. Details in `docs/superpowers/notes/claim-trial-inputs/README.md`.
6. **`carlos.prev` below the identity (the first session's 6.2 stop).** `guard-arm` checks both `/opt/carlos/carlos` and `/opt/carlos/carlos.prev` against the identity's router sha256 and the floor. After any `carlos system update`, `.prev` is the release before, so no updated box can arm. Resolved in code on main `50f5b268` (branch `claim-prev-rehearsal`, census ruled, copy approved by Paul): `carlos system install-channel --policy FILE` takes `sign-policy`'s armed policy as its floor before any floor is published, and on a box already at its channel's release replaces `carlos.prev` with that release. The root-runner rehearsal ran steps 6 and 7 with it and passed. It ships in v0.28.3, so the trial moves to v0.28.3: a new identity, a new floor (`min_release` v0.28.3), 5.0a again for the v0.28.3 console, and a second signing session.
+7. **The channel pointers carry no release tag.** Found on 2026-10-18 after the v0.28.3 roll: `carlos system update` writes `stable.json` and `canary.json` without `release_tag` or `moves_protocol` until a floor is published, and v0.28.3's `install-channel --policy` found the identity through that tag, so 6.1a would have refused. Resolved in code on main `f3bf1214` (branch `claim-untagged-pointer`, Paul's option (b), census ruled, nine strings approved by Paul): `install-channel --policy` takes a required `--identity FILE`. The file is checked against the published identity object and the release key, the pointer's current artefact sha256 must equal the identity's router sha256, a tag on the pointer (if any) must name this identity, and the pointer is left unchanged. It ships in v0.28.4 (with platform-87's console invite delivery, `91a651b3`), so the trial moves to v0.28.4: a new identity, a new floor (`min_release` v0.28.4), 5.0a for the v0.28.4 console. v0.28.3 was published and rolled to dev but is not the trial release; no v0.28.3 identity or console was published or shipped.
## Accepted trial gaps
@@ -474,7 +473,7 @@ Why a pool: dev's two home boxes both mint every unregioned, pool-less record. A
## Step 5. Box identity records, roster and enrollment
-Step 5.0 is done. 5.0a's first run (digests on for `bab`, the v0.28.2 console) is done. For v0.28.3, 5.0a is promote-only: it runs after v0.28.3 is on dev's boxes and before the second signing session opens. 5.1 (roster and enrollment) is done at revision 5; the second session resumes it ("5.1 in the second session").
+Step 5.0 is done. 5.0a's first run (digests on for `bab`, the v0.28.2 console) is done. For v0.28.4, 5.0a is promote-only: it runs after v0.28.4 is on dev's boxes and before the second signing session opens. 5.1 (roster and enrollment) is done at revision 5; the second session resumes it ("5.1 in the second session").
### 5.0 Box identity records (done 2026-10-18)
@@ -536,27 +535,27 @@ the record at control/boxes/sophie-knitting.json is valid AWS-signed evidence fo
Any other answer is a stop. The `invite-box` dry run in 5.1 then checks the same evidence again, from the signer.
-### 5.0a The release-stamped dev console for v0.28.3 (promote-only)
+### 5.0a The release-stamped dev console for v0.28.4 (promote-only)
-Runs after v0.28.3 is published and on dev's boxes (Go package, blocks 1 and 2), and before the second signing session opens. It is the v0.28.3 dev roll's console item, held back so it lands after the boxes. Approved: Paul's go on 2026-10-18 named "publish-identity and the console promote redone for v0.28.3" (relayed by platform-1e), with `7c1beb2d` as the rollback. It changes nothing claim-related.
+Runs after v0.28.4 is published and on dev's boxes (Go package, blocks 1 and 2), and before the second signing session opens. It is the v0.28.4 dev roll's console item, held back so it lands after the boxes. Approved: Paul's go on 2026-10-18 named "publish-identity and the console promote redone for v0.28.4" (relayed by platform-1e); the coordinator carries it to v0.28.4 (2026-10-18), with `7c1beb2d` still the rollback. The v0.28.4 console (`f3bf1214`) was never shipped: the dev console is still on `7c1beb2d`. It changes nothing claim-related.
-Digests have been on for `bab` since the first run (`pointer digests: on for bab since 2026-10-18T10:11:25Z`; the marker `releases/bab/pointer-digests`, 219 bytes, was read again on 2026-10-18 at 14:30Z). So the first run's item 3 (digests on) is not repeated, and its "Before 4" rollback no longer applies. What is left is item 4: ship and promote the prebuilt v0.28.3 console to `canary/console`.
+Digests have been on for `bab` since the first run (`pointer digests: on for bab since 2026-10-18T10:11:25Z`; the marker `releases/bab/pointer-digests`, 219 bytes, was read again on 2026-10-18 at 14:30Z). So the first run's item 3 (digests on) is not repeated, and its "Before 4" rollback no longer applies. What is left is item 4: ship and promote the prebuilt v0.28.4 console to `canary/console`.
-Why it is needed: `publish-identity` for v0.28.3 reads the console's sha256 only through the `canary/console` pointer's signed manifest digest for the console version, then the manifest, then the artefact, whose `version.Build` must be `v0.28.3`. Sophie's `install-console` then takes the promoted build as its `--source`.
+Why it is needed: `publish-identity` for v0.28.4 reads the console's sha256 only through the `canary/console` pointer's signed manifest digest for the console version, then the manifest, then the artefact, whose `version.Build` must be `v0.28.4`. Sophie's `install-console` then takes the promoted build as its `--source`.
Values, on DOCS-T1:
-- `CARLOS`: the carlos v0.28.3 CLI for DOCS-T1, `carlos-linux-amd64`, sha256 `82a60203b1790771b5f3da16bc5d342327131ed30a1aae59a520f42d253369b2` (signed sums, verified 2026-10-18). Download it with `SHA256SUMS.txt` and `SHA256SUMS.txt.sig` from `https://github.com/carlosframework/releases/releases/download/v0.28.3/`, verify the signature as the README's "Releasing the platform" does, then `sha256sum -c --ignore-missing SHA256SUMS.txt`.
-- `CONSOLE=~/.cache/release-v0.28.3/console-linux-arm64`: the prebuilt console, sha256 `3660b44c13080f0e9ead5f70754a11ec267d70eaab2a89e88ce256ab3437ec74`. Use that exact file; never rebuild it, and never run it.
-- `CV=50f5b268`: the console's ship version, the short sha of v0.28.3's commit (the forge's `v0.28.3^{}` is `50f5b268`, checked 2026-10-18). `apps/bab/console/releases/50f5b268/` does not exist (read 2026-10-18 14:30Z), so the ship is new. If that prefix appears before item 4, stop and ask. Never make up another version.
+- `CARLOS`: the carlos v0.28.4 CLI for DOCS-T1, `carlos-linux-amd64`, sha256 `<TO-CONFIRM>` (from v0.28.4's signed sums; the local build is `2d53162fa28accc724a0571f8c7ff59ca36c9050201446e3f33faf4a6f608548`). Download it with `SHA256SUMS.txt` and `SHA256SUMS.txt.sig` from `https://github.com/carlosframework/releases/releases/download/v0.28.4/`, verify the signature as the README's "Releasing the platform" does, then `sha256sum -c --ignore-missing SHA256SUMS.txt`.
+- `CONSOLE=~/.cache/release-v0.28.4/console-linux-arm64`: the prebuilt console, sha256 `8d49199f433662ab183a98ce7a5ebeb18f36bc02af5689752580a596bc565e9b`. Use that exact file; never rebuild it, and never run it.
+- `CV=f3bf1214`: the console's ship version, the short sha of v0.28.4's commit (`~/.cache/release-v0.28.4/src` is at `f3bf1214ab92a47ecb626400e7bd1b35cd7a112c`, main's tip; the tag commit is TO-CONFIRM). `apps/bab/console/releases/f3bf1214/` does not exist (read 2026-10-18), so the ship is new. If that prefix appears before item 4, stop and ask. Never make up another version.
- `release.pub`: as in the first run.
**1. Read-only checks, on the day.** The commands are the first run's (below); only the expected values change.
- **1a.** The listing shows exactly one key, `apps/bab/console/channels/canary/console.json`, and `"$CARLOS" channels --app console --account-id bab` shows `canary/console 7c1beb2d 2026-10-18T10:11:48Z yes`. Anything else is a STOP.
- **1c.** `x-carlos-version: 7c1beb2d`, running `/opt/carlos/versions/7c1beb2d/console`. Record `7c1beb2d` as the rollback version.
-- **1d.** The writers table again. With digests on, a writer older than v0.26.0 writes pointers without digests, and every box that has seen digests refuses them. Box CLIs now read `carlos v0.28.3` (`.prev` `carlos v0.28.2`), and `"$CARLOS" version` reads `carlos v0.28.3`. Every row PASS, as on 2026-10-18.
+- **1d.** The writers table again. With digests on, a writer older than v0.26.0 writes pointers without digests, and every box that has seen digests refuses them. Box CLIs now read `carlos v0.28.4` (`.prev` `carlos v0.28.3`), and `"$CARLOS" version` reads `carlos v0.28.4`. Every row PASS, as on 2026-10-18.
- **1e.** `"$CARLOS" channels digests --account-id bab` shows `pointer digests: on for bab since 2026-10-18T10:11:25Z`, no `console canary/console` row, and no row that says `written after digests were turned on`.
-**2. Check the prebuilt console** (local, read-only; never execute or rebuild it), with v0.28.3's `SHA256SUMS.txt`:
+**2. Check the prebuilt console** (local, read-only; never execute or rebuild it), with v0.28.4's `SHA256SUMS.txt`:
```bash
sha256sum "$CONSOLE"
@@ -566,14 +565,14 @@ go version -m "$CONSOLE" | grep -o 'internal/version.Build=[^ "]*' | sort | uniq
grep -c "$(sha256sum "$CONSOLE" | cut -d' ' -f1)" SHA256SUMS.txt
```
-Expected, as read on 2026-10-18 (PASS; the last line against v0.28.3's published, signature-verified sums):
+Expected. All but the last line were read on 2026-10-18 (PASS); the last waits for v0.28.4's published sums:
```
-3660b44c13080f0e9ead5f70754a11ec267d70eaab2a89e88ce256ab3437ec74 /home/paulca/.cache/release-v0.28.3/console-linux-arm64
+8d49199f433662ab183a98ce7a5ebeb18f36bc02af5689752580a596bc565e9b /home/paulca/.cache/release-v0.28.4/console-linux-arm64
.../console-linux-arm64: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, Go BuildID=..., stripped
path github.com/carlosframework/platform/cmd/console
- build -ldflags="-s -w -X github.com/carlosframework/platform/internal/version.Build=v0.28.3"
- 1 internal/version.Build=v0.28.3
+ build -ldflags="-s -w -X github.com/carlosframework/platform/internal/version.Build=v0.28.4"
+ 1 internal/version.Build=v0.28.4
0
```
@@ -584,17 +583,17 @@ A different sha256 on the day is a STOP.
**4. Ship and promote the prebuilt console.**
```bash
-CV=50f5b268
+CV=f3bf1214
CARLOS_BIN=$CARLOS platform-infrastructure/scripts/ship-app.sh kass carlos-dev-640012953448 console "$CV" canary/console binary "$CONSOLE"
```
Sophie stages, adopts and restarts `console.service` on its own, in about 20 seconds, so the dev console is briefly down.
Expected:
-- `shipped console 50f5b268 (1 artifacts)`, then `promoted console 50f5b268 -> canary/console`; the manifest's `linux-arm64` sha256 is `3660b44c…ec74`;
-- `x-carlos-version` (as in 1c) shows `50f5b268`;
-- the `readlink` from 1c shows `/opt/carlos/versions/50f5b268/console`, and that file's sha256 on sophie is `3660b44c…ec74`. That path is `install-console`'s `--source` in step 6.3;
-- `"$CARLOS" channels --app console --account-id bab` shows `canary/console 50f5b268 ... yes`;
+- `shipped console f3bf1214 (1 artifacts)`, then `promoted console f3bf1214 -> canary/console`; the manifest's `linux-arm64` sha256 is `8d49199f…5e9b`;
+- `x-carlos-version` (as in 1c) shows `f3bf1214`;
+- the `readlink` from 1c shows `/opt/carlos/versions/f3bf1214/console`, and that file's sha256 on sophie is `8d49199f…5e9b`. That path is `install-console`'s `--source` in step 6.3;
+- `"$CARLOS" channels --app console --account-id bab` shows `canary/console f3bf1214 ... yes`;
- `channels digests --account-id bab` (as in 1e) lists no `console canary/console` row and no `written after digests were turned on` row;
- wakecheck still answers 200 through DNS.
@@ -606,13 +605,13 @@ CARLOS_RELEASE_KEY=$(aws ssm get-parameter --name /carlos/release-key --with-dec
"$CARLOS" promote -app console 7c1beb2d canary/console
```
-A rollback means the second signing session cannot start: `publish-identity` for v0.28.3 needs the `50f5b268` console on `canary/console`.
+A rollback means the second signing session cannot start: `publish-identity` for v0.28.4 needs the `f3bf1214` console on `canary/console`.
Finish this step before the session opens. Its `GetParameter` reads come from `remote-agent`, and the CloudTrail check at the session's close counts from the session's start.
### 5.0a, first run: pointer digests and the v0.28.2 console (done 2026-10-18)
-Kept for the record and for its commands, which the v0.28.3 run above reuses.
+Kept for the record and for its commands, which the v0.28.4 run above reuses.
Builder finding from the review of platform-1e's branch `claim-step6-artifacts` (`c38a4ca9`, now on main 7c1beb2d, published (v0.28.2, tag on 7c1beb2d)), drafted for Paul after v0.28.2.
@@ -837,13 +836,13 @@ The rollback version, if one is ever needed, is `3b69c2bd`, promoted with digest
Paul's ruling, 2026-10-18: the trusted signing host is created once, after v0.28.2 is on dev, for one session that covers 5.1, step 6 and step 7, so the release key is on a host only once. The trial waits for v0.28.2 before anything past 5.0.
-That first session stopped at 6.2 and was closed (step 6's record). The second session, on v0.28.3, puts the release key on a host a second time. Paul's go is said to cover it, from 5.1's leftovers through step 7; platform-1e confirms with Paul before the signer is created.
+That first session stopped at 6.2 and was closed (step 6's record). The second session, on v0.28.4, puts the release key on a host a second time. Paul's go is said to cover it, from 5.1's leftovers through step 7; platform-1e confirms with Paul before the signer is created.
-Order of work for the second session (v0.28.3), each block with its stop points:
-1. **Open the session.** Only once the open STOP in the Go package (no `release_tag` on the channel pointers) is ruled on, v0.28.3 is published and verified (done), every dev box reads `carlos v0.28.3` (`.prev` `carlos v0.28.2`) and `current`, and 5.0a for v0.28.3 has passed. platform-1e confirms with Paul, then: create the signer host and its role policy (`signing-host.md`, "Create", with write on exactly `control/system/router/releases/v0.28.3.json`), install the verified tools (carlos v0.28.3 `linux-arm64` `6154afb4...def0`; jq 1.7.1 `4dd2d8a0...`), then fetch the key into tmpfs (`signing-host.md`, "Getting the key there"). Record the session's start time before the key fetch, and choose a new exchange `<run-id>`. Stop points: a checksum mismatch, `carlos version` not `carlos v0.28.3`, `claims help` not as quoted below, or a key file that is not root `600`.
+Order of work for the second session (v0.28.4), each block with its stop points:
+1. **Open the session.** Only once v0.28.4 (with `install-channel --identity`, the fix for the untagged pointers) is published and verified, every dev box reads `carlos v0.28.4` (`.prev` `carlos v0.28.3`) and `current`, and 5.0a for v0.28.4 has passed. platform-1e confirms with Paul, then: create the signer host and its role policy (`signing-host.md`, "Create", with write on exactly `control/system/router/releases/v0.28.4.json`), install the verified tools (carlos v0.28.4 `linux-arm64`, sha256 TO-CONFIRM from the signed sums; jq 1.7.1 `4dd2d8a0...`), then fetch the key into tmpfs (`signing-host.md`, "Getting the key there"). Record the session's start time before the key fetch, and choose a new exchange `<run-id>`. Stop points: a checksum mismatch, `carlos version` not `carlos v0.28.4`, `claims help` not as quoted below, or a key file that is not root `600`.
2. **5.1, resumed.** "5.1 in the second session". Stop points: revision not 5, the listing not as recorded, or a staged file missing or with another sha256.
-3. **Step 6.** In order: 6.1 `sign-policy` and `publish-identity` on the signer with new `--out` paths, then staging under new paths on leo and sophie; 6.1a `install-channel --policy` on leo, then on sophie; 6.2 `guard-arm` on leo; 6.3 the P9 re-run from DOCS-T1, then `install-console`, `guard-arm --console`, `console-config` and the restart on sophie, then the wait until both boxes' reports carry `floor_guard_ready`. `console-report` is not in step 6 any more: it opens step 7's report-and-floor pair. Stop points: any refusal; an identity value other than step 6.1's expected values (a wrong published identity cannot be replaced); any 6.1a output other than its expected lines; any P9 `SET` line or console-mail role policy; `console.service` not `active`.
-4. **Step 7** (`home_region` becomes write-once). Readers re-checked first (P3, with `carlos.prev`), then the read-only check that dennis's guard is unarmed. Then `console-report` on sophie, **followed immediately by `publish-floor` on the signer**, then home, leo's key and the console runtime. Stop points: any reader below v0.28.3; an armed policy on dennis; `publish-floor` refused after one fresh `console-report`; the `jq -e` roster check not `true`; a refused report that a fresh box report does not clear.
+3. **Step 6.** In order: 6.1 `sign-policy` and `publish-identity` on the signer with new `--out` paths, then staging under new paths on leo and sophie; 6.1a `install-channel --policy --identity` on leo, then on sophie; 6.2 `guard-arm` on leo; 6.3 the P9 re-run from DOCS-T1, then `install-console`, `guard-arm --console`, `console-config` and the restart on sophie, then the wait until both boxes' reports carry `floor_guard_ready`. `console-report` is not in step 6 any more: it opens step 7's report-and-floor pair. Stop points: any refusal; an identity value other than step 6.1's expected values (a wrong published identity cannot be replaced); any 6.1a output other than its expected lines; any P9 `SET` line or console-mail role policy; `console.service` not `active`.
+4. **Step 7** (`home_region` becomes write-once). Readers re-checked first (P3, with `carlos.prev`), then the read-only check that dennis's guard is unarmed. Then `console-report` on sophie, **followed immediately by `publish-floor` on the signer**, then home, leo's key and the console runtime. Stop points: any reader below v0.28.4; an armed policy on dennis; `publish-floor` refused after one fresh `console-report`; the `jq -e` roster check not `true`; a refused report that a fresh box report does not clear.
5. **Close the session.** No go needed; always allowed, and done at any stop. Shred the key, terminate the signer, remove the exchange objects, delete its role, profile and security group, then the CloudTrail check ("Closing the signing session", after step 7). Anything unexpected in CloudTrail is reported on #250 before any further step.
**Hard ordering: `console-report`, then `publish-floor`, with nothing between them.** `console-report` puts the console's process entry into sophie's box report, `status/boxes/sophie-knitting.json`. The agent writes that report whole at its next status tick (every 5 minutes plus jitter), and the entry is gone (#312; the prev-rehearsal saw 0 console process entries in the next report). `publish-floor` needs that entry. So have the `publish-floor` command ready to send before `console-report` runs, and send it the moment `console-report` succeeds: no P3, no `box_inspect`, no review pause, no other command in between. If `publish-floor` is refused because the entry has gone, run `box_inspect "$SOPHIE"`, then `console-report` again on sophie, then `publish-floor` again at once.
@@ -884,17 +883,9 @@ The sign-policy and publish-identity phases write their file to --out, a new pat
`install-console` sits between `console-report` and `install-control`, and the last line follows "Run signing phases on the trusted operator host. Run box installers as root." Check these against `/usr/local/bin/carlos system claims help` on the signer before step 6; any difference is a stop.
-On main `50f5b268` (v0.28.3) the `claims` help is unchanged from v0.28.2: `50f5b268` does not touch `internal/host/activation_command.go`, and the local v0.28.3 build (`carlos v0.28.3`) printed the v0.28.2 text above, the three changes included, on 2026-10-18. What `50f5b268` adds is `--policy` on `carlos system install-channel` (step 6.1a). Its help, as the local v0.28.3 build printed it for `carlos system install-channel -h` (the usage line is RHB-C19, re-approved by Paul on 2026-10-18):
+On main `f3bf1214` (v0.28.4) the `claims` help is unchanged from v0.28.2: `internal/host/activation_command.go` is the same at `7c1beb2d` and `f3bf1214`, and the local v0.28.4 build (`~/.cache/release-v0.28.4/dist/carlos-linux-amd64`, `carlos v0.28.4`) printed the v0.28.2 text above, the three changes included, on 2026-10-18. What changes is `carlos system install-channel`: `--policy` (from `50f5b268`) and `--identity` (from `f3bf1214`). Its help is quoted in step 6.1a, as the same local build printed it.
-```
-usage: carlos system install-channel [--policy FILE]
-
-Install the release this box's channel names, as root, on a box that has none yet.
-flags:
- --policy <string> the armed policy carlos system claims run sign-policy wrote, used as the host floor until one is published
-```
-
-Check both against the published v0.28.3 binary: `claims help` on the signer before step 6, and `/opt/carlos/carlos system install-channel -h` on leo before 6.1a (it prints and exits before reading anything). Any difference is a stop.
+Check both against the published v0.28.4 binary: `claims help` on the signer before step 6, and `/opt/carlos/carlos system install-channel -h` on leo before 6.1a (it prints and exits before reading anything). Any difference is a stop.
`run` takes a JSON plan whose `phase` is one of `initialize`, `configure`, `invite-box`, `enroll-box`, `invite-console`, `enroll-console`, `publish-floor`, `home` or `bind-key`, with `bucket` and `expected_revision`. v0.28.2 adds `sign-policy` and `publish-identity` (pending, same branch); these two must have `--out`, and every other phase refuses it. Every `carlos` command below is quoted from platform-1e's report (run 2, sections A to C; run 11 confirms these command branches are byte-identical on main), and every flag was checked against main's code.
@@ -1053,7 +1044,7 @@ Staging from DOCS-T1: `ok` for both directories, then `staged` for leo's `releas
The signer stayed up with the key in tmpfs for step 6. It was closed at the 6.2 stop (see the end of step 6).
-### 5.1 in the second session (v0.28.3): resume, not redo
+### 5.1 in the second session (v0.28.4): resume, not redo
The roster stays at revision 5. Nothing in 5.1 is run again: no `initialize`, `configure`, invitation or enrollment. A new phase run against revision 5 would only be needed if the roster had moved, and then it is a stop, not a repair.
@@ -1066,19 +1057,19 @@ What the first session's 5.1 left, read read-only on 2026-10-18 at 14:30Z:
What the second session still has to do for 5.1, in order:
1. **On DOCS-T1, before the signer is created:** re-run the box check above, read-only, on leo and sophie (`stat -c "%n %U:%G %a %h %s"` and `sha256sum` in `/root/claim-trial`). Every file listed must be there with the same sha256, mode and link count. A missing or changed file is a stop: report it, and do not re-stage over it.
-2. **Exchange:** upload to `claim-signer/<new run-id>/` only what the signer needs from here on: `release.pub`, the v0.28.3 `floor.json`, v0.28.3's `SHA256SUMS.txt` and `SHA256SUMS.txt.sig` as downloaded, the carlos v0.28.3 `linux-arm64` CLI and jq. `regions.json`, `leo.json`, `sophie.json` and `console.json` are not needed again.
+2. **Exchange:** upload to `claim-signer/<new run-id>/` only what the signer needs from here on: `release.pub`, the v0.28.4 `floor.json`, v0.28.4's `SHA256SUMS.txt` and `SHA256SUMS.txt.sig` as downloaded, the carlos v0.28.4 `linux-arm64` CLI and jq. `regions.json`, `leo.json`, `sophie.json` and `console.json` are not needed again.
3. **On the signer:** write `helpers.sh` again: this section's first helper block, with `CARLOS=/usr/local/bin/carlos`, `KEY=/run/claim-signer/release.key` and no `AWS_PROFILE`, then step 6.1's `phase_out` verbatim. Then `refresh` only. Expected: `.roster.revision` `5`; `leo-climbing` (pool `cohort1`, `i-046c7fe43620944bc`), `sophie-knitting` (pool `console`, `i-05498eada6f0afd23`) and `dev-console` (compute `sophie-knitting`); `holds` and `activations` empty. Print it with every `enrollment` value redacted, as in the first session. Any other revision or listing is a stop.
-4. **New paths for step 6's files.** The v0.28.3 policy and identity go to `/root/claim-trial/policy.v0.28.3.json` and `/root/claim-trial/identity.v0.28.3.json` on the signer, leo and sophie. `box_put` writes with `>`, so the step 6.1 staging first checks that both paths are absent on each box; the first session's `policy.json` and `identity.json` are never overwritten.
+4. **New paths for step 6's files.** The v0.28.4 policy and identity go to `/root/claim-trial/policy.v0.28.4.json` and `/root/claim-trial/identity.v0.28.4.json` on the signer, leo and sophie. `box_put` writes with `>`, so the step 6.1 staging first checks that both paths are absent on each box; the first session's `policy.json` and `identity.json` are never overwritten.
## Step 6. Arm leo's guard and start the guarded console
-Inside the signing session, after 5.1. The second session runs it on v0.28.3: everything it uses is on main `50f5b268`, built as v0.28.3: the `claims run` phases `sign-policy` and `publish-identity`, `claims run --out` and `claims install-console` (from `7c1beb2d`), and `install-channel --policy` (from `50f5b268`). Never hand-make any of their files. The first session's run, on v0.28.2, is recorded at the end of this step.
+Inside the signing session, after 5.1. The second session runs it on v0.28.4: everything it uses is on main `f3bf1214`, built as v0.28.4: the `claims run` phases `sign-policy` and `publish-identity`, `claims run --out` and `claims install-console` (from `7c1beb2d`), `install-channel --policy` (from `f3bf1214`) and its `--identity` (from `f3bf1214`). Never hand-make any of their files. The first session's run, on v0.28.2, is recorded at the end of this step.
Paul's go: once for step 6. It restarts `console.service` on sophie, which briefly interrupts the dev console. In the second session the go covers 5.1's leftovers through step 7.
### 6.1 Sign the policy and publish the identity (signer)
-`policy.v0.28.3.json` and `identity.v0.28.3.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 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:
+`policy.v0.28.4.json` and `identity.v0.28.4.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 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.
@@ -1093,70 +1084,83 @@ phase_out() {
}
```
-**The armed policy.** `floor.json` is now `{"min": 1, "min_release": "v0.28.3"}` (sha256 `c7063389…303f`), so the policy's bytes differ from the first session's.
+**The armed policy.** `floor.json` is now `{"min": 1, "min_release": "v0.28.4"}` (sha256 `881acf53…37a9`), so the policy's bytes differ from the first session's.
```bash
jq -n --slurpfile x floor.json '{floor:$x[0]}' > floor-phase.json
-phase_out sign-policy floor-phase.json /root/claim-trial/policy.v0.28.3.json sign-policy-result.json
+phase_out sign-policy floor-phase.json /root/claim-trial/policy.v0.28.4.json sign-policy-result.json
jq -er .floor_sha256 sign-policy-result.json
```
-Expected: the dry run, then the apply, succeed; `writes` is empty, because the phase writes nothing to the bucket; `floor_sha256` is the floor's digest, expected `07d2b63e711af4d97fad48713096447ce3c81ff92db0e956a98543478484aa09` (the sha256 of the compact `{"min":1,"min_release":"v0.28.3"}`), kept for step 7. Step 7's `publish-floor` writes the same floor bytes and authorization from the same `floor.json`. `sign-policy` refuses a floor other than the one the deployment is already bound to; no floor is bound yet (`control/moves-protocol.json` 404 on 2026-10-18), so the move from v0.28.2 to v0.28.3 is free.
+Expected: the dry run, then the apply, succeed; `writes` is empty, because the phase writes nothing to the bucket; `floor_sha256` is the floor's digest, expected `1a6a7e50f1081e1fdebe07026a9ad1cd0b27e9e36851176c5f510185832a4ab3` (the sha256 of the compact `{"min":1,"min_release":"v0.28.4"}`), kept for step 7. Step 7's `publish-floor` writes the same floor bytes and authorization from the same `floor.json`. `sign-policy` refuses a floor other than the one the deployment is already bound to; no floor is bound yet (`control/moves-protocol.json` 404 on 2026-10-18), so the move to v0.28.4 is free.
**The identity.** `publish-identity`'s plan carries these inputs, the first four verbatim:
| Plan field | Value | Where it comes from |
|---|---|---|
-| `identity.sha256sums` | the exact text of `SHA256SUMS.txt`, final newline included | the v0.28.3 public release: `https://github.com/carlosframework/releases/releases/download/v0.28.3/SHA256SUMS.txt` |
+| `identity.sha256sums` | the exact text of `SHA256SUMS.txt`, final newline included | the v0.28.4 public release: `https://github.com/carlosframework/releases/releases/download/v0.28.4/SHA256SUMS.txt` |
| `identity.sha256sums_sig` | the exact text of `SHA256SUMS.txt.sig` | the same release, same path with `.sig` |
| `identity.console.account` | `bab` | the console app's account, checked in 5.0a 1a (a STOP if it differs) |
| `identity.console.channel` | `canary/console` | the channel 5.0a promoted to, checked in 5.0a 1a (a STOP if it differs) |
-| `identity.console.version` | `50f5b268` (the v0.28.3 tag's commit) | the console's ship version from 5.0a for v0.28.3 |
+| `identity.console.version` | `f3bf1214` (the v0.28.4 tag's commit) | the console's ship version from 5.0a for v0.28.4 |
| `identity.console.app` | `console` | the console app |
-| `identity.release_tag` | `v0.28.3` | the release tag |
+| `identity.release_tag` | `v0.28.4` | 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. It also checks that the list is v0.28.3's own: it must name `carlos_0.28.3_amd64.deb` and `carlos_0.28.3_arm64.deb`, and no other `carlos_*.deb`, so another release's genuine list (v0.28.2's included) 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.
+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.4's own: it must name `carlos_0.28.3_amd64.deb` and `carlos_0.28.3_arm64.deb`, and no other `carlos_*.deb`, so another release's genuine list (v0.28.2's and v0.28.3's included) 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=50f5b268
+CV=f3bf1214
jq -n --rawfile sums SHA256SUMS.txt --rawfile sig SHA256SUMS.txt.sig --arg cv "$CV" \
- '{identity:{release_tag:"v0.28.3",moves_protocol:1,sha256sums:$sums,sha256sums_sig:$sig,
+ '{identity:{release_tag:"v0.28.4",moves_protocol:1,sha256sums:$sums,sha256sums_sig:$sig,
console:{account:"bab",app:"console",channel:"canary/console",version:$cv}}}' > identity-phase.json
-phase_out publish-identity identity-phase.json /root/claim-trial/identity.v0.28.3.json publish-identity-result.json
+phase_out publish-identity identity-phase.json /root/claim-trial/identity.v0.28.4.json publish-identity-result.json
jq '{identity_key, release}' publish-identity-result.json
```
Run the dry run on its own first and check it field by field, as the first session did, before `phase_out`.
Expected:
-- the dry run, then the apply, succeed, and `writes` lists one create-only put (`before_etag` empty) of `control/system/router/releases/v0.28.3.json`;
-- `identity_key` is that key, and `release` shows `version` and `release_tag` `v0.28.3`, `moves_protocol` `1`, `artifacts.linux-arm64` `{"key": "bootstrap/router/v0.28.3/carlos-linux-arm64", "sha256": "6154afb458a35183e470f5480ea08c09817400e6e67465530477e5204af2def0"}` (the `carlos-linux-arm64` line of v0.28.3's `SHA256SUMS.txt`, equal to the local build), and `console_artifacts.linux-arm64` `{"key": "bootstrap/console/v0.28.3/carlos-linux-arm64", "sha256": "3660b44c13080f0e9ead5f70754a11ec267d70eaab2a89e88ce256ab3437ec74"}` (5.0a's prebuilt console). The payload also holds `channel stable`, `spread 0s`, `source release:v0.28.3` and `released_at` zero, on purpose (step 6's first-session record). Any other value is a STOP. The console key is a name only: nothing in step 6 reads it, and it is not in the bucket;
-- `identity.v0.28.3.json` is the published object's bytes as read back, the same file `aws s3 cp` of that key would give.
+- the dry run, then the apply, succeed, and `writes` lists one create-only put (`before_etag` empty) of `control/system/router/releases/v0.28.4.json`;
+- `identity_key` is that key, and `release` shows `version` and `release_tag` `v0.28.4`, `moves_protocol` `1`, `artifacts.linux-arm64` `{"key": "bootstrap/router/v0.28.4/carlos-linux-arm64", "sha256": "<TO-CONFIRM>"}` (the `carlos-linux-arm64` line of v0.28.4's `SHA256SUMS.txt`; the local build is `736826e4…4b21`), and `console_artifacts.linux-arm64` `{"key": "bootstrap/console/v0.28.4/carlos-linux-arm64", "sha256": "8d49199f433662ab183a98ce7a5ebeb18f36bc02af5689752580a596bc565e9b"}` (5.0a's prebuilt console). The payload also holds `channel stable`, `spread 0s`, `source release:v0.28.4` and `released_at` zero, on purpose (step 6's first-session record). Any other value is a STOP. The console key is a name only: nothing in step 6 reads it, and it is not in the bucket;
+- `identity.v0.28.4.json` is the published object's bytes as read back, the same file `aws s3 cp` of that key would give.
-The signer's role may write exactly one identity object: the key `control/system/router/releases/v0.28.3.json`, an exact key and not a prefix (`signing-host.md`, statement `WriteIdentity`). The runner also admits only that key for the phase.
+The signer's role may write exactly one identity object: the key `control/system/router/releases/v0.28.4.json`, an exact key and not a prefix (`signing-host.md`, statement `WriteIdentity`). The runner also admits only that key for the phase.
-The identity is create-only per tag. Publishing the same identity again writes nothing; a different one under v0.28.3 is refused and can never replace it. So check the console values against 5.0a before the apply. A wrong identity, once published, is a stop, and putting it right needs a new release tag. A rerun after a successful publish needs a new `--out` path (for example `identity.v0.28.3.2.json`); it writes nothing to the bucket, and its file must have the same sha256.
+The identity is create-only per tag. Publishing the same identity again writes nothing; a different one under v0.28.4 is refused and can never replace it. So check the console values against 5.0a before the apply. A wrong identity, once published, is a stop, and putting it right needs a new release tag. A rerun after a successful publish needs a new `--out` path (for example `identity.v0.28.4.2.json`); it writes nothing to the bucket, and its file must have the same sha256.
-The v0.28.2 identity, `control/system/router/releases/v0.28.2.json` (1076 bytes, `2d22f05f`), stays exactly as it is. It is permanent, nothing reads it once the boxes are on v0.28.3, and it is never deleted or rewritten. The same goes for the first session's `policy.json` and `identity.json` staged on leo and sophie.
+The v0.28.2 identity, `control/system/router/releases/v0.28.2.json` (1076 bytes, `2d22f05f`), stays exactly as it is. It is permanent, nothing reads it once the boxes are on v0.28.4, and it is never deleted or rewritten. No v0.28.3 identity was published (`control/system/router/releases/` held only `v0.28.2.json` on 2026-10-18), and none is needed now; if one appears, it too is left as it is, unused. The same goes for the first session's `policy.json` and `identity.json` staged on leo and sophie.
Copy both new files to DOCS-T1 through `s3://carlos-dev-640012953448/claim-signer/<run-id>/` with `aws s3 cp`, and record each sha256 on both sides. Then stage them from DOCS-T1 under the new paths, after checking the paths are absent (`box_put` writes with `>`):
```bash
for box in "$LEO" "$SOPHIE"; do
- ssm_run "$box" "test ! -e /root/claim-trial/policy.v0.28.3.json && test ! -e /root/claim-trial/identity.v0.28.3.json && echo absent"
+ ssm_run "$box" "test ! -e /root/claim-trial/policy.v0.28.4.json && test ! -e /root/claim-trial/identity.v0.28.4.json && echo absent"
done
-for f in policy.v0.28.3.json identity.v0.28.3.json; do box_put "$LEO" "$f" "/root/claim-trial/$f"; done
-for f in policy.v0.28.3.json identity.v0.28.3.json; do box_put "$SOPHIE" "$f" "/root/claim-trial/$f"; done
+for f in policy.v0.28.4.json identity.v0.28.4.json; do box_put "$LEO" "$f" "/root/claim-trial/$f"; done
+for f in policy.v0.28.4.json identity.v0.28.4.json; do box_put "$SOPHIE" "$f" "/root/claim-trial/$f"; done
```
Expected: `absent` twice, then `staged` four times. If a path exists, stop: never stage over it.
-### 6.1a Bring `carlos.prev` to v0.28.3 with `install-channel --policy` (leo, then sophie)
+### 6.1a Bring `carlos.prev` to v0.28.4 with `install-channel --policy --identity` (leo, then sophie)
+
+From the prev-rehearsal (main `50f5b268`) and the untagged-pointer fix (main `f3bf1214`, Paul's option (b)), both in v0.28.4. Before `guard-arm`, each box to be armed runs `carlos system install-channel --policy <policy> --identity <identity>` as root, leo first, then sophie (the console box). On a box that `carlos system update` has already moved to its channel's release, it leaves `carlos` as it is and replaces `carlos.prev` (the release before, v0.28.3) with it, so `guard-arm`'s two-slot check passes. It takes the armed policy's floor as the floor, because none is published yet. It finds the release through the `--identity` file (6.1's `publish-identity` output), never through the pointer's tag: the file is checked against the published identity object and the release key, the pointer's current artefact sha256 must equal the identity's router sha256, a tag on the pointer (if any) must name this identity, and the pointer itself is left unchanged.
+
+Before it, on each box: the channel pointer names v0.28.4 with the identity's `carlos-linux-arm64` sha256 (leo `stable`, sophie `canary`; P3's `carlos system status`). It carries no `release_tag` and no `moves_protocol`: that is how `carlos system update` writes it before a floor is published, and it is expected (on 2026-10-18 the v0.28.3 pointers had exactly that shape). If a pointer does carry a `release_tag`, it must be `v0.28.4`. `install-channel` reads the box's own channel; a box whose channel names another release is refused ("this box already runs ...").
+
+The help check first, on leo: `/opt/carlos/carlos system install-channel -h`. It prints and exits before reading anything. Expected, exactly (as printed by the local v0.28.4 build, `~/.cache/release-v0.28.4/dist/carlos-linux-amd64`, built from `f3bf1214`, on 2026-10-18):
-New for v0.28.3, from the prev-rehearsal (main `50f5b268`). Before `guard-arm`, each box to be armed runs `carlos system install-channel --policy` as root, leo first, then sophie (the console box). On a box that `carlos system update` has already moved to its channel's release, it leaves `carlos` as it is and replaces `carlos.prev` (the release before, v0.28.2) with it, so `guard-arm`'s two-slot check passes. It takes the armed policy's floor as the floor, because none is published yet.
+```
+usage: carlos system install-channel [--policy FILE --identity FILE]
+
+Install the release this box's channel names, as root, on a box that has none yet.
+flags:
+ --identity <string> the release identity carlos system claims run publish-identity wrote, used with --policy in place of the channel's release tag
+ --policy <string> the armed policy carlos system claims run sign-policy wrote, used as the host floor until one is published
+```
-Before it, on each box: the channel pointer names v0.28.3, tag v0.28.3, with the identity's `carlos-linux-arm64` sha256 (leo `stable`, sophie `canary`; P3's `carlos system status`). `install-channel` reads the box's own channel, and a box whose channel names another release is refused ("this box already runs ..."). **Not met on dev on 2026-10-18:** the v0.28.3 pointers carry no `release_tag`, so this step would refuse with "the <channel> channel names v0.28.3 with no release tag, so nothing was installed". That is the open STOP in the Go package; platform-1e rules on it before the session opens.
+The usage line is RHB-C19, re-approved by Paul on 2026-10-18 (claim-untagged-pointer). All nine new or changed strings were approved verbatim the same day.
On leo, as root, then the same block on sophie:
@@ -1168,20 +1172,20 @@ set -a
set +a
/opt/carlos/carlos version
/opt/carlos/carlos.prev version
-/opt/carlos/carlos system install-channel --policy /root/claim-trial/policy.v0.28.3.json
+/opt/carlos/carlos system install-channel --policy /root/claim-trial/policy.v0.28.4.json --identity /root/claim-trial/identity.v0.28.4.json
/opt/carlos/carlos version
/opt/carlos/carlos.prev version
-/opt/carlos/carlos system install-channel --policy /root/claim-trial/policy.v0.28.3.json
-cmp /opt/carlos/system/router/channel-identity.json /root/claim-trial/identity.v0.28.3.json && echo identity-equal
+/opt/carlos/carlos system install-channel --policy /root/claim-trial/policy.v0.28.4.json --identity /root/claim-trial/identity.v0.28.4.json
+cmp /opt/carlos/system/router/channel-identity.json /root/claim-trial/identity.v0.28.4.json && echo identity-equal
```
-Expected: `carlos v0.28.3`, then `carlos v0.28.2`; then exactly
+Expected: `carlos v0.28.4`, then `carlos v0.28.3` (the release before, if the roll went v0.28.3 to v0.28.4); then exactly
```
-replaced the rollback copy carlos.prev with v0.28.3 (v0.28.3) from the <channel> channel, the release carlos already runs. the release carlos.prev held before is no longer on this box
+replaced the rollback copy carlos.prev with v0.28.4 (v0.28.4) from the <channel> channel, the release carlos already runs. the release carlos.prev held before is no longer on this box
```
-then `carlos v0.28.3` twice, then `v0.28.3 (v0.28.3) from the <channel> channel is already installed`, then `identity-equal`. `<channel>` is `stable` on leo and `canary` on sophie. Standard error may begin with the root AWS hardening line (`ALERT: root AWS file, profile, endpoint and trust-store environment overrides ignored`); judge the last line. Nothing is fetched, and `applied.json` and `seen.json` are unchanged.
+then `carlos v0.28.4` twice, then `v0.28.4 (v0.28.4) from the <channel> channel is already installed`, then `identity-equal`. `<channel>` is `stable` on leo and `canary` on sophie. The `(v0.28.4)` is the identity's tag; the pointer has none. Standard error may begin with the root AWS hardening line (`ALERT: root AWS file, profile, endpoint and trust-store environment overrides ignored`); judge the last line. Nothing is fetched, and `applied.json` and `seen.json` are unchanged.
Stop points: any other output. In particular:
- "this box already runs ..." (the channel names another release);
@@ -1191,15 +1195,21 @@ Stop points: any other output. In particular:
- "the --policy file is not an armed policy signed for this deployment";
- "the published host floor is not the floor in the --policy file";
- "an update on this box has not finished, so nothing was installed: ...";
-- "the record of the release in carlos.prev could not be written, so the install is not complete. run it again: ...". This is the one refusal that is retried: run the same command again, once, and stop if it fails again.
+- "the record of the release in carlos.prev could not be written, so the install is not complete. run it again: ...". This is the one refusal that is retried: run the same command again, once, and stop if it fails again;
+- "--policy and --identity go together, so nothing was installed" (exit 2): one of the two flags is missing. Nothing was read;
+- "the identity <path> could not be read, so nothing was installed: ...": the staged identity is missing, a symlink, not a regular file, or over 64 KiB;
+- "the --identity file is not a release identity signed for this deployment, so nothing was installed": the wrong file was staged (the policy, a pointer), or it is not signed by the deployment key;
+- "no release identity is published for v0.28.4, so nothing was installed": there is no object at `control/system/router/releases/v0.28.4.json` (6.1's publish did not happen, or the file names another tag);
+- "the --identity file is not the identity published for v0.28.4, so nothing was installed": the staged bytes are not the published object (re-saved, retyped, or another identity);
+- "the --identity file names ... not the release the <channel> channel names, so nothing was installed": the identity is for another release or artefact, or the pointer carries another tag. Check P3: the box's channel may not have rolled to v0.28.4.
-Without `--policy` the command refuses with "no host floor is published, so this box could not arm its guard and nothing was installed". That is the loop the first session would have hit, and it is expected; never run it that way here.
+Without `--policy` and `--identity` the command refuses with "no host floor is published, so this box could not arm its guard and nothing was installed". That is the loop the first session would have hit, and it is expected; never run it that way here.
-Losing the rollback copy is the point of this step. After it, neither box can go back to v0.28.2, and once armed, the guard would refuse v0.28.2 anyway (the floor is v0.28.3). Paul's go for step 6 covers it.
+Losing the rollback copy is the point of this step. After it, neither box can go back to v0.28.3, and once armed, the guard would refuse anything below v0.28.4 anyway (the floor is v0.28.4). Paul's go for step 6 covers it.
### 6.2 Arm leo
-On leo, right after 6.1a (both slots `carlos v0.28.3`):
+On leo, right after 6.1a (both slots `carlos v0.28.4`):
```bash
set -euo pipefail
@@ -1207,13 +1217,13 @@ cd /root/claim-trial
set -a
. /etc/carlos/host.env
set +a
-/opt/carlos/carlos system guard-arm --policy /root/claim-trial/policy.v0.28.3.json \
- --identity /root/claim-trial/identity.v0.28.3.json --enrollment /root/claim-trial/leo-climbing-enrollment.json
+/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
```
### 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/50f5b268/console`, the path 5.0a's `readlink` showed. Sophie's own `carlos` and `carlos.prev` must both be v0.28.3 (6.1a) before `guard-arm --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`.
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.
@@ -1235,14 +1245,14 @@ set +a
PUB=$(cat release.pub)
CID=$(cat console-id)
REV=$(cat rev)
-CONSOLE_SOURCE=/opt/carlos/versions/50f5b268/console
+CONSOLE_SOURCE=/opt/carlos/versions/f3bf1214/console
CONSOLE_BINARY=/opt/carlos/system/console/console
-/opt/carlos/carlos system claims install-console --identity /root/claim-trial/identity.v0.28.3.json --source "$CONSOLE_SOURCE" \
+/opt/carlos/carlos system claims install-console --identity /root/claim-trial/identity.v0.28.4.json --source "$CONSOLE_SOURCE" \
--bucket carlos-dev-640012953448 --aws-region eu-west-1 --public-key "$PUB" --dry-run
-/opt/carlos/carlos system claims install-console --identity /root/claim-trial/identity.v0.28.3.json --source "$CONSOLE_SOURCE" \
+/opt/carlos/carlos system claims install-console --identity /root/claim-trial/identity.v0.28.4.json --source "$CONSOLE_SOURCE" \
--bucket carlos-dev-640012953448 --aws-region eu-west-1 --public-key "$PUB"
-/opt/carlos/carlos system guard-arm --policy /root/claim-trial/policy.v0.28.3.json \
- --identity /root/claim-trial/identity.v0.28.3.json --enrollment /root/claim-trial/sophie-knitting-enrollment.json \
+/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"
/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
@@ -1263,11 +1273,11 @@ for b in leo-climbing sophie-knitting; do
done
```
-Pass: for each box, `generated_at` later than its `guard-arm`, release `v0.28.3`, `floor_guard_ready` `True` and `floor_sha256` `07d2b63e` (sign-policy's `floor_sha256`). Sophie's `console_processes` count is `0` until `console-report` runs.
+Pass: for each box, `generated_at` later than its `guard-arm`, release `v0.28.4`, `floor_guard_ready` `True` and `floor_sha256` `1a6a7e50` (sign-policy's `floor_sha256`). Sophie's `console_processes` count is `0` until `console-report` runs.
Expected:
- 6.2: `guard-arm` succeeds. It writes `/etc/carlos/host-actor.json` and installs `/etc/systemd/system/carlos-edge.service.d/90-host-floor.conf`, which step 8 has to handle.
-- `install-console`'s dry run, then its apply, print `release_tag` `v0.28.3`, a `sha256` equal to the identity's `console_artifacts.linux-arm64.sha256` (`3660b44c…ec74`), `path` `/opt/carlos/system/console/console`, `previous` `/opt/carlos/system/console/console.prev` and `mode` `0755`. Both copies are the same bytes, owned by root (`.prev` equals current at arming). It does not touch `console.service`. It refuses a staged identity that differs from the published one, an identity that does not name sophie's running build (sophie's `/opt/carlos/carlos` must be v0.28.3), and a source with a symlink on its path, more than one link, or bytes other than the identity's console sha256. A refusal is a stop.
+- `install-console`'s dry run, then its apply, print `release_tag` `v0.28.4`, a `sha256` equal to the identity's `console_artifacts.linux-arm64.sha256` (`8d49199f…5e9b`), `path` `/opt/carlos/system/console/console`, `previous` `/opt/carlos/system/console/console.prev` and `mode` `0755`. Both copies are the same bytes, owned by root (`.prev` equals current at arming). It does not touch `console.service`. It refuses a staged identity that differs from the published one, an identity that does not name sophie's running build (sophie's `/opt/carlos/carlos` must be v0.28.4), and a source with a symlink on its path, more than one link, or bytes other than the identity's console sha256. A refusal is a stop.
- `guard-arm --console` succeeds on sophie.
- `console-config` writes `/etc/carlos/claim-console.env` and `/etc/systemd/system/console.service.d/91-claim-runtime.conf`, reloads systemd and does not start anything.
- `console.service` is `active`. If a command refuses the revision because the roster moved, run `box_inspect "$SOPHIE"` again and repeat from `REV=$(cat rev)`.
@@ -1297,13 +1307,13 @@ Closing the signing session, 10:28Z to 10:31Z, in full:
Left as they are ("Stop and hold"): the roster at revision 5, the three invitations, the published identity (correct, and permanent under v0.28.2), and `policy.json` and `identity.json` staged in `/root/claim-trial` on leo and sophie. Resuming needs Paul's go for a new signing session, after a reviewed way to bring each box's `carlos.prev` to v0.28.2. A rerun of `sign-policy` and `publish-identity` with new `--out` paths should give the same bytes, and `publish-identity` writes nothing the second time.
-That reviewed way is `install-channel --policy` (main `50f5b268`, step 6.1a). It is not in v0.28.2, so the trial resumes on v0.28.3, with a new identity, a new floor and new staged files (step 6 above). The v0.28.2 identity and the first session's staged files stay as they are, unused.
+That reviewed way is `install-channel --policy` (main `50f5b268`, step 6.1a). It is not in v0.28.2, so the trial resumed on v0.28.3 and then, once the untagged channel pointers were found (blocker 7), on v0.28.4, with a new identity, a new floor and new staged files (step 6 above). The v0.28.2 identity and the first session's staged files stay as they are, unused.
## Step 7. Readers, floor, home and leo's key
Paul's go: once for step 7. `home_region` becomes write-once as soon as claims are on. In the second session the go must be in hand before 7.2 starts, because `publish-floor` cannot wait for one.
-1. **Readers, first.** Re-run P3, with the `carlos.prev` lines. Dennis is not in the trial, but its agent reads the roster, so it counts. Every reader must be on the trial release (v0.28.3) before the home designation is published, and leo's and sophie's `carlos.prev` must read `carlos v0.28.3` (6.1a). Do this before 7.2: nothing may run between `console-report` and `publish-floor`.
+1. **Readers, first.** Re-run P3, with the `carlos.prev` lines. Dennis is not in the trial, but its agent reads the roster, so it counts. Every reader must be on the trial release (v0.28.4) before the home designation is published, and leo's and sophie's `carlos.prev` must read `carlos v0.28.4` (6.1a). Do this before 7.2: nothing may run between `console-report` and `publish-floor`.
Then, as the last check before 7.2, confirm from DOCS-T1 that dennis's guard is unarmed, so the floor about to be published does not bind dennis's recovery (platform-1e's ruling, 2026-10-18). Dennis is not enrolled and is not in the census scope, and `install-channel` is never run on it. The check runs here, and not between `console-report` and `publish-floor`, because nothing may run between those two. It is read-only:
@@ -1311,15 +1321,15 @@ Paul's go: once for step 7. `home_region` becomes write-once as soon as claims a
$TMPDIR/cohort1/ssm.sh eu-west-2 i-009d593f451cfd562 'p=/opt/carlos/system/guard/policy.json; if [ -e $p ]; then echo "present $p $(grep -oE "\"mode\" *: *\"[^\"]*\"" $p | head -1)"; else echo "absent $p"; fi; [ -e /etc/carlos/host-actor.json ] && echo "present /etc/carlos/host-actor.json" || echo "absent /etc/carlos/host-actor.json"; /opt/carlos/carlos version'
```
- Pass, exactly (the version line reads `carlos v0.28.3` after the roll):
+ Pass, exactly (the version line reads `carlos v0.28.4` after the v0.28.4 roll):
```
Success absent /opt/carlos/system/guard/policy.json
absent /etc/carlos/host-actor.json
- carlos v0.28.3
+ carlos v0.28.4
```
- Run read-only on 2026-10-18 at about 14:45Z, before the roll: `Success`, both `absent`, `carlos v0.28.2`. A `present /opt/carlos/system/guard/policy.json` line is a STOP whatever its mode, and so is status `Failed` or any other output: do not start 7.2, report to platform-1e, and close the session if they say so.
+ Run read-only on 2026-10-18 at about 14:45Z, before the v0.28.3 roll: `Success`, both `absent`, `carlos v0.28.2`; and at about 15:45Z, after it: both `absent`, `carlos v0.28.3`. A `present /opt/carlos/system/guard/policy.json` line is a STOP whatever its mode, and so is status `Failed` or any other output: do not start 7.2, report to platform-1e, and close the session if they say so.
2. **Report and floor, back to back. HARD ORDERING: `console-report` is followed IMMEDIATELY by `publish-floor`.** The console's evidence in sophie's box report lasts only until the agent's next status tick, about 5 minutes (#312). Before starting, check that both boxes' reports carry `floor_guard_ready` (6.3's check), and have the `publish-floor` command ready to send. For the widest window, start just after sophie's report's `generated_at` moves.
On the trusted host, stage sophie's current roster and revision (`box_inspect "$SOPHIE"`). Then on sophie:
@@ -1347,7 +1357,7 @@ Paul's go: once for step 7. `home_region` becomes write-once as soon as claims a
refresh
```
- Expected: `console-report` publishes; `publish-floor` (dry run, then apply) writes the floor and its authorization (`control/moves-protocol.json` and `control/moves-protocol-auth.json`) with the floor digest `07d2b63e…aa09` from 6.1, and moves the roster to a new revision (four writes in the rehearsal); `home` takes the next revision.
+ Expected: `console-report` publishes; `publish-floor` (dry run, then apply) writes the floor and its authorization (`control/moves-protocol.json` and `control/moves-protocol-auth.json`) with the floor digest `1a6a7e50…4ab3` from 6.1, and moves the roster to a new revision (four writes in the rehearsal); `home` takes the next revision.
If `console-report` refuses because sophie's agent has not yet reported the armed guard, wait for the next report (6.3's check) and run it again. Never refresh timestamps by hand. If `publish-floor` is refused because the agent's own report has replaced the console process entry, run `box_inspect "$SOPHIE"`, `console-report` again on sophie, and `publish-floor` again at once. A second refusal is a stop. If a command refuses the revision because the roster moved, run `box_inspect "$SOPHIE"` again and repeat from `REV=$(cat rev)`.
3. **Leo's evidence key.** On the trusted host, stage leo's roster, revision and enrollment ID:
@@ -2193,7 +2203,7 @@ What must not be touched while leo is held:
- `/etc/carlos/host-actor.json`, `/etc/carlos/host-evidence.key`, `/etc/carlos/fence-writer/` and `/opt/carlos/system/guard`;
- `/opt/carlos/carlos`, `/opt/carlos/carlos.prev` and anything staged: no `carlos system update`, rollback or old-road roll, and no `stable`-channel roll while leo is held, because it is on `stable` with dennis;
- `/data`, including `registry.db`, `/data/instances` and any parked database;
-- sophie's protected console (`/opt/carlos/system/console/console` and `console.prev`), both published identities (`control/system/router/releases/v0.28.2.json`, unused, and `control/system/router/releases/v0.28.3.json`), every staged `policy*.json` and `identity*.json`, `/opt/carlos/system/router/channel-identity.json`, and the `bab` pointer-digest marker;
+- sophie's protected console (`/opt/carlos/system/console/console` and `console.prev`), every published identity (`control/system/router/releases/v0.28.2.json`, unused; `control/system/router/releases/v0.28.4.json`; and `v0.28.3.json` if one ever appears, unused), every staged `policy*.json` and `identity*.json`, `/opt/carlos/system/router/channel-identity.json`, and the `bab` pointer-digest marker;
- the roster, floor and enrollments, and the claim's S3 objects: `control/hosts/`, `control/host-actors.json`, `control/host-origins/`, admitted prefixes, and `instances/` in `carlos-dev-ie-640012953448`;
- no `systemctl reset-failed` before the failed results are recorded, no reboot unless the edge is persistently masked, no rerun of `journal-migrate`, `install-control` or the seed, and no console `reset-wake-retry`.
@@ -2221,7 +2231,7 @@ Leaving hold is a separate decision: a fix and resume, or a claims-off teardown
**H10. The console's evidence is short-lived (#312).** The agent writes sophie's box report whole at each status tick (5 minutes plus jitter), so the console process entry `console-report` adds is gone at the next tick. `publish-floor` must follow `console-report` at once (step 7.2). Anything later that reads the console's compute evidence sees it vanish within about 6 minutes.
-**H11. `install-channel --policy` drops the rollback.** Step 6.1a replaces `carlos.prev` (v0.28.2) with v0.28.3 on leo and sophie. Afterwards neither box can roll back to v0.28.2, and once armed, the guard refuses anything below the v0.28.3 floor anyway. A broken v0.28.3 on those boxes is fixed forward with a new release, never by a rollback.
+**H11. `install-channel --policy --identity` drops the rollback.** Step 6.1a replaces `carlos.prev` (v0.28.3) with v0.28.4 on leo and sophie. Afterwards neither box can roll back to v0.28.3, and once armed, the guard refuses anything below the v0.28.4 floor anyway. A broken v0.28.4 on those boxes is fixed forward with a new release, never by a rollback.
## Later option: carlos-dev-uk
@@ -2243,6 +2253,7 @@ For platform-1e:
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` (census ruled), on main 7c1beb2d, published (v0.28.2, tag on 7c1beb2d): `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` for the second session: the second signer's IDs (the v0.28.3 values are confirmed, 2026-10-18). The console enrollment token was generated in the first session and is in the roster. Namespace `platform`, sophie's pool `console`, console ID `dev-console` and operator `paul@keymail.dev` are confirmed.
+7. Still open in `docs/superpowers/notes/claim-trial-inputs/README.md` for the second session: the v0.28.4 release values (TO-CONFIRM until platform-1e posts "v0.28.4 on dev") and the second signer's IDs. The console enrollment token was generated in the first session and is in the roster. 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.
-8. Answered 2026-10-18 by platform-1e: dennis's `carlos.prev` at v0.28.2 passes P3 (P3 is about reader versions against the floor; dennis is not enrolled and is outside the census scope). Dennis never runs 6.1a. Step 7.1 checks read-only that its guard is unarmed.
+8. Answered 2026-10-18 by platform-1e: dennis's `carlos.prev` (the release before) passes P3 (P3 is about reader versions against the floor; dennis is not enrolled and is outside the census scope). Dennis never runs 6.1a. Step 7.1 checks read-only that its guard is unarmed.
+9. Answered 2026-10-18 (Paul, option (b)): the untagged channel pointer is met by `install-channel --policy --identity` (main `f3bf1214`, v0.28.4), not by tagging the pointer. Blocker 7.