carlos / platform Public

docs: cohort-1 runbook 7a on /carlos/cohort1, PR #45 plan and simulation; drift owed

- Fence parameter moves to /carlos/cohort1/fence/carlos-dev-ie-b (Paul,
  option A). The dedicated key carries the separation; sophie can fetch
  the ciphertext through /carlos/* (accepted), dev-uk cannot, and both
  are denied kms:Decrypt.
- 7a.1 quotes the file on platform-infrastructure PR #45 and its
  targeted plan (3 to add, 0 to change, 0 to destroy, on main f609434
  with #44 merged), plus the pre-apply policy simulation and lockout
  check. Add and remove plans are targeted at the three resources.
- New "Owed before production cohorts": the dev module drift (15 adds,
  1 change, including leo's object-version actions and the SSM scope
  policy), with its own plan and Paul's approval later; not part of 7a.
  Open question on step 3's overlap with it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 555a31777a5a9d9109209860bc15207dd71e6f21 parent 93d1bf9
1 file changed, +65 −22
  • docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md +65 −22

CI passed — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.011s
?   	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	293.213s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	2.839s
ok  	github.com/carlosframework/platform/cmd/moveprobe	92.155s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.700s
ok  	github.com/carlosframework/platform/internal/activator	251.387s
ok  	github.com/carlosframework/platform/internal/agentsock	0.015s
ok  	github.com/carlosframework/platform/internal/alerting	0.003s
ok  	github.com/carlosframework/platform/internal/analytics	0.024s
ok  	github.com/carlosframework/platform/internal/appconfig	2.458s
ok  	github.com/carlosframework/platform/internal/apperr	0.070s
ok  	github.com/carlosframework/platform/internal/applog	0.125s
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.638s
?   	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.009s
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.695s
ok  	github.com/carlosframework/platform/internal/changelog	0.305s
ok  	github.com/carlosframework/platform/internal/claimactivation	0.026s
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	400.604s
?   	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.439s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.009s
ok  	github.com/carlosframework/platform/internal/dbverify	0.004s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.002s
ok  	github.com/carlosframework/platform/internal/docsite	0.004s
ok  	github.com/carlosframework/platform/internal/domains	2.384s
ok  	github.com/carlosframework/platform/internal/doorkey	0.006s
ok  	github.com/carlosframework/platform/internal/economics	0.003s
ok  	github.com/carlosframework/platform/internal/edge	44.827s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.007s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.129s
ok  	github.com/carlosframework/platform/internal/gate	1.656s
ok  	github.com/carlosframework/platform/internal/genselect	0.014s
ok  	github.com/carlosframework/platform/internal/geo	0.017s
ok  	github.com/carlosframework/platform/internal/geoip	0.031s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.150s
ok  	github.com/carlosframework/platform/internal/host	173.912s
ok  	github.com/carlosframework/platform/internal/hostactors	0.629s
ok  	github.com/carlosframework/platform/internal/hostbox	2.685s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.544s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.006s
ok  	github.com/carlosframework/platform/internal/hostexec	4.735s
ok  	github.com/carlosframework/platform/internal/hostfence	0.038s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.400s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.017s
ok  	github.com/carlosframework/platform/internal/hostguard	0.993s
ok  	github.com/carlosframework/platform/internal/hostops	1.587s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.004s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.003s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.455s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.139s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.002s
ok  	github.com/carlosframework/platform/internal/infraview	0.002s
ok  	github.com/carlosframework/platform/internal/instancectl	5.209s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.746s
ok  	github.com/carlosframework/platform/internal/instancefs	0.018s
ok  	github.com/carlosframework/platform/internal/instancekey	0.014s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.006s
ok  	github.com/carlosframework/platform/internal/layout	1.512s
ok  	github.com/carlosframework/platform/internal/lbhealth	0.002s
ok  	github.com/carlosframework/platform/internal/lease	9.806s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.007s
ok  	github.com/carlosframework/platform/internal/ledger	0.013s
ok  	github.com/carlosframework/platform/internal/mail	0.151s
ok  	github.com/carlosframework/platform/internal/maintenance	0.084s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.473s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.077s
ok  	github.com/carlosframework/platform/internal/metrics	0.004s
ok  	github.com/carlosframework/platform/internal/moves	20.869s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.813s
ok  	github.com/carlosframework/platform/internal/objstore	0.181s
ok  	github.com/carlosframework/platform/internal/ops	0.164s
ok  	github.com/carlosframework/platform/internal/park	0.194s
ok  	github.com/carlosframework/platform/internal/privatefile	0.003s
ok  	github.com/carlosframework/platform/internal/provision	17.180s
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.376s
ok  	github.com/carlosframework/platform/internal/relay	7.224s
ok  	github.com/carlosframework/platform/internal/release	21.129s
ok  	github.com/carlosframework/platform/internal/rollout	3.593s
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.057s
ok  	github.com/carlosframework/platform/internal/rootstate	0.026s
ok  	github.com/carlosframework/platform/internal/runs	0.574s
ok  	github.com/carlosframework/platform/internal/s3compat	0.004s
ok  	github.com/carlosframework/platform/internal/safefile	0.004s
ok  	github.com/carlosframework/platform/internal/schedule	2.469s
ok  	github.com/carlosframework/platform/internal/seeds	0.011s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	8.354s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	3.760s
ok  	github.com/carlosframework/platform/internal/status	0.039s
ok  	github.com/carlosframework/platform/internal/steering	0.005s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.232s
ok  	github.com/carlosframework/platform/internal/svcrole	0.002s
ok  	github.com/carlosframework/platform/internal/telemetry	0.003s
ok  	github.com/carlosframework/platform/internal/termfence	0.003s
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.549s
=== 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.021s
=== RUN   TestT4StorageProbeMinio
2026/10/17 23:26:28 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DF74CA45CE9FFF, 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.00s)
PASS
ok  	github.com/carlosframework/platform/internal/host	0.019s
minio contract: PASS
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 7a2f59066..1d8cf4206 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
@@ -202,6 +202,10 @@ Paul deferred these for this trial only. They are not blockers here, and they mu
- **`fence-credentials --source -`.** For the trial, the credentials pass through a tmpfs staging file under `/run` (step 7a.2). Reading standard input instead is a follow-up for production cohorts (platform-1e is filing it on amadan).
- **Key rotation.** The release signing key is not rotated for the trial, and no rotation schedule is set for the fence-writer bootstrap keys. (The separate exposure, box roles reading `/carlos/release-key`, is resolved; see P2.)
+## Owed before production cohorts
+
+- **Dev module drift.** A whole-root `tofu plan` of `platform-infrastructure/dev` on 2026-10-17 showed 15 adds and 1 change that come from the platform modules (pinned `?ref=main`) moving ahead of dev: termination SQS queues and EventBridge rules for both IE boxes, the deployment bucket's lifecycle rule and its box-records bucket policy, leo's object-version actions (`host_buckets` gains `PullDoorObjectVersions`, `s3:DeleteObjectVersion` and `PullDoorVersionList`), and an SSM parameter scope policy on both IE roles (a deny on parameter reads outside `/carlos/*`). It is owed: its own plan and Paul's approval later, before production cohorts. It is not part of step 7a, whose plan is targeted at its own three resources. Step 3's IAM overlaps it (see open question 3).
+
## Step 1. Release and roll, claims off
What: v0.28.0, cut from main at 26065378 (it contains Plan 14 Parts 2 and 3, the activation tools and e8e961dd), rolled with every claim switch unset. Dev rolls first. The trial waits until dev is rolled and checked. Flagship does not need this release before dev's window (Paul, 2026-10-16); its roll is separate.
@@ -592,8 +596,8 @@ Scope: the independent claim writer (Plan 15) is deferred for this trial. This s
Rules for the whole step:
- The credential values never appear in an SSM command parameter, a command line, shell history, a log, terminal output or CloudTrail request data. SSM command history and CloudTrail store command parameters, so `box_put` is never used for these credentials.
-- The parameter lives outside `/carlos/`, at `/carlos-staging/fence/carlos-dev-ie-b`. Every dev box role may read `/carlos/*`, so under `/carlos/` sophie's role could read leo's credentials.
-- It is encrypted with a dedicated KMS key whose key policy lets only leo's role (`carlos-dev-host-b`) decrypt. A grant on the parameter path alone is not enough.
+- The parameter is `/carlos/cohort1/fence/carlos-dev-ie-b` (Paul, 2026-10-18, option A). The dedicated KMS key carries the separation: only leo's role (`carlos-dev-host-b`) may decrypt, the encryption context binds decryption to that parameter's ARN, and an explicit deny covers everyone else. A grant on the parameter path alone would not be enough.
+- Every dev box role may read `/carlos/*`, so sophie's role can fetch the parameter's ciphertext (dev-uk's role cannot: its parameter scope denies eu-west-1). Both are denied `kms:Decrypt`, so a fetch with decryption fails. Paul accepted the ciphertext exposure.
Why not option (b), leo's role fetching the credentials whenever it needs them: leo's instance role is reachable from any process on leo through the instance metadata service (IMDS), including the carlos agent. A standing grant would let the agent fetch the root fence writer's credentials at will, which removes the separation between the root fence writer and the box role (Plan 9 A3, #250 M-2). With (a), the parameter exists for minutes, leo's read grant is removed straight after, and the only remaining copy is root-owned and `0600` on leo.
@@ -605,15 +609,34 @@ Paul's go: separate gate, for creating the key, its policy and leo's temporary r
The operator principal in the key policy is `arn:aws:iam::640012953448:user/remote-agent`, the user behind profile `kass`. platform-1e will state this when taking 7a.1 to Paul. It must also be the principal that runs `tofu apply`, or the key policy lockout check refuses the key.
-Dev's infrastructure is OpenTofu in `platform-infrastructure/dev`, so the key is declared there. Add this file on a reviewed branch:
+Dev's infrastructure is OpenTofu in `platform-infrastructure/dev`, so the key is declared there. The file is on branch `claim-cohort1-fence-kms`, PR carlosframework/platform-infrastructure#45 (opened 2026-10-18 on Paul's go for a plan and PR only; not applied). Its content:
```hcl
# platform-infrastructure/dev/cohort1-fence-kms.tf
-# Dev cohort-1 mechanics trial only. Remove this file after step 7a.2.
+# Dev cohort-1 mechanics trial only (host-claim activation on
+# carlos-dev-ie-b / leo-climbing). Runbook step 7a.1 in carlosframework/platform,
+# docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md.
+#
+# A dedicated KMS key for the one-time transfer of leo's fence-writer
+# credentials through a SecureString at /carlos/cohort1/fence/carlos-dev-ie-b.
+# Every dev box role may read /carlos/*, so sophie's and dev-uk's roles can
+# fetch the ciphertext; that is accepted (Paul, 2026-10-18), because the key
+# carries the separation: only leo's role may decrypt, only for that
+# parameter's encryption context, and an explicit deny covers everyone else.
+# There is no account-root statement, so IAM policies alone grant nothing on
+# this key. The operator administers and encrypts but cannot decrypt.
+#
+# leo's role already reads /carlos/* through its host_buckets policy; the
+# explicit one-parameter grant below keeps reading this parameter tied to the
+# trial, and still holds once the module's SSM parameter scope (a deny outside
+# /carlos/*) is applied.
+#
+# Remove this file after step 7a.2: that deletes the alias and leo's read
+# grant and schedules the key's deletion (7-day window).
locals {
- cohort1_fence_param = "arn:aws:ssm:eu-west-1:640012953448:parameter/carlos-staging/fence/carlos-dev-ie-b"
- cohort1_leo_role_arn = "arn:aws:iam::640012953448:role/carlos-dev-host-b"
- cohort1_operator_arns = ["arn:aws:iam::640012953448:user/remote-agent"]
+ cohort1_fence_param = "arn:aws:ssm:eu-west-1:640012953448:parameter/carlos/cohort1/fence/carlos-dev-ie-b"
+ cohort1_leo_role_arn = "arn:aws:iam::640012953448:role/carlos-dev-host-b"
+ cohort1_operator_arns = ["arn:aws:iam::640012953448:user/remote-agent"]
}
resource "aws_kms_key" "cohort1_fence" {
@@ -672,13 +695,16 @@ resource "aws_iam_role_policy" "cohort1_fence_read" {
The key policy has no statement for the account root, so IAM policies alone grant nothing on this key: only the listed operator administers and encrypts, and only leo's role decrypts, and only for this one parameter (SSM passes the parameter ARN as encryption context). The operator has no `kms:Decrypt`. Leo's role does not otherwise reach the parameter, because its existing grant covers `parameter/carlos/*` only.
+The plan is targeted, because the dev root carries unrelated module drift (owed separately, see "Owed before production cohorts"). It was run on 2026-10-18 from the PR branch, on main `f609434` (which includes #44, the release-key deny), and saved so the apply uses exactly this plan:
+
```bash
cd platform-infrastructure/dev
-tofu plan -out=cohort1-fence.plan
-tofu apply cohort1-fence.plan
+tofu plan -target=aws_kms_key.cohort1_fence -target=aws_kms_alias.cohort1_fence \
+ -target=aws_iam_role_policy.cohort1_fence_read -out=cohort1-fence-targeted.tfplan
+tofu apply cohort1-fence-targeted.tfplan
```
-Expected plan: exactly three additions (`aws_kms_key.cohort1_fence`, `aws_kms_alias.cohort1_fence`, `aws_iam_role_policy.cohort1_fence_read`) and nothing else. Stop and ask on any other change.
+Expected plan, and the result on 2026-10-18: `Plan: 3 to add, 0 to change, 0 to destroy.` (`aws_iam_role_policy.cohort1_fence_read`, `aws_kms_alias.cohort1_fence`, `aws_kms_key.cohort1_fence`). Stop and ask on any other change. The apply waits for Paul's go against that exact plan.
Checks after apply, read-only, from the operator host:
@@ -686,16 +712,31 @@ Checks after apply, read-only, from the operator host:
for r in carlos-dev-host-b carlos-dev-host carlos-dev-host-uk; do
echo "$r $(aws --profile kass iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::640012953448:role/$r --action-names ssm:GetParameter \
- --resource-arns arn:aws:ssm:eu-west-1:640012953448:parameter/carlos-staging/fence/carlos-dev-ie-b \
+ --resource-arns arn:aws:ssm:eu-west-1:640012953448:parameter/carlos/cohort1/fence/carlos-dev-ie-b \
--query 'EvaluationResults[0].EvalDecision' --output text)"
done
aws --profile kass --region eu-west-1 kms get-key-policy --key-id alias/cohort1-fence-leo --policy-name default \
--query Policy --output text | python3 -c 'import json,sys;print([(s["Sid"],s["Effect"]) for s in json.load(sys.stdin)["Statement"]])'
```
-Expected: `carlos-dev-host-b allowed`, the other two `implicitDeny`; the three statement IDs as above.
+Expected: `carlos-dev-host-b allowed`; `carlos-dev-host allowed` (ciphertext only, accepted); `carlos-dev-host-uk explicitDeny`; the three statement IDs as above.
+
+Simulated before the apply, on 2026-10-18 (method in PR #45: `simulate-principal-policy` on the real roles for SSM; `simulate-custom-policy` with the planned key policy as the resource policy for KMS, an IAM-user stand-in in `Principal`, the real role ARN as `aws:PrincipalArn` context):
+
+| Principal | Action | Result |
+|---|---|---|
+| leo, with the planned grant | `ssm:GetParameter` | allowed |
+| leo, context = the trial parameter | `kms:Decrypt` | allowed |
+| leo, context = another parameter | `kms:Decrypt` | implicitDeny |
+| sophie | `ssm:GetParameter` | allowed (ciphertext only) |
+| sophie | `kms:Decrypt` | explicitDeny |
+| dev-uk | `ssm:GetParameter` | explicitDeny |
+| dev-uk | `kms:Decrypt` | explicitDeny |
+| `remote-agent` | `kms:Decrypt` | explicitDeny |
+| `remote-agent` | `kms:Encrypt` | allowed |
+| `remote-agent` | `ScheduleKeyDeletion`, `CancelKeyDeletion`, `PutKeyPolicy`, `DescribeKey`, `GetKeyPolicy` | allowed (no lockout) |
-Rollback: remove `cohort1-fence-kms.tf` and run `tofu plan` and `tofu apply`. That deletes the alias and leo's read grant and schedules the key's deletion (7-day window). Until the window ends, `aws --profile kass --region eu-west-1 kms cancel-key-deletion --key-id <key-id>` can undo it.
+Rollback: remove `cohort1-fence-kms.tf` and run `tofu plan -target=aws_kms_key.cohort1_fence -target=aws_kms_alias.cohort1_fence -target=aws_iam_role_policy.cohort1_fence_read` and `tofu apply` of that plan. That deletes the alias and leo's read grant and schedules the key's deletion (7-day window). Until the window ends, `aws --profile kass --region eu-west-1 kms cancel-key-deletion --key-id <key-id>` can undo it.
### 7a.2 Transfer, install, delete, revoke
@@ -705,7 +746,7 @@ Paul's go: separate, once 7a.1 passes. Do the five parts back to back, in one at
```bash
cd /root/claim-trial
-aws --profile kass --region eu-west-1 ssm put-parameter --name /carlos-staging/fence/carlos-dev-ie-b \
+aws --profile kass --region eu-west-1 ssm put-parameter --name /carlos/cohort1/fence/carlos-dev-ie-b \
--type SecureString --key-id alias/cohort1-fence-leo --value file:///root/claim-trial/fence-credentials.json \
--query Version --output text
python3 -c 'import json,sys,hashlib;print(hashlib.sha256(json.dumps(json.load(open(sys.argv[1])),sort_keys=True).encode()).hexdigest()[:8])' fence-credentials.json
@@ -731,7 +772,7 @@ The AWS CLI on leo (v2, `/usr/bin/aws`) uses leo's instance role; do not set a p
install -d -m 0700 -o root -g root "$STAGE"
test ! -e "$SRC"
trap 'rm -f -- "$SRC"' EXIT
- aws --region eu-west-1 ssm get-parameter --name /carlos-staging/fence/carlos-dev-ie-b --with-decryption \
+ aws --region eu-west-1 ssm get-parameter --name /carlos/cohort1/fence/carlos-dev-ie-b --with-decryption \
--query Parameter.Value --output text | install -m 0600 -o root -g root /dev/stdin "$SRC"
stat -c '%U %a %h %s' "$SRC"
python3 -c 'import json,sys,hashlib;print(hashlib.sha256(json.dumps(json.load(open(sys.argv[1])),sort_keys=True).encode()).hexdigest()[:8])' "$SRC"
@@ -762,31 +803,32 @@ Nothing prints a credential value. If anything fails, the `trap` has already rem
**3. Delete the parameter immediately.** On the trusted host:
```bash
-aws --profile kass --region eu-west-1 ssm delete-parameter --name /carlos-staging/fence/carlos-dev-ie-b
+aws --profile kass --region eu-west-1 ssm delete-parameter --name /carlos/cohort1/fence/carlos-dev-ie-b
```
**4. Verify it is gone.**
```bash
aws --profile kass --region eu-west-1 ssm describe-parameters \
- --parameter-filters Key=Name,Values=/carlos-staging/fence/carlos-dev-ie-b --query 'Parameters[].Name' --output text
+ --parameter-filters Key=Name,Values=/carlos/cohort1/fence/carlos-dev-ie-b --query 'Parameters[].Name' --output text
```
Expected: empty output. Deleting a parameter removes all its versions and history.
-**5. Straight after, remove leo's grant and schedule the key's deletion.** On the reviewed branch, remove `cohort1-fence-kms.tf`, then:
+**5. Straight after, remove leo's grant and schedule the key's deletion.** On the reviewed branch, remove `cohort1-fence-kms.tf`, then (targeted, as for the add):
```bash
cd platform-infrastructure/dev
-tofu plan -out=cohort1-fence-remove.plan
+tofu plan -target=aws_kms_key.cohort1_fence -target=aws_kms_alias.cohort1_fence \
+ -target=aws_iam_role_policy.cohort1_fence_read -out=cohort1-fence-remove.plan
tofu apply cohort1-fence-remove.plan
aws --profile kass iam simulate-principal-policy --policy-source-arn arn:aws:iam::640012953448:role/carlos-dev-host-b \
- --action-names ssm:GetParameter --resource-arns arn:aws:ssm:eu-west-1:640012953448:parameter/carlos-staging/fence/carlos-dev-ie-b \
+ --action-names ssm:GetParameter --resource-arns arn:aws:ssm:eu-west-1:640012953448:parameter/carlos/cohort1/fence/carlos-dev-ie-b \
--query 'EvaluationResults[0].EvalDecision' --output text
aws --profile kass --region eu-west-1 kms describe-key --key-id <key-id> --query KeyMetadata.KeyState --output text
```
-Expected: the plan removes exactly the three resources; `implicitDeny`; `PendingDeletion`. If `tofu` cannot run straight away, do the same by hand and reconcile the branch afterwards:
+Expected: the plan removes exactly the three resources; `allowed` (leo's role still reads `/carlos/*` through `host_buckets`, but the parameter is gone and decryption is no longer possible); `PendingDeletion`. If `tofu` cannot run straight away, do the same by hand and reconcile the branch afterwards:
```bash
aws --profile kass iam delete-role-policy --role-name carlos-dev-host-b --policy-name cohort1-fence-read
@@ -1335,8 +1377,9 @@ Not part of this trial (Paul, 2026-10-16). dennis-stenciling is placement `uk`,
## Open questions
-Answered: dev-uk is out of this trial; the console is enrolled by the activation tools; the independent writer and key rotation are accepted trial gaps; box roles no longer read `/carlos/release-key`; the home name is `ie`; the `proof*` routes are removed; step 4 is approved; flagship does not need the release before dev's window; the automated offline window is replaced by the manual one; `jq` runs only on the trusted host and nothing is installed on the boxes; `install-control` runs once, before `journal-migrate`; the usage and docs edits for the removed commands are confirmed by Paul and on main. Fence credentials go by option (a): a one-time SecureString outside `/carlos/`, under a dedicated KMS key only leo's role can use, deleted and revoked straight after (step 7a); on leo it passes only through a tmpfs staging file under `/run`, deleted by a `trap`; `--source -` is a follow-up for production cohorts.
+Answered: dev-uk is out of this trial; the console is enrolled by the activation tools; the independent writer and key rotation are accepted trial gaps; box roles no longer read `/carlos/release-key`; the home name is `ie`; the `proof*` routes are removed; step 4 is approved; flagship does not need the release before dev's window; the automated offline window is replaced by the manual one; `jq` runs only on the trusted host and nothing is installed on the boxes; `install-control` runs once, before `journal-migrate`; the usage and docs edits for the removed commands are confirmed by Paul and on main. Fence credentials go by option (a): a one-time SecureString at `/carlos/cohort1/fence/carlos-dev-ie-b`, under a dedicated KMS key only leo's role can use, deleted and revoked straight after (step 7a); on leo it passes only through a tmpfs staging file under `/run`, deleted by a `trap`; `--source -` is a follow-up for production cohorts.
For platform-1e:
1. Should `hostops.Apps` skip an empty app segment instead of refusing, so one stray object cannot stop activation on a whole deployment?
2. For a later teardown out of hold: how do writes made under the claim reach the claims-off restore path?
+3. Step 3 expects leo's object-version actions and the deployment bucket's box-records policy, which are part of the owed module drift. Are they applied for the trial (as part of step 3, with their own plan) or deferred with the rest of the drift?