| 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. |