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>
3 files changed,
+61
−24
docs/superpowers/notes/claim-trial-inputs/README.md+4 −4docs/superpowers/notes/claim-trial-inputs/signing-host.md+1 −1docs/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: |