| index 86583ad10..c99ed37f9 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,36 +1,42 @@ |
| # 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)). 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) |
| - |
| -Prepared on 2026-10-18 for platform-1e to present. v0.28.2 is published (tag on `7c1beb2d`) and on leo, sophie and dennis (current, 0 refusing). The dev console is held at `3b69c2bd` until 5.0a. Everything below was checked read-only today; nothing was changed on any box or bucket. |
| - |
| -**v0.28.2 values** |
| -- carlos `linux-arm64` sha256 `7141e037d61b0d95ae554252af1bb9155653a9954ad0dcaffb35c7047850f492`, from the signed `SHA256SUMS.txt` (signature verified with the README's pinned key). It equals the router artefact in dev's signed `stable.json` and `canary.json`. |
| -- carlos `linux-amd64` (the operator CLI for 5.0a) sha256 `303f6522ac3a03529c3d08b0e1cd2751d94c68293355db3ed67c1a5b9049a808`. |
| -- `SHA256SUMS.txt` sha256 `9c29b3d305c464cf981afffa0bc51f1654015883c02717477f38357709d46db2` (704 bytes, lists `carlos_0.28.2_amd64.deb` and `carlos_0.28.2_arm64.deb`); `SHA256SUMS.txt.sig` sha256 `0fdb855730c857f017cb3ca2908739b4c0695f7e712f155596c63a863edbc6c0`. `publish-identity` carries both verbatim. |
| -- Router identity, as `publish-identity` should produce it: `version` and `release_tag` `v0.28.2`, `moves_protocol` `1`, `artifacts.linux-arm64` = `bootstrap/router/v0.28.2/carlos-linux-arm64` with the arm64 sha256 above, `console_artifacts.linux-arm64` = `bootstrap/console/v0.28.2/carlos-linux-arm64` with the console sha256 below. Key `control/system/router/releases/v0.28.2.json` (not yet written). |
| -- Console: the prebuilt `~/.cache/release-v0.28.2/console-linux-arm64`, sha256 `0827341051360e888ed4f6df568a77ebb070e6ad16a7c87a2947a02dbd552ff4`, stripped, `cmd/console`, stamped `version.Build=v0.28.2` once (checked without executing it). Ship version `7c1beb2d`, not shipped yet. Never rebuild it. |
| -- Floor: `{"min": 1, "min_release": "v0.28.2"}`, final. |
| - |
| -**STOPs and open items before the 5.0a go** |
| -1. **STOP:** `~/bin/carlos` on DOCS-T1 is `carlos v0.17.0`, older than v0.26.0. It is on `PATH` behind `~/.local/bin/carlos` (v0.28.0), so it is shadowed today, but a session with another `PATH` order would write pointers without digests. Remove or rename it, or rule it out (5.0a 1d). |
| -2. **OPEN:** writer confirmations on #250 (5.0a 1d): platform-1e, platform-d8 (`zdd-probe4`, `zdd-probe5`), whoever ships `appsdomain-check`, `rollcheck-0261` and `wakecheck`, anyone else with a dev credential, and any CI. Each must answer v0.26.0 or later, or hold off promoting on dev. |
| -3. Noted, not STOPs: legacy account-less copies under `apps/console/`, `apps/helloworld/` and `apps/website/` (no runtime path reads them); the identity's console key `bootstrap/console/v0.28.2/carlos-linux-arm64` is a name only and not in the bucket (nothing in step 6 reads it). |
| - |
| -**Decisions and the go each needs, in order** |
| - |
| -| # | What | Go | State on 2026-10-18 | |
| -|---|---|---|---| |
| -| 1 | Step 2: delete the 267 objects under `apps/bab//` (the script on main) | Paul's go; Paul runs it | not run | |
| -| 2 | Step 4: `CARLOS_POOLS=cohort1` on leo and the disposable app `claimtrial` | approved 2026-10-16; apply after platform-d8 answers | not applied (`CARLOS_POOLS` unset on leo, no `claimtrial` in the bucket) | |
| -| 3 | 5.0a: pointer digests on for all of `bab` on dev (eight apps, nine pointers), then ship and promote the prebuilt console `7c1beb2d` to `canary/console` | one go for both; outside any roll | 1a PASS, 1b listed, 1c PASS, 1d STOP and OPEN rows, 1e as expected, prebuilt console PASS | |
| -| 4 | Open the signing session: create the signer host and its role policy, with read on `apps/bab/console/*` and write on exactly `control/system/router/releases/v0.28.2.json` (`signing-host.md`) | one go for host and policy together | DONE 2026-10-18: `SG` `sg-08c374c8c31ef448a`, `I` `i-0ea2852ef727929bb`, run-id `cohort1-20261018T101832Z`, session start 10:19:37Z | |
| -| 5 | 5.1 roster and enrollment | Paul's go | DONE 2026-10-18: roster revision 5 (leo, sophie, `dev-console`), inputs staged on both boxes; token generated on the signer, never printed | |
| -| 6 | Step 6: sign the policy, publish the identity, arm leo, re-run P9, install the protected console, arm sophie, start the guarded console | Paul's go; restarts the dev console | 2026-10-18: 6.1 PASS (identity published, `2d22f05f`); 6.2 STOP: leo's `carlos.prev` is v0.28.1 and `guard-arm` refuses it; 6.3 not run; session closed | |
| -| 7 | Step 7: readers, floor, home, leo's key, console runtime | Paul's go; `home_region` becomes write-once | ready after 6 | |
| -| 8 | Close the session: shred the key, terminate the signer, delete its role, profile and security group, CloudTrail check | none; always allowed | DONE 2026-10-18 10:31Z at the 6.2 stop; CloudTrail clean | |
| +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 |
| + |
| +Prepared on 2026-10-18 for platform-1e to present. The values and state below were read read-only at about 14:30Z; 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`. It takes `sign-policy`'s armed policy as its floor before any floor is published, and on a box already at the channel's release it replaces `carlos.prev` with that release. The root-runner rehearsal ran all of steps 6 and 7 with it and passed. v0.28.3 is built from `50f5b268` on DOCS-T1 and is not yet published. Read today: dev's router pointers still name v0.28.2; leo and sophie run `carlos v0.28.2` with `.prev` `carlos v0.28.1`; the console runs `7c1beb2d`; 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. |
| + |
| +**v0.28.3 values** |
| +- Release: v0.28.3, built from main `50f5b268` (`~/.cache/release-v0.28.3/src` at `50f5b26845fb0a23109078654ec5d59b3723d9c4`). Not published: `gh release list -R carlosframework/releases` shows v0.28.2 as the latest, and the forge has no v0.28.3 tag. |
| +- carlos `linux-arm64` (the router artefact and the signer's CLI) sha256: **TO-CONFIRM (published?)**, from v0.28.3's signed `SHA256SUMS.txt`. The local build `dist/carlos-linux-arm64` hashes to `6154afb458a35183e470f5480ea08c09817400e6e67465530477e5204af2def0` (49217698 bytes). That is what to expect, not the value of record. |
| +- carlos `linux-amd64` (the operator CLI for 5.0a) sha256: **TO-CONFIRM (published?)**. Local build: `82a60203b1790771b5f3da16bc5d342327131ed30a1aae59a520f42d253369b2`. |
| +- `SHA256SUMS.txt` and `SHA256SUMS.txt.sig`: **TO-CONFIRM (published?)**. The list must name `carlos_0.28.3_amd64.deb` and `carlos_0.28.3_arm64.deb` and no other `carlos_*.deb`. `publish-identity` carries both files verbatim. |
| +- 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. Never rebuild it. |
| +- Console ship version `CV`: `50f5b268`, the short sha of v0.28.3's commit. **TO-CONFIRM:** the v0.28.3 tag must land on `50f5b268`. `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. |
| +- Floor: `{"min": 1, "min_release": "v0.28.3"}` (`floor.json`, sha256 `c7063389c0bd78a7cd7b400cf6a548cf527320fd562dd6c26ded1a0ecc0f303f`). Expected `floor_sha256` `07d2b63e711af4d97fad48713096447ce3c81ff92db0e956a98543478484aa09`, the sha256 of the compact form `{"min":1,"min_release":"v0.28.3"}` (the same rule gives v0.28.2's `3a361ab8…`). `sign-policy`'s own output is the value of record. |
| +- Policy and identity files: new bytes, new paths. On the signer and on both boxes: `/root/claim-trial/policy.v0.28.3.json` and `/root/claim-trial/identity.v0.28.3.json`. The v0.28.2 files (`policy.json` `dfac87e6`, `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` (TO-CONFIRM above); jq 1.7.1 `4dd2d8a0…b4a5` (unchanged). |
| +- Console rollback version: `7c1beb2d`, promoted with digests on. |
| + |
| +**The session, in order, each block with its stop points.** Paul's go is said to cover the merge, the release, the dev roll, and the new session from 5.1's leftovers through step 7. platform-1e confirms with Paul before the signer is created. |
| +1. **Release v0.28.3** (README, "Releasing the platform"): tag `50f5b268`, publish, verify from the public side, then fill in every TO-CONFIRM value above. Stop: the signature does not verify; the tag is not on `50f5b268`; the sums list other debs; the published `carlos-linux-arm64` differs from the local build. |
| +2. **Dev roll, boxes only** (step 1's commands with `--version v0.28.3`, `stable` then `canary`; no console item). Stop: any box not `current` at v0.28.3, `refused` or `rolled-back`; `carlos-instance-control` not restarted (H4). Expected after: each box `carlos v0.28.3`, `.prev` `carlos v0.28.2`. |
| +3. **5.0a, promote-only**: ship and promote the v0.28.3 console to `canary/console`. Digests are already on. Stop: the console's sha256 is not `3660b44c…ec74`; a pointer row `written after digests were turned on`; the header or `readlink` not `50f5b268`. |
| +4. **Open the session** (`signing-host.md`, "Create", with the v0.28.3 key and CLI). This puts the release key on a host a second time. Stop: a checksum mismatch; `carlos version` not `carlos v0.28.3`; a key file that is not root `600`. |
| +5. **5.1, resumed** ("5.1 in the second session"): `refresh` only, never `initialize`. Stop: revision not 5, or the listing or a staged file differs. |
| +6. **Step 6**: 6.1 `sign-policy` and `publish-identity` (new `--out` paths), 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, any identity value other than 6.1's expected ones, any line from 6.1a other than the expected ones. |
| +7. **Step 7**: readers first (P3, now with `carlos.prev`); 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; `jq -e` not `true`; `publish-floor` refused twice. |
| +8. **Close the session**: shred the key, terminate the signer, remove the exchange objects, delete the role, profile and security group, then the CloudTrail check ("Closing the signing session"). Always allowed, and done at any stop. |
| + |
| +**TO-CONFIRM before the session** |
| +1. Is v0.28.3 published? Then, from its signed `SHA256SUMS.txt` (signature verified with the README's pinned key): the `carlos-linux-arm64` and `carlos-linux-amd64` sha256 values, and the sha256 of `SHA256SUMS.txt` and `.sig`. Fill them in here, in 6.1's expected identity, and in `claim-trial-inputs/`. |
| +2. The v0.28.3 tag is on `50f5b268`, so `CV` is `50f5b268`. If it is anywhere else, stop and ask: the prebuilt console's source and the ship version would not match. |
| +3. The dev roll: every box `carlos v0.28.3`, `.prev` `carlos v0.28.2`, `current`. |
| +4. Whether the dev-roll go covers 5.0a's console promote. It is the dev roll's console item, held back so it lands after the boxes; if it is not covered, ask. |
| +5. Dennis's `carlos.prev`. After the roll it is v0.28.2, and nothing in this session replaces it (dennis is not armed and is not in the trial). P3's pass says every `.prev` image is on the trial release before step 7. platform-1e rules whether dennis's `.prev` at v0.28.2 passes P3, or whether dennis also runs 6.1a. |
| |
| 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. |
| |
| @@ -147,6 +153,8 @@ 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: platform-1e rules whether that passes (Go package, TO-CONFIRM 5). In the v0.28.3 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. |
| |
| ### P4. Leo is empty or quiet |
| @@ -274,6 +282,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. |
| |
| ## Accepted trial gaps |
| |
| @@ -458,7 +467,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 (pointer digests and the release-stamped console) runs after v0.28.2 is on dev and before the signing session opens. 5.1 (roster and enrollment) runs inside the signing session (see "The signing 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.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"). |
| |
| ### 5.0 Box identity records (done 2026-10-18) |
| |
| @@ -520,7 +529,83 @@ 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 Pointer digests and the release-stamped dev console |
| +### 5.0a The release-stamped dev console for v0.28.3 (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; Paul's go is the one for the dev roll (Go package, TO-CONFIRM 4). 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`. |
| + |
| +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`. |
| + |
| +Values, on DOCS-T1: |
| +- `CARLOS`: the carlos v0.28.3 CLI for DOCS-T1, `carlos-linux-amd64`, sha256 **TO-CONFIRM (published?)**. 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 (**TO-CONFIRM:** the tag is on `50f5b268`). `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. |
| +- `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. |
| +- **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`: |
| + |
| +```bash |
| +sha256sum "$CONSOLE" |
| +file "$CONSOLE" |
| +go version -m "$CONSOLE" | grep -E 'path|-ldflags' |
| +go version -m "$CONSOLE" | grep -o 'internal/version.Build=[^ "]*' | sort | uniq -c |
| +grep -c "$(sha256sum "$CONSOLE" | cut -d' ' -f1)" SHA256SUMS.txt |
| +``` |
| + |
| +Expected. All but the last line were read on 2026-10-18; the last waits for the published sums: |
| + |
| +``` |
| +3660b44c13080f0e9ead5f70754a11ec267d70eaab2a89e88ce256ab3437ec74 /home/paulca/.cache/release-v0.28.3/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 |
| +0 |
| +``` |
| + |
| +A different sha256 on the day is a STOP. |
| + |
| +**3.** Not run: digests are already on for `bab`. |
| + |
| +**4. Ship and promote the prebuilt console.** |
| + |
| +```bash |
| +CV=50f5b268 |
| +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`; |
| +- `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. |
| + |
| +Rollback: leave digests on and promote `7c1beb2d` again, with the same CLI. That pointer carries digests, so sophie accepts it: |
| + |
| +```bash |
| +export AWS_PROFILE=kass AWS_REGION=eu-west-1 CARLOS_DEPLOYMENT_BUCKET=carlos-dev-640012953448 |
| +CARLOS_RELEASE_KEY=$(aws ssm get-parameter --name /carlos/release-key --with-decryption --query Parameter.Value --output text) \ |
| + "$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`. |
| + |
| +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. |
| |
| 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. |
| |
| @@ -745,7 +830,18 @@ 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. |
| |
| -Order of work, each block with its own Paul's go and its stop points: |
| +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. |
| + |
| +Order of work for the second session (v0.28.3), each block with its stop points: |
| +1. **Open the session.** Only once v0.28.3 is published and verified, 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`, 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.3`, `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 `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; `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. |
| + |
| +Order of work for the first session (v0.28.2, 2026-10-18), kept for the record: |
| 1. **Open the session.** Paul's go: create the signer host and its role policy (`signing-host.md`, "Create", including write on exactly `control/system/router/releases/v0.28.2.json`). Only once 5.0a has passed and, in the runbook's order, steps 2 and 4 are done (neither had run on 2026-10-18). Create the signer, install the verified tools (carlos v0.28.2 `7141e037...`, 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. Stop points: a checksum mismatch, `carlos version` not `carlos v0.28.2`, or a key file that is not root `600`. |
| 2. **5.1 roster and enrollment.** Paul's go: once for 5.1. Stop points: any dry run or apply refused; `inspect.json` not listing `leo-climbing` (pool `cohort1`, instance `i-046c7fe43620944bc`), `sophie-knitting` and the console; a staged file without `staged`. |
| 3. **Step 6.** Paul's go: once for step 6 (it restarts the dev console). In order: 6.1 `sign-policy` and `publish-identity` on the signer; 6.2 `guard-arm` on leo; 6.3 the P9 re-run from DOCS-T1, then `install-console`, `guard-arm --console`, `console-config`, the restart and `console-report` on sophie. Stop points: any refusal; an identity value other than step 6.1's expected values (a wrong published identity cannot be replaced); any P9 `SET` line or console-mail role policy; `console.service` not `active`. |
| @@ -781,12 +877,24 @@ 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): |
| + |
| +``` |
| +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. |
| + |
| `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. |
| |
| **No new tools on the boxes (platform-1e, 2026-10-17).** `jq` is not installed on leo or sophie, and nothing is installed for the trial. Every `jq` line runs on the trusted signing host. A box writes raw JSON (`inspect.json`, `key-proof.json`) to a file; the trusted host fetches it over SSM, parses it there, and stages the results (`roster.json`, `rev`, `le`) back on the box over SSM. The box commands read those files with `cat`. A wrong or stale staged roster is refused, because `console-config` and `evidence-key` verify the roster's signature and its revision against `--expect-revision`. |
| |
| Where they run: |
| -- **The trusted signing host:** a throwaway dev instance, `carlos-claim-signer`, proposed in `docs/superpowers/notes/claim-trial-inputs/signing-host.md` (root-runner pattern, SSM only, its own role that may read `/carlos/release-key`). It holds `/root/claim-trial` with the inputs, the key in a `0600` tmpfs file at `/run/claim-signer/release.key`, `jq` and the carlos v0.28.2 CLI, both checked by sha256. It runs only the signing helpers (`refresh`, `apply_plan`, `phase`, and step 6's `phase_out`) with its instance role; set `KEY=/run/claim-signer/release.key` and `CARLOS=/usr/local/bin/carlos`, and leave out `export AWS_PROFILE=kass`. The release signing key never goes on either box. |
| +- **The trusted signing host:** a throwaway dev instance, `carlos-claim-signer`, proposed in `docs/superpowers/notes/claim-trial-inputs/signing-host.md` (root-runner pattern, SSM only, its own role that may read `/carlos/release-key`). It holds `/root/claim-trial` with the inputs, the key in a `0600` tmpfs file at `/run/claim-signer/release.key`, `jq` and the carlos CLI of the session's release (v0.28.2 in the first session, v0.28.3 in the second), both checked by sha256. It runs only the signing helpers (`refresh`, `apply_plan`, `phase`, and step 6's `phase_out`) with its instance role; set `KEY=/run/claim-signer/release.key` and `CARLOS=/usr/local/bin/carlos`, and leave out `export AWS_PROFILE=kass`. The release signing key never goes on either box. |
| - **The operator host (DOCS-T1, profile `kass`):** runs the box helpers below (`ssm_run`, `box_get`, `box_put`, `box_inspect`) and the `jq` parsing for the boxes. Files move between the signer and DOCS-T1 through `s3://carlos-dev-640012953448/claim-signer/<run-id>/`. |
| - **Leo and sophie:** attended root shells for the box blocks, which use only the platform binary, `systemctl` and the shell. Inputs are staged at the exact paths shown, root-owned, private files `0600`, no symlinks or extra hard links. Staging is a checkpoint, not permission to make up signatures. |
| |
| @@ -938,15 +1046,32 @@ 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 |
| + |
| +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. |
| + |
| +What the first session's 5.1 left, read read-only on 2026-10-18 at 14:30Z: |
| +- **Bucket:** `control/host-actors.json` 1967 bytes, etag `f3717d8d67f496ffd9525704c7a690b0`, written 10:21:54Z, the revision 5 the record above names. No floor (`control/moves-protocol.json` 404). Nothing under `claim-signer/`. |
| +- **Leo, `/root/claim-trial`** (root `700`), each file root:root `600`, one link: `release.pub` (`960d03ff`), `leo-climbing-enrollment.json` (`800b181f`, 120 bytes), and the first session's `policy.json` (`dfac87e6`, 507 bytes) and `identity.json` (`2d22f05f`, 1076 bytes). No `roster.json`, `rev` or `le` yet: `box_inspect` stages those when they are needed (7.3). |
| +- **Sophie, `/root/claim-trial`**, the same way: `release.pub` (`960d03ff`), `sophie-knitting-enrollment.json` (`85380180`, 123 bytes), `console-id` (`cfacdf55`, 12 bytes, `dev-console`), `policy.json` (`dfac87e6`) and `identity.json` (`2d22f05f`). No `roster.json` or `rev` yet (6.3's `box_inspect` never ran). |
| +- **Both boxes:** `carlos v0.28.2`, `.prev` `carlos v0.28.1`; no `/etc/carlos/host-actor.json`, no `/opt/carlos/system/guard`, no `carlos-edge.service.d`, no `/opt/carlos/system/router/channel-identity.json`, no `/opt/carlos/system/console`. Leo has no `CARLOS_HOST_CLAIM` and no `CARLOS_SYSTEM_CHANNEL` (so `stable`); sophie has `CARLOS_SYSTEM_CHANNEL=canary`. |
| +- **Gone with the first signer** (terminated, exchange emptied): its `/root/claim-trial`, `helpers.sh`, `inspect.json`, `roster.json` and the console enrollment token file. The token is also in the roster, and nothing from 6.1 on reads the file (`console-config` and `console-report` take the console from the roster by `--console-id`), so nothing needs it back. |
| + |
| +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. |
| +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. |
| + |
| ## Step 6. Arm leo's guard and start the guarded console |
| |
| -Inside the signing session, after 5.1. Waits for v0.28.2 on dev and for 5.0a. Everything this step adds is on main 7c1beb2d, published (v0.28.2, tag on 7c1beb2d): the `claims run` phases `sign-policy` and `publish-identity`, `claims run --out`, and `claims install-console`. Never hand-make any of their files. The builder findings from the review of that branch (the release-tag stamp, 5.0a, the fixed console path and the verbatim identity inputs) are drafted here for Paul after v0.28.2. |
| +Inside the signing session, after 5.1. 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. |
| |
| -Paul's go: once for step 6. It restarts `console.service` on sophie, which briefly interrupts the dev console. |
| +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.json` and `identity.json` are byte-exact files, read directly. Nothing parses or rewrites them: no `jq`, no reformatting, no copy and paste. Each phase writes its file to `--out`: a new absolute path in a root-owned directory that no group or other user can write. On main at `7c1beb2d` the phase creates the file `0600` before it can write to the bucket, fills it, and reads it back; it refuses a path that already exists. If the phase fails after that, the file is kept and the error says so (and whether the phase had already written to the bucket): run it again with a new `--out` path, never by deleting the old file. The dry run needs `--out` too; it checks the path and creates nothing. `apply_plan` passes no `--out`, so add this helper beside `phase` on the signer: |
| +`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: |
| |
| ```bash |
| # phase_out <phase> <input> <out file> <result file>: phase, for the two phases that write a file to --out. |
| @@ -961,59 +1086,113 @@ phase_out() { |
| } |
| ``` |
| |
| -**The armed policy:** |
| +**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. |
| |
| ```bash |
| jq -n --slurpfile x floor.json '{floor:$x[0]}' > floor-phase.json |
| -phase_out sign-policy floor-phase.json /root/claim-trial/policy.json sign-policy-result.json |
| +phase_out sign-policy floor-phase.json /root/claim-trial/policy.v0.28.3.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, kept for step 7. Step 7's `publish-floor` writes the same floor bytes and authorization from the same `floor.json` (the branch's tests pin that). `sign-policy` refuses a floor other than the one the deployment is already bound to. |
| +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. |
| |
| **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.2 public release: `https://github.com/carlosframework/releases/releases/download/v0.28.2/SHA256SUMS.txt` | |
| +| `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_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` | `7c1beb2d` | the console's ship version from 5.0a (the short sha of v0.28.2's commit) | |
| +| `identity.console.version` | `50f5b268` (TO-CONFIRM: the v0.28.3 tag's commit) | the console's ship version from 5.0a for v0.28.3 | |
| | `identity.console.app` | `console` | the console app | |
| -| `identity.release_tag` | `v0.28.2` | the release tag | |
| +| `identity.release_tag` | `v0.28.3` | 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.2's own: it must name `carlos_0.28.2_amd64.deb` and `carlos_0.28.2_arm64.deb`, and no other `carlos_*.deb`, so another release's genuine list is refused under this tag. The console values are copied from 5.0a's record, not from memory. No sha256 is typed anywhere: the router's comes from the signed sums (`carlos-linux-arm64`), and the console's from the channel pointer's signed manifest digest, the manifest and the artefact. The console's sha256 may not be any file listed in the sums. |
| +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. |
| |
| ```bash |
| -CV=7c1beb2d |
| +CV=50f5b268 |
| jq -n --rawfile sums SHA256SUMS.txt --rawfile sig SHA256SUMS.txt.sig --arg cv "$CV" \ |
| - '{identity:{release_tag:"v0.28.2",moves_protocol:1,sha256sums:$sums,sha256sums_sig:$sig, |
| + '{identity:{release_tag:"v0.28.3",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.json publish-identity-result.json |
| +phase_out publish-identity identity-phase.json /root/claim-trial/identity.v0.28.3.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 of `control/system/router/releases/v0.28.2.json`; |
| -- `identity_key` is that key, and `release` shows `version` and `release_tag` `v0.28.2`, `moves_protocol` `1`, `artifacts.linux-arm64` `{"key": "bootstrap/router/v0.28.2/carlos-linux-arm64", "sha256": "7141e037d61b0d95ae554252af1bb9155653a9954ad0dcaffb35c7047850f492"}` (the `carlos-linux-arm64` line of `SHA256SUMS.txt`), and `console_artifacts.linux-arm64` `{"key": "bootstrap/console/v0.28.2/carlos-linux-arm64", "sha256": "0827341051360e888ed4f6df568a77ebb070e6ad16a7c87a2947a02dbd552ff4"}` (5.0a's prebuilt console). 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.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.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": "<TO-CONFIRM (published?)>"}` (the `carlos-linux-arm64` line of v0.28.3's `SHA256SUMS.txt`; the local build is `6154afb4…def0`), 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 signer's role may write exactly one identity object: the key `control/system/router/releases/v0.28.2.json`, an exact key and not a prefix (`signing-host.md`, statement `WriteIdentity`). platform-1e accepted that scope on 2026-10-18; Paul approves it together with the signer host. The runner also admits only that key for the phase. |
| +The 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 identity is create-only per tag. Publishing the same identity again writes nothing; a different one under v0.28.2 is refused and can never replace it. So check the console values against 5.0a before the apply. A wrong identity, once published, is a stop, and putting it right needs a new release tag. A rerun after a successful publish needs a new `--out` path (for example `identity.2.json`); it writes nothing to the bucket, and its file must have the same sha256. |
| +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. |
| |
| -Copy both files to DOCS-T1 through `s3://carlos-dev-640012953448/claim-signer/<run-id>/` with `aws s3 cp`, and record each sha256 on both sides. `box_put` checks the same sha256 on arrival. Stage them from DOCS-T1: |
| +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. |
| + |
| +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 f in policy.json identity.json; do box_put "$LEO" "$f" "/root/claim-trial/$f"; done |
| -for f in policy.json identity.json; do box_put "$SOPHIE" "$f" "/root/claim-trial/$f"; done |
| +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" |
| +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 |
| ``` |
| |
| +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) |
| + |
| +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. |
| + |
| +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 ..."). |
| + |
| +On leo, as root, then the same block on sophie: |
| + |
| +```bash |
| +set -euo pipefail |
| +cd /root/claim-trial |
| +set -a |
| +. /etc/carlos/host.env |
| +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 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 |
| +``` |
| + |
| +Expected: `carlos v0.28.3`, then `carlos v0.28.2`; 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 |
| +``` |
| + |
| +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. |
| + |
| +Stop points: any other output. In particular: |
| +- "this box already runs ..." (the channel names another release); |
| +- "this box's host lifecycle guard is armed" (6.2 already ran: arming is one-way, hold); |
| +- "... holds a build this command did not install" (`carlos` is not the channel's artefact, or `carlos.prev` is not a regular file); |
| +- "is below the host floor"; |
| +- "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. |
| + |
| +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. |
| + |
| +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. |
| + |
| ### 6.2 Arm leo |
| |
| -On leo, after the release and protected current and previous binaries are installed: |
| +On leo, right after 6.1a (both slots `carlos v0.28.3`): |
| |
| ```bash |
| set -euo pipefail |
| @@ -1021,13 +1200,13 @@ cd /root/claim-trial |
| set -a |
| . /etc/carlos/host.env |
| set +a |
| -/opt/carlos/carlos system guard-arm --policy /root/claim-trial/policy.json \ |
| - --identity /root/claim-trial/identity.json --enrollment /root/claim-trial/leo-climbing-enrollment.json |
| +/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 |
| ``` |
| |
| ### 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 (on main 7c1beb2d, published (v0.28.2, tag on 7c1beb2d)). Its `--source` is the console build 5.0a promoted, at `/opt/carlos/versions/7c1beb2d/console`, the path 5.0a's `readlink` showed. |
| +The console path is fixed: `/opt/carlos/system/console/console`, with `console.prev` beside it, both created by `carlos system claims install-console` on sophie. 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`. |
| |
| 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. |
| |
| @@ -1049,14 +1228,14 @@ set +a |
| PUB=$(cat release.pub) |
| CID=$(cat console-id) |
| REV=$(cat rev) |
| -CONSOLE_SOURCE=/opt/carlos/versions/7c1beb2d/console |
| +CONSOLE_SOURCE=/opt/carlos/versions/50f5b268/console |
| CONSOLE_BINARY=/opt/carlos/system/console/console |
| -/opt/carlos/carlos system claims install-console --identity /root/claim-trial/identity.json --source "$CONSOLE_SOURCE" \ |
| +/opt/carlos/carlos system claims install-console --identity /root/claim-trial/identity.v0.28.3.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.json --source "$CONSOLE_SOURCE" \ |
| +/opt/carlos/carlos system claims install-console --identity /root/claim-trial/identity.v0.28.3.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.json \ |
| - --identity /root/claim-trial/identity.json --enrollment /root/claim-trial/sophie-knitting-enrollment.json \ |
| +/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 \ |
| --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 |
| @@ -1064,21 +1243,33 @@ CONSOLE_BINARY=/opt/carlos/system/console/console |
| --bucket carlos-dev-640012953448 --console-id "$CID" --public-key "$PUB" |
| systemctl restart console.service |
| systemctl is-active console.service |
| -/opt/carlos/carlos system claims console-report --bucket carlos-dev-640012953448 --aws-region eu-west-1 \ |
| - --expect-revision "$REV" --console-id "$CID" --public-key "$PUB" --dry-run |
| -/opt/carlos/carlos system claims console-report --bucket carlos-dev-640012953448 --aws-region eu-west-1 \ |
| - --expect-revision "$REV" --console-id "$CID" --public-key "$PUB" |
| ``` |
| |
| +`console-report` is not run here. It opens step 7's report-and-floor pair, because its evidence lasts only until the agent's next status tick ("Hard ordering" under "The signing session"). |
| + |
| +Then wait for the agent to report the armed guard. `console-report` refuses until sophie's agent has reported it, and `publish-floor` needs leo's report too. The agent's first tick comes about a minute after the edge starts, then every 5 minutes plus jitter, so allow up to about 6 minutes after `guard-arm`. From DOCS-T1, read-only: |
| + |
| +```bash |
| +for b in leo-climbing sophie-knitting; do |
| + aws --profile kass --region eu-west-1 s3 cp "s3://carlos-dev-640012953448/status/boxes/$b.json" - \ |
| + | python3 -c 'import json,sys;d=json.load(sys.stdin);m=d.get("metrics") or {};print(sys.argv[1],d["generated_at"],m.get("release"),m.get("binary_sha256","")[:8],m.get("floor_guard_ready"),m.get("floor_sha256","")[:8],len(m.get("console_processes") or []))' "$b" |
| +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. |
| + |
| Expected: |
| -- `install-console`'s dry run, then its apply, print `release_tag` `v0.28.2`, a `sha256` equal to the identity's `console_artifacts.linux-arm64.sha256`, `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.2), 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` succeeds on both. On leo 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. |
| +- 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. |
| +- `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` and the report publishes. If reporting refuses because a just-started console or box has no fresh telemetry yet, hold, read the journal, wait for the normal report and rerun the reporting pair. Never refresh timestamps by hand. If a command refuses the revision because the roster moved, run `box_inspect "$SOPHIE"` again and repeat from `REV=$(cat rev)`. |
| +- `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)`. |
| + |
| +Leo's host claims stay off until step 8. |
| |
| -Leo's host claims stay off until step 8. Get real fresh box evidence from both enrolled computes before step 7. |
| +On failure: hold. Arming is one-way: keep the local armed policy, and never delete the enrollment or the guard. Restore only an image at or above the floor. Never delete or rewrite either published identity, any staged `policy*.json` or `identity*.json`, or the files in `/opt/carlos/system/console/`. |
| |
| -On failure: hold. Arming is one-way: keep the local armed policy, and never delete the enrollment or the guard. Restore only an image at or above the floor. Never delete or rewrite the published identity, `policy.json`, `identity.json` or the files in `/opt/carlos/system/console/`. |
| +### The first session's step 6 (v0.28.2, 2026-10-18) |
| |
| **2026-10-18: 6.1 PASS; 6.2 STOP. The session is closed.** Run by the executor on Paul's go for step 6, relayed by the controller. Commands went over SSM, as in 5.1. |
| |
| @@ -1099,12 +1290,31 @@ 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. |
| + |
| ## 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. |
| +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`. |
| +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. |
| |
| -1. **Readers.** Re-run P3. Dennis is not in the trial, but its agent reads the roster, so it counts. Every reader must be on the trial release before the home designation is published. |
| -2. **Floor and home.** On the trusted signing host, immediately after sophie's successful report: |
| + On the trusted host, stage sophie's current roster and revision (`box_inspect "$SOPHIE"`). Then on sophie: |
| + |
| + ```bash |
| + set -euo pipefail |
| + umask 077 |
| + cd /root/claim-trial |
| + PUB=$(cat release.pub) |
| + CID=$(cat console-id) |
| + REV=$(cat rev) |
| + /opt/carlos/carlos system claims console-report --bucket carlos-dev-640012953448 --aws-region eu-west-1 \ |
| + --expect-revision "$REV" --console-id "$CID" --public-key "$PUB" --dry-run |
| + /opt/carlos/carlos system claims console-report --bucket carlos-dev-640012953448 --aws-region eu-west-1 \ |
| + --expect-revision "$REV" --console-id "$CID" --public-key "$PUB" |
| + ``` |
| + |
| + The moment the apply succeeds, on the trusted signing host, then home: |
| |
| ```bash |
| jq -n --slurpfile x floor.json '{floor:$x[0]}' > floor-phase.json |
| @@ -1114,6 +1324,9 @@ 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. |
| + |
| + 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: |
| |
| ```bash |
| @@ -1172,6 +1385,7 @@ Expected: |
| - the `jq -e` in 3 prints `true`; |
| - `/etc/carlos/host-evidence.key` exists on leo (`root`, `600`, one link); |
| - `console.service` is `active` and its report publishes; if publication loses a race with the agent's own report, run `box_inspect` again, report again and retry with the current revision; |
| +- this report, too, lasts only until the agent's next status tick (#312). Nothing in step 7 needs it after it publishes; anything later that needs the console's compute evidence (the console runtime's own census, step 9) sees it vanish within about 6 minutes. That is an open design question (prev-rehearsal report), not a step 7 stop. |
| |
| On failure: hold. A torn publish is resumed with identical bytes. Never erase the floor or substitute another digest. Leo's claim flag stays off. |
| |
| @@ -1956,7 +2170,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`), the published identity (`control/system/router/releases/v0.28.2.json`) and the `bab` pointer-digest marker; |
| +- 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; |
| - 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`. |
| |
| @@ -1982,6 +2196,10 @@ Leaving hold is a separate decision: a fix and resume, or a claims-off teardown |
| |
| **H9. No new tools on the boxes.** `jq` is not installed on leo, sophie or dennis (2026-10-17), and nothing is installed for the trial (platform-1e). Every `jq` line runs on the trusted signing host, which fetches box JSON over SSM and stages parsed results back (steps 5 to 7). Staged values are checked by sha256, and the box commands verify the roster's signature and revision. |
| |
| +**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. |
| + |
| ## Later option: carlos-dev-uk |
| |
| Not part of this trial (Paul, 2026-10-16). dennis-stenciling is placement `uk`, serves nothing, and already has its own `CARLOS_SECRETS_KEY` (fingerprint `3fe29008`, the same as sophie's). Adding it later means steps 3 to 10 on it as well (SSM region eu-west-2, instance i-009d593f451cfd562), with these differences: |
| @@ -2002,5 +2220,6 @@ 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`: the console enrollment token, the v0.28.1 and v0.28.2 artifacts, and the signing host values. Namespace `platform`, sophie's pool `console`, console ID `dev-console` and operator `paul@keymail.dev` are confirmed. |
| +7. Still open in `docs/superpowers/notes/claim-trial-inputs/README.md` for the second session: the v0.28.3 release values (TO-CONFIRM until it is published) 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. For platform-1e: does dennis's `carlos.prev` at v0.28.2 pass P3 before step 7, or does dennis also run step 6.1a? (Go package, TO-CONFIRM 5.) |