carlos / platform Public

docs: cohort-1 5.0a and signer scope take platform-1e's answers

- signer scope accepted: WriteIdentity is the exact key
  control/system/router/releases/v0.28.2.json, not a prefix, stated in
  signing-host.md and step 6.1; Paul approves it with the signer host
- 5.0a step 1 split into read-only checks: 1a the console's account
  and channel (bab, canary/console), an explicit STOP if they differ;
  1b every bab app on dev with its channel pointers (bucket listing and
  carlos channels per app); 1d every session or CI job that promotes
  into dev bab, any writer older than v0.26.0 a STOP
- digests on for all of bab accepted in principle, Paul decides at
  5.0a's go; moving the console to its own account is out of scope
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 65584bbba879f80ec72c86491567dce829f7bfe9 parent d8f56eb
3 files changed, +61 −24
  • docs/superpowers/notes/claim-trial-inputs/README.md +4 −4
  • docs/superpowers/notes/claim-trial-inputs/signing-host.md +1 −1
  • docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md +56 −19

CI passed — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.012s
?   	amadan.net/carlos/platform.git/launchctx/controlname	[no test files]
?   	amadan.net/carlos/platform.git/launchctx/wireid	[no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m
ok  	github.com/carlosframework/platform	309.739s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	3.077s
ok  	github.com/carlosframework/platform/cmd/moveprobe	92.285s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.700s
ok  	github.com/carlosframework/platform/internal/activator	250.176s
ok  	github.com/carlosframework/platform/internal/agentsock	0.015s
ok  	github.com/carlosframework/platform/internal/alerting	0.004s
ok  	github.com/carlosframework/platform/internal/analytics	0.019s
ok  	github.com/carlosframework/platform/internal/appconfig	2.504s
ok  	github.com/carlosframework/platform/internal/apperr	0.075s
ok  	github.com/carlosframework/platform/internal/applog	0.127s
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.617s
?   	github.com/carlosframework/platform/internal/boxid/boxidtest	[no test files]
?   	github.com/carlosframework/platform/internal/boxid/internal/signtest	[no test files]
ok  	github.com/carlosframework/platform/internal/boxvolumes	0.008s
ok  	github.com/carlosframework/platform/internal/caps	0.013s
ok  	github.com/carlosframework/platform/internal/certwarm	0.002s
ok  	github.com/carlosframework/platform/internal/cgroup	0.694s
ok  	github.com/carlosframework/platform/internal/changelog	0.308s
ok  	github.com/carlosframework/platform/internal/claimactivation	0.028s
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	405.925s
?   	github.com/carlosframework/platform/internal/controlname	[no test files]
?   	github.com/carlosframework/platform/internal/controlsock	[no test files]
ok  	github.com/carlosframework/platform/internal/dbdoor	6.140s
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.002s
ok  	github.com/carlosframework/platform/internal/docsite	0.005s
ok  	github.com/carlosframework/platform/internal/domains	2.427s
ok  	github.com/carlosframework/platform/internal/doorkey	0.007s
ok  	github.com/carlosframework/platform/internal/economics	0.004s
ok  	github.com/carlosframework/platform/internal/edge	44.929s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.009s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.133s
ok  	github.com/carlosframework/platform/internal/gate	1.656s
ok  	github.com/carlosframework/platform/internal/genselect	0.016s
ok  	github.com/carlosframework/platform/internal/geo	0.014s
ok  	github.com/carlosframework/platform/internal/geoip	0.030s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.155s
ok  	github.com/carlosframework/platform/internal/host	174.275s
ok  	github.com/carlosframework/platform/internal/hostactors	0.475s
ok  	github.com/carlosframework/platform/internal/hostbox	2.710s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.561s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.015s
ok  	github.com/carlosframework/platform/internal/hostexec	4.738s
ok  	github.com/carlosframework/platform/internal/hostfence	0.031s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.377s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.015s
ok  	github.com/carlosframework/platform/internal/hostguard	1.035s
ok  	github.com/carlosframework/platform/internal/hostops	1.658s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.014s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.014s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.476s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.159s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.016s
ok  	github.com/carlosframework/platform/internal/infraview	0.014s
ok  	github.com/carlosframework/platform/internal/instancectl	5.228s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.749s
ok  	github.com/carlosframework/platform/internal/instancefs	0.019s
ok  	github.com/carlosframework/platform/internal/instancekey	0.016s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.007s
ok  	github.com/carlosframework/platform/internal/layout	1.538s
ok  	github.com/carlosframework/platform/internal/lbhealth	0.002s
ok  	github.com/carlosframework/platform/internal/lease	9.857s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.006s
ok  	github.com/carlosframework/platform/internal/ledger	0.012s
ok  	github.com/carlosframework/platform/internal/mail	0.160s
ok  	github.com/carlosframework/platform/internal/maintenance	0.080s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.503s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.077s
ok  	github.com/carlosframework/platform/internal/metrics	0.009s
ok  	github.com/carlosframework/platform/internal/moves	20.967s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.807s
ok  	github.com/carlosframework/platform/internal/objstore	0.190s
ok  	github.com/carlosframework/platform/internal/ops	0.202s
ok  	github.com/carlosframework/platform/internal/park	0.193s
ok  	github.com/carlosframework/platform/internal/privatefile	0.003s
ok  	github.com/carlosframework/platform/internal/provision	23.440s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.008s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	5.401s
ok  	github.com/carlosframework/platform/internal/relay	7.233s
ok  	github.com/carlosframework/platform/internal/release	21.768s
ok  	github.com/carlosframework/platform/internal/rollout	3.628s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.003s
ok  	github.com/carlosframework/platform/internal/rootaws	0.007s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.057s
ok  	github.com/carlosframework/platform/internal/rootstate	0.027s
ok  	github.com/carlosframework/platform/internal/runs	0.580s
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.496s
ok  	github.com/carlosframework/platform/internal/seeds	0.011s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	8.774s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	4.046s
ok  	github.com/carlosframework/platform/internal/status	0.038s
ok  	github.com/carlosframework/platform/internal/steering	0.005s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.213s
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.004s
ok  	github.com/carlosframework/platform/internal/version	0.001s
ok  	github.com/carlosframework/platform/internal/vet	1.558s
=== 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.023s
=== RUN   TestT4StorageProbeMinio
2026/10/18 06:49:20 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DF8CF5149A2B84, 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 7dfb56b65..7a70f68d4 100644
--- a/docs/superpowers/notes/claim-trial-inputs/README.md
+++ b/docs/superpowers/notes/claim-trial-inputs/README.md
@@ -49,8 +49,8 @@ Platform main at drafting: `b1118925` (region HA 2b). The box-record verb is on
| Release for step 6 onwards | v0.28.2 (`claims run` phases `sign-policy` and `publish-identity`, `claims run --out`, `claims install-console`) | pending v0.28.2 (branch `claim-step6-artifacts` @ `c38a4ca9`) |
| Router linux-arm64 sha256 (v0.28.2) | the `carlos-linux-arm64` line of the v0.28.2 release's signed `SHA256SUMS.txt`, read by `publish-identity` from the sums text in its plan. Never typed | TO-CONFIRM when published |
| Dev console build for v0.28.2 | built from the v0.28.2 tag with `-ldflags "-s -w -X github.com/carlosframework/platform/internal/version.Build=v0.28.2"`: the release tag, not the short sha. `publish-identity` reads the stamp and refuses any other; the console's floor report compares it with the identity's version, and the identity is create-only per tag. Shipped as version `<short sha of v0.28.2's commit>` and promoted to `canary/console` after pointer digests are on (runbook 5.0a). `roll-console.sh` can't build it: it stamps the short sha | TO-CONFIRM: built in 5.0a |
-| Console account, app and channel | `bab`, `console`, `canary/console` (pointer `apps/bab/console/channels/canary/console.json`) | TO-CONFIRM by 5.0a step 1's read-only checks. Flagship's console is `bab`, `canary/console`, and direct-bucket ships default to the ops account |
-| Pointer digests for `bab` | off today; must be on, and `canary/console`'s pointer must carry digests, before `publish-identity`. `carlos channels digests on --account-id bab`, then a fresh console promote (runbook 5.0a, its own Paul's go, outside any roll) | TO-DO: 5.0a |
+| Console account, app and channel | `bab`, `console`, `canary/console` (pointer `apps/bab/console/channels/canary/console.json`) | TO-CONFIRM by runbook 5.0a 1a's read-only check. If the console's pointer is in any other account or channel, that is a STOP: the values are not adapted, it is reported on #250. Flagship's console is `bab`, `canary/console`, and direct-bucket ships default to the ops account. Moving the console to its own account is out of scope for the trial |
+| Pointer digests for `bab` | off today; must be on, and `canary/console`'s pointer must carry digests, before `publish-identity`. `carlos channels digests on --account-id bab`, then a fresh console promote (runbook 5.0a, its own Paul's go, outside any roll). It covers every `bab` app on dev; platform-1e accepts that in principle and Paul decides at the go, with 5.0a's listing of every `bab` app's pointers and every writer. A writer older than v0.26.0 is a STOP | TO-DO: 5.0a |
| Console linux-arm64 sha256 (v0.28.2) | read by `publish-identity`: the `canary/console` pointer's signed manifest digest for the console version, then that manifest's `linux-arm64` sha256, then the artefact itself, hashed and stamp-checked. Never typed by hand, and never taken from `SHA256SUMS.txt`: the phase refuses a console sha256 that is any file in the sums | TO-CONFIRM: equals the `sha256sum` of 5.0a's build |
| Router linux-arm64 sha256, v0.28.0 (current, for comparison) | `3ca7c74d5356a3995ecf677d142bc32fafaf790a2cc1bc0b628f3eec1b7ea731`, from the signed dev `control/system/router/stable.json` | confirmed |
| Console executable on sophie | fixed at `/opt/carlos/system/console/console`, with `console.prev` beside it (same bytes at arming), both root:root `0755`, created by `carlos system claims install-console` on sophie from the build 5.0a promoted (`--source /opt/carlos/versions/<CV>/console`). `guard-arm --console` takes this path. Replaces today's `/opt/carlos/versions/26065378/console`, which has no `.prev` | pending v0.28.2 (branch `claim-step6-artifacts` @ `c38a4ca9`) |
@@ -65,8 +65,8 @@ Platform main at drafting: `b1118925` (region HA 2b). The box-record verb is on
|---|---|---|---|
| `sha256sums` | the whole text of `SHA256SUMS.txt`, final newline included | yes, byte for byte | the v0.28.2 public release, `https://github.com/carlosframework/releases/releases/download/v0.28.2/SHA256SUMS.txt` |
| `sha256sums_sig` | the whole text of `SHA256SUMS.txt.sig` | yes, byte for byte | the same release |
-| `console.account` | `bab` | yes | 5.0a step 1 |
-| `console.channel` | `canary/console` | yes | the channel 5.0a promoted to |
+| `console.account` | `bab` | yes | checked in 5.0a 1a; a STOP if it differs |
+| `console.channel` | `canary/console` | yes | the channel 5.0a promoted to, checked in 5.0a 1a; a STOP if it differs |
| `console.version` | the console's ship version, the short sha of v0.28.2's commit | yes | 5.0a's record (`CV`) |
| `console.app` | `console` | yes | the console app |
| `release_tag` | `v0.28.2` | yes | the release tag |
diff --git a/docs/superpowers/notes/claim-trial-inputs/signing-host.md b/docs/superpowers/notes/claim-trial-inputs/signing-host.md
index 0b3ffb29c..b1b64892a 100644
--- a/docs/superpowers/notes/claim-trial-inputs/signing-host.md
+++ b/docs/superpowers/notes/claim-trial-inputs/signing-host.md
@@ -79,7 +79,7 @@ Why each statement:
- **ListForNotFound:** without `s3:ListBucket`, S3 answers a missing key with 403 instead of 404, and the runner's first `initialize` reads a roster that doesn't exist yet.
- **WriteRosterAndFloor:** these are exactly the runner's write allowlist (`internal/claimactivation/runner.go:89-104`).
- **ReadControl:** covers the reads for invitations, box records and floor evidence. Confirm it with a `--dry-run` of every phase from the signer, and widen only on a refusal.
-- **ReadConsoleRelease and WriteIdentity** (added 2026-10-18 for step 6.1, pending v0.28.2, branch `claim-step6-artifacts` @ `c38a4ca9`): `publish-identity` reads the console's `canary/console` pointer, manifest and artefact under `apps/bab/console/`, and writes one object, the identity at `control/system/router/releases/v0.28.2.json`, create-only. `sign-policy` writes nothing. The runner admits only that one identity key for the phase, whatever IAM allows.
+- **ReadConsoleRelease and WriteIdentity** (added 2026-10-18 for step 6.1, pending v0.28.2, branch `claim-step6-artifacts` @ `c38a4ca9`): `publish-identity` reads the console's `canary/console` pointer, manifest and artefact under `apps/bab/console/`, and writes one object, the identity at `control/system/router/releases/v0.28.2.json`, create-only. `WriteIdentity` names that exact key, not a prefix: no wildcard, so the role can write no other release's identity. `sign-policy` writes nothing. The runner admits only that one identity key for the phase, whatever IAM allows. platform-1e accepted this scope as proposed on 2026-10-18; Paul approves it together with the signer host.
**The release-key deny doesn't block this role.** That deny is the inline policy `deny-release-key-reads` on the box roles only (`carlos-dev-host`, `carlos-dev-host-b`). Dev-uk's role is limited by its own eu-west-2 scope. There is no account-wide deny. A policy simulation of this `ReleaseKey` statement returns `allowed` (2026-10-18). Because any role granted `ssm:GetParameter` on that ARN can read the key, the role exists only for the trial and is deleted afterwards. CloudTrail is checked so that every `GetParameter` on `/carlos/release-key` comes from a `carlos-claim-signer` session.
diff --git a/docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md b/docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md
index 79c31e593..880c53d28 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
@@ -384,16 +384,44 @@ Runs after v0.28.2 is on dev's boxes and checked, and before the signing session
Why it is needed: `publish-identity` (step 6) takes the console's sha256 only from the console channel's root-verified pointer. It reads the pointer's signed manifest digest for the exact console version, then that manifest, then the artefact, whose `version.Build` stamp must be `v0.28.2`. Dev's `canary/console` pointer carries no digests today, so the phase would refuse it. The console's own floor report compares its build stamp with the identity's version, and the identity is create-only per tag, so a console stamped with the short sha cannot be put right later under v0.28.2.
-Scope: the digest marker is per account. The console app lives in account `bab` (`apps/bab/console/`), alongside wakecheck, claimtrial and the other dev test apps. With digests on for `bab`, every upgraded writer signs digests into every `bab` pointer it writes. A box that has verified a pointer with digests refuses later pointers for that channel without them (README, "Pointer digests (#253): writers before readers").
+Scope: the digest marker is per account. The console app lives in account `bab` (`apps/bab/console/`), alongside wakecheck, claimtrial and the other dev test apps. With digests on for `bab`, every upgraded writer signs digests into every `bab` pointer it writes. A box that has verified a pointer with digests refuses later pointers for that channel without them (README, "Pointer digests (#253): writers before readers"). platform-1e accepts digests on for all of `bab` on dev in principle (2026-10-18); Paul decides at this step's go, with step 1's listings in front of him. Moving the console to an account of its own is out of scope for the trial.
Values, on DOCS-T1:
- `CARLOS`: the carlos v0.28.2 CLI for DOCS-T1, downloaded from the public release and checked against the release's signed `SHA256SUMS.txt`, the same way `signing-host.md` checks the signer's copy.
- `CV`: the console's ship version, the short sha of v0.28.2's commit. `carlos ship` compares a re-ship with the stored manifest byte for byte and refuses different bytes, so `CV` must not have been shipped for `console` before. If it has, stop and ask. Never make up another version.
- `release.pub`: the dev release public key (fingerprint `960d03ff`), the same file 5.1 stages.
-**1. Writers before readers (read-only).** Every writer that can promote into `bab` on dev must sign digests before the marker goes on. Digest signing first shipped in v0.26.0 (#253).
+**1. Read-only checks.** Every writer that can promote into `bab` on dev must sign digests before the marker goes on. Digest signing first shipped in v0.26.0 (#253). Any writer older than v0.26.0 is a STOP.
-The dev console is on v0.28.x:
+Set up the read-only environment once (no key):
+
+```bash
+export AWS_PROFILE=kass AWS_REGION=eu-west-1 CARLOS_DEPLOYMENT_BUCKET=carlos-dev-640012953448 CARLOS_RELEASE_PUBKEY="$(cat release.pub)"
+```
+
+**1a. The console's account and channel.** The inputs README, step 6.1's identity plan and the rollback all assume account `bab`, app `console`, channel `canary/console`.
+
+```bash
+aws s3 ls --recursive s3://carlos-dev-640012953448/apps/ | awk '{print $4}' | grep -E '^apps/[^/]+/console/channels/'
+"$CARLOS" channels --app console --account-id bab
+```
+
+Pass: the listing shows exactly one key, `apps/bab/console/channels/canary/console.json`, and `carlos channels` shows `canary/console` serving the version the console checks below record. STOP if the console's pointer is in any other account or channel, or if there is more than one: don't adapt the values, report on #250 and wait for platform-1e and Paul.
+
+**1b. Every `bab` app on dev and its current channel pointers.** This is what digests on for `bab` reaches:
+
+```bash
+aws s3 ls --recursive s3://carlos-dev-640012953448/apps/bab/ | awk '{print $4}' \
+ | grep -E '^apps/bab/[^/]+/channels/.+\.json$' | sort
+for app in $(aws s3 ls s3://carlos-dev-640012953448/apps/bab/ | awk '$1 == "PRE" {print $2}' | tr -d / | grep .); do
+ echo "== $app"
+ "$CARLOS" channels --app "$app" --account-id bab
+done
+```
+
+The first command lists every pointer object; the loop prints, per app, each channel's version, promotion time, tag and whether its pointer carries digests. `grep .` drops the empty `apps/bab//` segment if step 2 has not removed it yet. Record the output for Paul's go. A recent `PROMOTED` time on any channel means a writer is active there; it must appear in 1d.
+
+**1c. The dev console is on v0.28.x:**
```bash
curl -sI https://platform.dev.carlosframework.com/ | grep -i x-carlos-version
@@ -402,20 +430,27 @@ $TMPDIR/cohort1/ssm.sh eu-west-1 i-05498eada6f0afd23 'readlink /proc/$(systemctl
Pass: the header names a v0.28.x commit (`26065378` for v0.28.0, `3b69c2bd` for v0.28.1, or v0.28.2's), and the running executable is `/opt/carlos/versions/<that version>/console`. Expected today: `3b69c2bd`. Record it as the rollback version. Promotes made through the console are signed by the console, which reads the marker itself, so this check covers every console-mode CLI and CI job.
-Every CLI that promotes direct-bucket on dev understands digests:
-- On DOCS-T1: `"$CARLOS" version` prints `carlos v0.28.2`; it is the `CARLOS_BIN` for `ship-app.sh` below. `carlos version` (the copy on `PATH`) prints v0.26.0 or later (P3 saw v0.27.2 on 2026-10-17).
-- On the boxes: P3's box line shows v0.28.2 on leo, sophie and dennis after the roll. A box CLI is a writer only if someone promotes from the box.
-- Everyone else with dev bucket credentials who promotes direct-bucket into `bab`: platform-d8 (H2) and any other agent or CI job. Ask on #250 and wait for a clear answer from each: `carlos version` is v0.26.0 or later, or they will not promote on dev until this step is done.
+**1d. Every session or CI job that promotes into dev `bab`.** Each one is checked, and any writer older than v0.26.0 is a STOP:
+
+| Writer | How it promotes | Check |
+|---|---|---|
+| The dev console | console-mode promotes, rollbacks and fast-tracks, including any CLI or CI job that goes through `platform.dev.carlosframework.com` (the console signs those pointers and reads the marker itself) | 1c |
+| Operator sessions on DOCS-T1 (Paul, and the agent sessions running this runbook) | direct-bucket: `ship-app.sh`, `carlos promote`, `carlos rollback` | `"$CARLOS" version` prints `carlos v0.28.2` (it is the `CARLOS_BIN` for 4 below); `carlos version` on `PATH` prints v0.26.0 or later (P3 saw v0.27.2 on 2026-10-17) |
+| platform-1e | direct-bucket, for the trial's `claimtrial` (step 4) and any other trial ship | asks itself; `carlos version` v0.26.0 or later |
+| platform-d8 (H2) | direct-bucket ships of its zero-downtime test apps | ask on #250 |
+| Box CLIs on leo, sophie and dennis | only if someone promotes from a box | P3's box line: v0.28.2 after the roll |
+| Any other agent or person with `kass` or another dev credential | direct-bucket | ask on #250 |
+| CI | none known. The platform repo's own CI (`.amadan/ci.d/`: `10-gate`, `20-cross`, `30-minio-contract`) does not deploy to dev: nothing on main names the dev bucket or the dev console in CI. Any CI job elsewhere that does is added here | ask on #250 |
-The state before:
+Ask on #250 and wait for a clear answer from each writer that is asked: its `carlos version` is v0.26.0 or later, or it will not promote on dev until this step is done. Any writer from 1b's recent promotions that is missing from this table is found and checked before the go.
+
+**1e. The digest state before:**
```bash
-AWS_PROFILE=kass AWS_REGION=eu-west-1 CARLOS_DEPLOYMENT_BUCKET=carlos-dev-640012953448 CARLOS_RELEASE_PUBKEY="$(cat release.pub)" \
- "$CARLOS" channels digests --account-id bab
-aws --profile kass s3 ls s3://carlos-dev-640012953448/apps/bab/console/channels/canary/
+"$CARLOS" channels digests --account-id bab
```
-Expected: `pointer digests: off for bab`, then a table of the `bab` channels without digests, including `bab console canary/console <rollback version> no`. The listing shows `console.json`. Any other account for the console is a stop: the inputs README's console release values assume `bab`.
+Expected: `pointer digests: off for bab`, then a table of the `bab` channels without digests, including `bab console canary/console <rollback version> no`.
**2. Build the console (local only).** From a clean checkout at the v0.28.2 tag:
@@ -438,7 +473,7 @@ CARLOS_RELEASE_KEY=$(aws ssm get-parameter --name /carlos/release-key --with-dec
"$CARLOS" channels digests on --account-id bab
```
-The key is read the way `ship-app.sh` reads it, and never printed. Expected, verbatim from main: `pointer digests on for bab. Upgraded writers sign digests from now on`, then `pointer digests: on for bab since <time>`, then the same table as in 1. Existing pointers get digests at their next promote.
+The key is read the way `ship-app.sh` reads it, and never printed. Expected, verbatim from main: `pointer digests on for bab. Upgraded writers sign digests from now on`, then `pointer digests: on for bab since <time>`, then the same table as in 1e. Existing pointers get digests at their next promote.
**4. Promote the release-stamped console.**
@@ -449,9 +484,9 @@ CARLOS_BIN=$CARLOS platform-infrastructure/scripts/ship-app.sh kass carlos-dev-6
Sophie stages, adopts and restarts `console.service` on its own, in about 20 seconds, so the dev console is briefly down.
Expected:
-- `x-carlos-version` (as in 1) shows `$CV`;
-- the `readlink` from 1 shows `/opt/carlos/versions/$CV/console`. That path is `install-console`'s `--source` in step 6;
-- `channels digests --account-id bab` (as in 1) no longer lists `console canary/console`, and no row says `written after digests were turned on`. Such a row means an old writer is still in use: stop and report on #250;
+- `x-carlos-version` (as in 1c) shows `$CV`;
+- the `readlink` from 1c shows `/opt/carlos/versions/$CV/console`. That path is `install-console`'s `--source` in step 6;
+- `channels digests --account-id bab` (as in 1e) no longer lists `console canary/console`, and no row says `written after digests were turned on`. Such a row means an old writer is still in use: stop and report on #250;
- wakecheck still answers 200 through DNS.
Finish this step before the signing session opens. Its `GetParameter` reads come from `remote-agent`, and the CloudTrail check at the session's close counts from the session's start.
@@ -466,7 +501,7 @@ Rollback:
"$CARLOS" promote -app console <rollback version> canary/console
```
- Then check `x-carlos-version` and the `readlink` as in 1. `carlos channels digests off` is only for a broken writer (README), and it does not clear boxes that have already seen digests.
+ Then check `x-carlos-version` and the `readlink` as in 1c. `carlos channels digests off` is only for a broken writer (README), and it does not clear boxes that have already seen digests.
## The signing session: 5.1, 6 and 7
@@ -673,8 +708,8 @@ Expected: the dry run, then the apply, succeed; `writes` is empty, because the p
|---|---|---|
| `identity.sha256sums` | the exact text of `SHA256SUMS.txt`, final newline included | the v0.28.2 public release: `https://github.com/carlosframework/releases/releases/download/v0.28.2/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 step 1 |
-| `identity.console.channel` | `canary/console` | the channel 5.0a promoted to |
+| `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` | `$CV` | the console's ship version from 5.0a (the short sha of v0.28.2's commit) |
| `identity.console.app` | `console` | the console app |
| `identity.release_tag` | `v0.28.2` | the release tag |
@@ -696,6 +731,8 @@ Expected:
- `identity_key` is that key, and `release` shows `version` and `release_tag` `v0.28.2`, `moves_protocol` `1`, `artifacts.linux-arm64.sha256` equal to the `carlos-linux-arm64` line of `SHA256SUMS.txt`, and `console_artifacts.linux-arm64.sha256` equal to the `sha256sum` of 5.0a's build;
- `identity.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.2.json`, an exact key and not a prefix (`signing-host.md`, statement `WriteIdentity`). platform-1e accepted that scope on 2026-10-18; Paul approves it together with the signer host. 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.2 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.2.json`); it writes nothing to the bucket, and its file must have the same sha256.
Copy both files to DOCS-T1 through `s3://carlos-dev-640012953448/claim-signer/<run-id>/` with `aws s3 cp`, and record each sha256 on both sides. `box_put` checks the same sha256 on arrival. Stage them from DOCS-T1: