carlos / platform Public

docs: cohort-1 runbook: 5.0 done, operator ruled, one signing session after v0.28.2

- Step 5.0 recorded as done 2026-10-18: leo 06:10:49 (leo-climbing.json,
  919 B) and sophie 06:11:32 (sophie-knitting.json, 922 B), both verify
  re-runs "nothing was written"; wakecheck 200, edges active, no new
  AccessDenied.
- Operator paul@keymail.dev (Paul's ruling) in leo.json, sophie.json and
  the README; removed from TO-CONFIRM.
- Paul's ruling: the signer is created once, after v0.28.2, for one
  session covering 5.1, 6 and 7. New "The signing session" section (open:
  create signer; 5.1; 6; 7; close) and "Closing the signing session"
  (shred, terminate, delete role/profile/SG, CloudTrail check). Nothing
  else touches the signer between. Stop-and-hold, blockers and
  signing-host.md updated; the trial waits for v0.28.2 past 5.0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev e65690533d0b92ae57da085ae2c5f323cf3246ec parent 6cf0b10
5 files changed, +95 −56
  • docs/superpowers/notes/claim-trial-inputs/README.md +11 −10
  • docs/superpowers/notes/claim-trial-inputs/leo.json +1 −1
  • docs/superpowers/notes/claim-trial-inputs/signing-host.md +5 −5
  • docs/superpowers/notes/claim-trial-inputs/sophie.json +1 −1
  • docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md +77 −39

CI passed — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.012s
?   	amadan.net/carlos/platform.git/launchctx/controlname	[no test files]
?   	amadan.net/carlos/platform.git/launchctx/wireid	[no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m
ok  	github.com/carlosframework/platform	321.033s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	3.030s
ok  	github.com/carlosframework/platform/cmd/moveprobe	92.203s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.702s
ok  	github.com/carlosframework/platform/internal/activator	251.561s
ok  	github.com/carlosframework/platform/internal/agentsock	0.016s
ok  	github.com/carlosframework/platform/internal/alerting	0.003s
ok  	github.com/carlosframework/platform/internal/analytics	0.022s
ok  	github.com/carlosframework/platform/internal/appconfig	2.515s
ok  	github.com/carlosframework/platform/internal/apperr	0.071s
ok  	github.com/carlosframework/platform/internal/applog	0.126s
ok  	github.com/carlosframework/platform/internal/awsinv	0.003s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.004s
ok  	github.com/carlosframework/platform/internal/boxid	0.644s
?   	github.com/carlosframework/platform/internal/boxid/boxidtest	[no test files]
?   	github.com/carlosframework/platform/internal/boxid/internal/signtest	[no test files]
ok  	github.com/carlosframework/platform/internal/boxvolumes	0.008s
ok  	github.com/carlosframework/platform/internal/caps	0.013s
ok  	github.com/carlosframework/platform/internal/certwarm	0.002s
ok  	github.com/carlosframework/platform/internal/cgroup	0.696s
ok  	github.com/carlosframework/platform/internal/changelog	0.312s
ok  	github.com/carlosframework/platform/internal/claimactivation	0.023s
ok  	github.com/carlosframework/platform/internal/clamd	0.054s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
ok  	github.com/carlosframework/platform/internal/console	411.062s
?   	github.com/carlosframework/platform/internal/controlname	[no test files]
?   	github.com/carlosframework/platform/internal/controlsock	[no test files]
ok  	github.com/carlosframework/platform/internal/dbdoor	6.043s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.008s
ok  	github.com/carlosframework/platform/internal/dbverify	0.004s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.004s
ok  	github.com/carlosframework/platform/internal/docsite	0.005s
ok  	github.com/carlosframework/platform/internal/domains	2.416s
ok  	github.com/carlosframework/platform/internal/doorkey	0.006s
ok  	github.com/carlosframework/platform/internal/economics	0.004s
ok  	github.com/carlosframework/platform/internal/edge	44.978s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.008s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.134s
ok  	github.com/carlosframework/platform/internal/gate	1.661s
ok  	github.com/carlosframework/platform/internal/genselect	0.015s
ok  	github.com/carlosframework/platform/internal/geo	0.016s
ok  	github.com/carlosframework/platform/internal/geoip	0.031s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.155s
ok  	github.com/carlosframework/platform/internal/host	175.577s
ok  	github.com/carlosframework/platform/internal/hostactors	0.489s
ok  	github.com/carlosframework/platform/internal/hostbox	2.675s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.569s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.014s
ok  	github.com/carlosframework/platform/internal/hostexec	4.812s
ok  	github.com/carlosframework/platform/internal/hostfence	0.029s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.413s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.014s
ok  	github.com/carlosframework/platform/internal/hostguard	1.031s
ok  	github.com/carlosframework/platform/internal/hostops	1.682s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.014s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.014s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.437s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.155s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.014s
ok  	github.com/carlosframework/platform/internal/infraview	0.015s
ok  	github.com/carlosframework/platform/internal/instancectl	5.218s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.760s
ok  	github.com/carlosframework/platform/internal/instancefs	0.018s
ok  	github.com/carlosframework/platform/internal/instancekey	0.016s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.008s
ok  	github.com/carlosframework/platform/internal/layout	1.539s
ok  	github.com/carlosframework/platform/internal/lbhealth	0.002s
ok  	github.com/carlosframework/platform/internal/lease	9.807s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.007s
ok  	github.com/carlosframework/platform/internal/ledger	0.011s
ok  	github.com/carlosframework/platform/internal/mail	0.172s
ok  	github.com/carlosframework/platform/internal/maintenance	0.086s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.481s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.072s
ok  	github.com/carlosframework/platform/internal/metrics	0.004s
ok  	github.com/carlosframework/platform/internal/moves	21.080s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.805s
ok  	github.com/carlosframework/platform/internal/objstore	0.189s
ok  	github.com/carlosframework/platform/internal/ops	0.207s
ok  	github.com/carlosframework/platform/internal/park	0.200s
ok  	github.com/carlosframework/platform/internal/privatefile	0.005s
ok  	github.com/carlosframework/platform/internal/provision	18.024s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.006s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	5.461s
ok  	github.com/carlosframework/platform/internal/relay	7.232s
ok  	github.com/carlosframework/platform/internal/release	21.688s
ok  	github.com/carlosframework/platform/internal/rollout	3.640s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.003s
ok  	github.com/carlosframework/platform/internal/rootaws	0.006s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.058s
ok  	github.com/carlosframework/platform/internal/rootstate	0.026s
ok  	github.com/carlosframework/platform/internal/runs	0.583s
ok  	github.com/carlosframework/platform/internal/s3compat	0.005s
ok  	github.com/carlosframework/platform/internal/safefile	0.004s
ok  	github.com/carlosframework/platform/internal/schedule	2.502s
ok  	github.com/carlosframework/platform/internal/seeds	0.011s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	8.909s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	3.560s
ok  	github.com/carlosframework/platform/internal/status	0.041s
ok  	github.com/carlosframework/platform/internal/steering	0.006s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.249s
ok  	github.com/carlosframework/platform/internal/svcrole	0.002s
ok  	github.com/carlosframework/platform/internal/telemetry	0.004s
ok  	github.com/carlosframework/platform/internal/termfence	0.004s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.004s
ok  	github.com/carlosframework/platform/internal/unixdial	0.002s
ok  	github.com/carlosframework/platform/internal/updatewords	0.004s
ok  	github.com/carlosframework/platform/internal/usage	0.004s
ok  	github.com/carlosframework/platform/internal/version	0.001s
ok  	github.com/carlosframework/platform/internal/vet	1.562s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
linux/arm64 carlos binary: 48824482 bytes (ceiling 52000000)
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
  minio: cached, digest verified
  mc: cached, digest verified
  minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
  buckets and scoped user ready
=== RUN   TestDeliveredContractMinio
=== RUN   TestDeliveredContractMinio/put
=== RUN   TestDeliveredContractMinio/get_round-trips_the_body
=== RUN   TestDeliveredContractMinio/list_shows_the_key
=== RUN   TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN   TestDeliveredContractMinio/delete_removes_it
=== RUN   TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
    --- PASS: TestDeliveredContractMinio/put (0.00s)
    --- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
    --- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
    --- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
    --- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
    --- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok  	github.com/carlosframework/platform/internal/objstore	0.023s
=== RUN   TestT4StorageProbeMinio
2026/10/18 06:26:38 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DF8BB7D17595DB, HostID: 7c420053e0fbcd7f41653fdd92ffa2802c903d05303b1aa15adc2e8bb65d183f, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
--- PASS: TestT4StorageProbeMinio (0.01s)
PASS
ok  	github.com/carlosframework/platform/internal/host	0.021s
minio contract: PASS
diff --git a/docs/superpowers/notes/claim-trial-inputs/README.md b/docs/superpowers/notes/claim-trial-inputs/README.md
index b043f5279..13cac6b6d 100644
--- a/docs/superpowers/notes/claim-trial-inputs/README.md
+++ b/docs/superpowers/notes/claim-trial-inputs/README.md
@@ -2,7 +2,7 @@
These are the concrete inputs the runbook's steps 5 to 7 say "the controller supplies and Paul approves" (`docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md`). They were drafted on 2026-10-18 from dev values read with read-only calls, then updated with platform-1e's rulings the same day. Nothing here is signed, and no secret is in any file. A value still open is marked `TO-CONFIRM`; one that waits on a platform fix is marked `BLOCKED`.
-Timing: step 5 (roster and enrollment) needs only v0.28.1 on dev and Paul's approval of the package. Step 6 (arming) and later wait for v0.28.2, which carries the fixes for gaps 1 and 2.
+Timing: step 5.0 (box identity records) is done. Everything after it, from 5.1 (roster and enrollment) on, waits for v0.28.2, which carries the fixes for gaps 1 and 2, and runs in one signing session (Paul's ruling, 2026-10-18).
Platform main at drafting: `b1118925` (region HA 2b). The box-record verb is on branch `box-publish-record` (`6040bb1c`), merging for v0.28.1.
@@ -31,7 +31,7 @@ Platform main at drafting: `b1118925` (region HA 2b). The box-record verb is on
| Pools | `cohort1` (leo), `console` (sophie) | leo: runbook step 4 sets `CARLOS_POOLS=cohort1`. Sophie: platform-1e ruling 2026-10-18; `console` keeps sophie out of cohort1's required set, and with claims off it does not affect sophie's serving | confirmed |
| Box references and labels | `leo-climbing`, `sophie-knitting` | each box's name in `carlos system status` and its `metricsBoxLabel`; the record key is `control/boxes/<reference>.json` | confirmed |
| Instance IDs | leo `i-046c7fe43620944bc`, sophie `i-05498eada6f0afd23` | IMDS on each box | confirmed (revalidate before the run) |
-| Operator | suggested default `paul@keymail.dev` | the identity of the person approving enrollment, recorded in the roster | TO-CONFIRM (Paul) |
+| Operator | `paul@keymail.dev` | Paul's ruling 2026-10-18; the identity of the person approving enrollment, recorded in the roster | confirmed |
| Console ID | `dev-console` | platform-1e ruling 2026-10-18 | confirmed |
| Console enrollment | 32 lowercase hex characters | `hostactors` token rule `^[0-9a-f]{32}$`; chosen once at approval and kept | TO-CONFIRM (generated at approval) |
| Console deployment bucket | `carlos-dev-640012953448` | dev deployment bucket | confirmed |
@@ -63,18 +63,19 @@ platform-1e confirmed gaps 1 and 2 on main on 2026-10-18 and is building the fix
## Open items
TO-CONFIRM:
-1. Operator identity for both boxes (Paul; suggested default `paul@keymail.dev`).
-2. Console enrollment token, 32 lowercase hex characters (generated at approval).
-3. v0.28.2 published: the router sha256 from its signed `SHA256SUMS.txt`, the console sha256 from the console app's signed release manifest, and the carlos v0.28.2 CLI sha256 for the signer. This also fixes the floor's `min_release`.
-4. The bucket keys the signer needs, confirmed by a `--dry-run` of every phase from the signer.
+1. Console enrollment token, 32 lowercase hex characters (generated at approval).
+2. v0.28.2 published: the router sha256 from its signed `SHA256SUMS.txt`, the console sha256 from the console app's signed release manifest, and the carlos v0.28.2 CLI sha256 for the signer. This also fixes the floor's `min_release`.
+3. The bucket keys the signer needs, confirmed by a `--dry-run` of every phase from the signer.
TO-CREATE (each with Paul's go): the `carlos-claim-signer` security group (its ID), role, instance profile and instance (`signing-host.md`).
Known now: v0.28.1 is published and on dev; the carlos v0.28.1 and jq 1.7.1 sha256 values, the AMI, the VPC and the subnet are in `signing-host.md`.
BLOCKED (fixes ship in v0.28.2, built by platform-1e):
-5. `policy.json`: the signed armed policy (gap 1).
-6. `identity.json`: the signed v0.28.2 system identity (gap 2).
-7. Sophie's protected console path and its `.prev` (gap 2; `.prev` may equal current).
+4. `policy.json`: the signed armed policy (gap 1).
+5. `identity.json`: the signed v0.28.2 system identity (gap 2).
+6. Sophie's protected console path and its `.prev` (gap 2; `.prev` may equal current).
-Confirmed on 2026-10-18: namespace `platform`, sophie's pool `console`, console ID `dev-console`.
+Confirmed on 2026-10-18: namespace `platform`, sophie's pool `console`, console ID `dev-console`, operator `paul@keymail.dev`. Box identity records published (step 5.0): `control/boxes/leo-climbing.json` and `control/boxes/sophie-knitting.json`.
+
+Timing (Paul's ruling, 2026-10-18): the signer is created once, after v0.28.2, for one session that covers 5.1 through step 7.
diff --git a/docs/superpowers/notes/claim-trial-inputs/leo.json b/docs/superpowers/notes/claim-trial-inputs/leo.json
index 4661a6e60..be07a2bf7 100644
--- a/docs/superpowers/notes/claim-trial-inputs/leo.json
+++ b/docs/superpowers/notes/claim-trial-inputs/leo.json
@@ -6,5 +6,5 @@
"pool": "cohort1",
"aws_region": "eu-west-1",
"instance_id": "i-046c7fe43620944bc",
- "operator": "TO-CONFIRM (Paul): the approving operator's identity; suggested default paul@keymail.dev"
+ "operator": "paul@keymail.dev"
}
diff --git a/docs/superpowers/notes/claim-trial-inputs/signing-host.md b/docs/superpowers/notes/claim-trial-inputs/signing-host.md
index 53c831c84..ff05084d1 100644
--- a/docs/superpowers/notes/claim-trial-inputs/signing-host.md
+++ b/docs/superpowers/notes/claim-trial-inputs/signing-host.md
@@ -39,7 +39,7 @@ The signer reuses the AMI, type, subnet and launch options. It gets its **own**
| Access | SSM Session Manager only. No SSH key, no inbound port |
| Metadata | IMDSv2 required, hop limit 1 |
| Storage | default root volume, deleted on termination. No swap (Debian cloud default), so a key in `/run` stays in memory |
-| Lifetime | one instance per signing session (step 5, then step 7), terminated straight after each |
+| Lifetime | created once, after v0.28.2 is on dev, for a single signing session that covers runbook 5.1, 6 and 7 in order (Paul's ruling, 2026-10-18), so the release key is on a host only once. Nothing else touches the signer during the session. Torn down at its end, or at any stop |
| Tags | `Name=carlos-claim-signer`, `purpose=claim-signer` |
## IAM role `carlos-claim-signer`
@@ -96,10 +96,10 @@ The key goes from the SSM response straight into a `0600` file on tmpfs (`/run`)
Known values (2026-10-18):
- carlos v0.28.1 `carlos-linux-arm64`: `2ae174da29b1d10fdcb009172ba830db5460b62e838bcecc7d9e17535f35adea`. It comes from the published release's `SHA256SUMS.txt`, whose signature verified with the README's pinned key (`openssl pkeyutl -verify`: "Signature Verified Successfully"). It is the same binary leo and sophie run.
- jq 1.7.1 `jq-linux-arm64`: `4dd2d8a0661df0b22f1bb9a1f9830f06b6f3b8f7d91211a1ef5d7c4f06a8b4a5` (1709616 bytes). It was computed on DOCS-T1 from the file downloaded from `github.com/jqlang/jq/releases/download/jq-1.7.1/`, and matches that release's `sha256sum.txt`.
-- carlos v0.28.2: TO-CONFIRM when published (step 6's policy signing and step 7 need it).
+- carlos v0.28.2: TO-CONFIRM when published. The signing session uses it.
On DOCS-T1, before launching:
-1. Download the carlos CLI `carlos-linux-arm64` (v0.28.1 for step 5; v0.28.2 for step 6's policy signing and step 7), with its `SHA256SUMS` and `SHA256SUMS.sig`, from the public release. Verify the signature as the README's "Releasing the platform" verification does, then `sha256sum -c`. Record the sha256.
+1. Download the carlos CLI `carlos-linux-arm64` (v0.28.2: the session runs 5.1 through 7, and step 6's policy signing needs v0.28.2), with its `SHA256SUMS` and `SHA256SUMS.sig`, from the public release. Verify the signature as the README's "Releasing the platform" verification does, then `sha256sum -c`. Record the sha256.
2. Download jq 1.7.1 `jq-linux-arm64` and the release's `sha256sum.txt` from `github.com/jqlang/jq/releases/tag/jq-1.7.1`, then check it with `sha256sum -c`. Record the sha256.
3. Upload both to `s3://carlos-dev-640012953448/claim-signer/<run-id>/`.
@@ -114,7 +114,7 @@ chmod 0755 /usr/local/bin/carlos /usr/local/bin/jq
/usr/local/bin/carlos version
```
-Expected: both `OK`, then `carlos v0.28.1`. For step 6 onwards, use the v0.28.2 binary and its sha256 instead (TO-CONFIRM when published).
+Expected: both `OK`, then `carlos v0.28.2`. The session uses the v0.28.2 binary: replace the carlos sha256 above with v0.28.2's (TO-CONFIRM when published). The v0.28.1 value is kept for reference.
## Create (each part needs Paul's go)
@@ -157,7 +157,7 @@ aws ec2 terminate-instances --instance-ids "$I" && aws ec2 wait instance-termina
aws s3 rm "s3://carlos-dev-640012953448/claim-signer/<run-id>/<each exact key>"
```
-After step 7, when no more signing is needed:
+At the end of the session (after step 7, or at any stop), straight after terminating the instance:
```bash
aws iam remove-role-from-instance-profile --instance-profile-name carlos-claim-signer --role-name carlos-claim-signer
diff --git a/docs/superpowers/notes/claim-trial-inputs/sophie.json b/docs/superpowers/notes/claim-trial-inputs/sophie.json
index 571dfe272..a08c08028 100644
--- a/docs/superpowers/notes/claim-trial-inputs/sophie.json
+++ b/docs/superpowers/notes/claim-trial-inputs/sophie.json
@@ -6,5 +6,5 @@
"pool": "console",
"aws_region": "eu-west-1",
"instance_id": "i-05498eada6f0afd23",
- "operator": "TO-CONFIRM (Paul): the approving operator's identity; suggested default paul@keymail.dev"
+ "operator": "paul@keymail.dev"
}
diff --git a/docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md b/docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md
index f79788fcd..2f08bc6fc 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
@@ -191,13 +191,13 @@ Pass: all twelve are root-owned regular files in `/etc/systemd/system`; the edge
## 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), which unblocks step 5, and v0.28.2 (gap 1 and 2 fixes), which step 6 waits for.
+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.
2. **Empty app segment.** 267 objects under `apps/bab//` (P5). Source: leo's app-less `proof*` routes, removed (2026-10-16). Cleanup waits for the e8e961dd release to reach leo and for Paul's approval. Step 2.
3. **IAM.** Leo's object-version actions were applied on 2026-10-18 (targeted, `0 to add, 1 to change, 0 to destroy`): the claim's data store lists, reads and deletes object versions in `carlos-dev-ie-640012953448` on the seed and cold-wake paths. The fence-writer role (`carlos-dev-ie-b-fence-writer`, bootstrap user `carlos-dev-ie-b-fence-writer-root`, no access key yet) and the deployment bucket policy were applied on 2026-10-18 from the saved targeted plan (`5 added, 0 changed, 0 destroyed`) and merged as platform-infrastructure#46 (`beb346b`). The bucket policy names that role as the only fence writer. After the apply, wakecheck still served on sophie (200) and leo answered `waking elsewhere` (sophie held the lease), with no AccessDenied in either box's journals. Step 3.
-4. **Box identity records.** Step 5's enrollment needs each box's signed AWS identity record at `control/boxes/<box>.json`. None exists, and none has ever been written: the publisher (`internal/host/boxrecord.go`, `boxRecordLoop`) runs only with `CARLOS_REGION_HA` on, which is off on every dev box. Leo's and sophie's records must be published before step 5. Paul ruled a reviewed one-off publish, with region HA staying off.
+4. **Box identity records.** Resolved 2026-10-18 (step 5.0 done: `control/boxes/leo-climbing.json` and `control/boxes/sophie-knitting.json`). Step 5's enrollment needs each box's signed AWS identity record at `control/boxes/<box>.json`. None exists, and none has ever been written: the publisher (`internal/host/boxrecord.go`, `boxRecordLoop`) runs only with `CARLOS_REGION_HA` on, which is off on every dev box. Leo's and sophie's records must be published before step 5. Paul ruled a reviewed one-off publish, with region HA staying off.
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 (step 6 onwards).** 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 is building the fixes, with Paul's go: (a) a `claims run` phase that signs the armed policy's authorization for a floor on the trusted host without publishing anything; (b) the release workflow publishing the signed system identity (router and console), and the protected console install with its `.prev`. Both ship in v0.28.2, so the floor's `min_release` is v0.28.2. Step 5 does not need them. Details in `docs/superpowers/notes/claim-trial-inputs/README.md`.
+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 is building the fixes, with Paul's go: (a) a `claims run` phase that signs the armed policy's authorization for a floor on the trusted host without publishing anything; (b) the release workflow publishing the signed system identity (router and console), and the protected console install with its `.prev`. Both ship in v0.28.2, so the floor's `min_release` is v0.28.2. By Paul's ruling the signing session (5.1 to 7) opens only after v0.28.2, so this blocker gates everything past 5.0. Details in `docs/superpowers/notes/claim-trial-inputs/README.md`.
## Accepted trial gaps
@@ -240,7 +240,7 @@ Hazards: H3 (#272, never the old road on leo) and H4 (the old-updater instance-c
On failure: `carlos system rollback --console https://platform.dev.carlosframework.com --channel stable` (and `canary`). This is an ordinary claims-off release rollback, and it works only while no actor is armed. From step 6 on, never run below the floor.
-Failure handling for the rest of the runbook: steps 1 to 4 change nothing claim-related, so their own undo applies. From step 5 on, a failure means stop and hold. Before step 8 there are no claim-mode units, so "hold" means: stop the runbook, change nothing more, and leave leo serving claims off as it is. From step 8 on, follow "Stop and hold".
+Failure handling for the rest of the runbook: steps 1 to 4 change nothing claim-related, so their own undo applies. From step 5.1 on, a failure means stop and hold. Before step 8 there are no claim-mode units, so "hold" means: stop the runbook, change nothing more, and leave leo serving claims off as it is. From step 8 on, follow "Stop and hold".
## Step 2. Clean up the empty app segment
@@ -311,43 +311,18 @@ Why a pool: dev's two home boxes both mint every unregioned, pool-less record. A
`claimtrial` is the only host the files seed and acceptance touch. It holds nothing anyone needs.
-## Steps 5 to 7: the activation tools
+## Step 5. Box identity records, roster and enrollment
-Steps 5 to 7 use `carlos system claims` from main. Its help text on main:
+Step 5.0 is done. 5.1 (roster and enrollment) runs inside the signing session, after v0.28.2 (see "The signing session").
-```
-usage: carlos system claims <command> [flags]
-
- inspect --bucket B --aws-region R --public-key K
- run --plan FILE --signing-key FILE --public-key K --aws-region R
- evidence-key --roster FILE --expect-revision N --bucket B --box BOX --enrollment E --public-key K
- fence-credentials --source FILE --bucket B --aws-region R --role ARN
- console-config --roster FILE --expect-revision N --bucket B --console-id ID --public-key K [--enable-console-runtime]
- console-report --bucket B --aws-region R --expect-revision N --console-id ID --public-key K
- install-control
-
-For commands that write, add --dry-run to validate and print the planned operation without writing.
-Run signing phases on the trusted operator host. Run box installers as root.
-```
-
-`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`. 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.
+### 5.0 Box identity records (done 2026-10-18)
-**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 CLI (v0.28.1 for step 5, v0.28.2 from step 6), both checked by sha256. It runs only the signing helpers (`refresh`, `apply_plan`, `phase`) 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.
-
-The controller supplies, and Paul approves, the concrete inputs: the signed release, floor and guard artifacts; namespace, pool and label inputs (leo's pool `cohort1`, sophie's matching its real placement); the console identity; the fence role and credentials from step 3; and leo's reviewed host.env changes. Revalidate both instance IDs first. The drafts, with every value that could not be confirmed read-only marked `TO-CONFIRM`, are in `docs/superpowers/notes/claim-trial-inputs/`.
-
-## Step 5. Roster, enrollment and console enrollment
+**Done on 2026-10-18, on v0.28.1 (`3b69c2bd`), each box on its own Paul's go:**
+- leo published at 06:10:49: `control/boxes/leo-climbing.json`, 919 bytes. Its verify re-run said "nothing was written".
+- sophie published at 06:11:32: `control/boxes/sophie-knitting.json`, 922 bytes. Its verify re-run said "nothing was written".
+- Afterwards wakecheck answered 200, both edges were active, and there were no new AccessDenied lines (sophie's two were the known Cost Explorer lines).
-Unblocked: v0.28.1 (`3b69c2bd`) is on dev since 2026-10-18 (leo, sophie and dennis current and armed with 0 REFUSING; the dev console on `3b69c2bd`). It needs Paul's approval of the step 5 package (the inputs in `docs/superpowers/notes/claim-trial-inputs/`, the signing host, and the box-record publishes), but not the gap 1 and 2 fixes.
-
-Paul's go: once for step 5.
-
-### 5.0 Box identity records
+The procedure as run, kept for the record:
Each box's signed AWS identity record must be in place at `control/boxes/<box>.json` before the box is invited. They come from `carlos system box publish-record` (v0.28.1, dev only), run once on each box. A missing or refused record is a stop, not permission to make one up.
@@ -400,8 +375,51 @@ 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.
+## The signing session: 5.1, 6 and 7
+
+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:
+1. **Open the session:** create the signer (TO-CREATE, Paul's go), install the verified tools, and fetch the key into tmpfs (`signing-host.md`, "Create" and "Getting the key there").
+2. **5.1** roster and enrollment.
+3. **Step 6:** arm leo's guard and start the guarded console.
+4. **Step 7:** readers, floor, home and leo's key.
+5. **Close the session:** shred the key, terminate the signer, delete its role, profile and security group, and run the CloudTrail check ("Closing the signing session", after step 7).
+
+Nothing else touches the signer between opening and closing: no other work, no other users, no second instance. Each of 5.1, 6 and 7 still has its own Paul's go inside the session. A stop at any point ends the session (see "Stop and hold").
+
+Steps 5.1 to 7 use `carlos system claims` from main. Its help text on main:
+
+```
+usage: carlos system claims <command> [flags]
+
+ inspect --bucket B --aws-region R --public-key K
+ run --plan FILE --signing-key FILE --public-key K --aws-region R
+ evidence-key --roster FILE --expect-revision N --bucket B --box BOX --enrollment E --public-key K
+ fence-credentials --source FILE --bucket B --aws-region R --role ARN
+ console-config --roster FILE --expect-revision N --bucket B --console-id ID --public-key K [--enable-console-runtime]
+ console-report --bucket B --aws-region R --expect-revision N --console-id ID --public-key K
+ install-control
+
+For commands that write, add --dry-run to validate and print the planned operation without writing.
+Run signing phases on the trusted operator host. Run box installers as root.
+```
+
+`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`. 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`) 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.
+
+The controller supplies, and Paul approves, the concrete inputs: the signed release, floor and guard artifacts; namespace, pool and label inputs (leo's pool `cohort1`, sophie's matching its real placement); the console identity; the fence role and credentials from step 3; and leo's reviewed host.env changes. Revalidate both instance IDs first. The drafts, with every value that could not be confirmed read-only marked `TO-CONFIRM`, are in `docs/superpowers/notes/claim-trial-inputs/`.
+
### 5.1 Roster and enrollment
+Inside the signing session. Paul's go: once for 5.1.
+
On the trusted signing host, the helpers. The first block is the report's; the second adds the SSM transfer helpers that replace `jq` on the boxes:
```bash
@@ -659,6 +677,26 @@ Expected:
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.
+## Closing the signing session
+
+Straight after step 7 passes, or at any stop inside the session. Paul's go is not needed to close; closing is always allowed.
+
+1. On the signer, as root: `shred -u /run/claim-signer/release.key && rmdir /run/claim-signer`.
+2. From DOCS-T1: terminate the instance, remove the exchange objects under `claim-signer/<run-id>/` by exact key, then delete the role, instance profile and security group (`signing-host.md`, "Teardown").
+3. CloudTrail check, from DOCS-T1, with the session's start time:
+
+ ```bash
+ aws --profile kass --region eu-west-1 cloudtrail lookup-events \
+ --lookup-attributes AttributeKey=EventName,AttributeValue=GetParameter --start-time <session-start> \
+ --query 'Events[].CloudTrailEvent' --output json \
+ | jq -r '.[] | fromjson | select(.requestParameters.name == "/carlos/release-key") | [.eventTime, .userIdentity.arn] | @tsv'
+ aws --profile kass ec2 describe-instances --region eu-west-1 --filters Name=tag:purpose,Values=claim-signer \
+ Name=instance-state-name,Values=pending,running,stopping,stopped --query 'Reservations[].Instances[].InstanceId' --output text
+ aws --profile kass iam get-role --role-name carlos-claim-signer
+ ```
+
+ Expected: every `GetParameter` on `/carlos/release-key` since the session started comes from `arn:aws:sts::640012953448:assumed-role/carlos-claim-signer/<the signer's instance id>`, and there are as many as the session made (one per key fetch); no instance listed; `NoSuchEntity` for the role. Anything else is reported on #250 before any further step.
+
## Step 7a. Fence-writer credentials
Ruled by platform-1e on 2026-10-17: option (a), a one-time SecureString transfer, with two tightenings. This step installs leo's fence-writer credentials (the bootstrap user's access key and the role ARN from step 3) at `/etc/carlos/fence-writer/credentials.json`.
@@ -1370,7 +1408,7 @@ This replaces a claims-off rollback for the trial. If anything from step 5 on fa
Paul's go: not needed to stop. Stopping is always allowed. Anything after it needs a go.
-**Steps 5 to 7.** Nothing claim-mode runs yet. Stop the runbook and change nothing more; leo keeps serving claims off.
+**Inside the signing session (5.1 to 7).** Nothing claim-mode runs on leo yet. A stop ends the session: close it at once ("Closing the signing session": shred the key, terminate the signer, delete its role, profile and security group, run the CloudTrail check). Leave leo, sophie, the roster, the floor and the bucket as they are; leo keeps serving claims off. Resuming needs Paul's go for a new signing session, which would put the release key on a host a second time.
**Step 8, before the seed starts (8.1, 8.2).** The six services, five triggers and the edge are already persistently masked. Leave every mask and `$WINDOW` as they are. Leave instance-control as it is (running if 8.2 started it).
@@ -1465,5 +1503,5 @@ 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). Step 6 waits for it; step 5 does not.
-7. Still open in `docs/superpowers/notes/claim-trial-inputs/README.md`: the operator identity (Paul; suggested default `paul@keymail.dev`), the console enrollment token, the v0.28.1 and v0.28.2 artifacts, and the signing host values. Namespace `platform`, sophie's pool `console` and console ID `dev-console` are confirmed.
+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.
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.