docs: cohort-1 step 3.4 denied-operation witnesses, simulated
platform-1e ruled the step 3 item 4 witnesses run as read-only IAM policy simulations against the real roles and the live bucket and key policies. All checks pass, with no unexpected allow. The create-only refusal cites step 5.0's "nothing was written" re-runs. Live denied writes with the boxes' own credentials are added to "Owed before production cohorts".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+28
−0
docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md+28 −0
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 313.162s
? github.com/carlosframework/platform/cmd/changelog [no test files]
ok github.com/carlosframework/platform/cmd/console 3.032s
ok github.com/carlosframework/platform/cmd/moveprobe 93.325s
ok github.com/carlosframework/platform/cmd/zddprobe 0.726s
ok github.com/carlosframework/platform/internal/activator 250.779s
ok github.com/carlosframework/platform/internal/agentsock 0.034s
ok github.com/carlosframework/platform/internal/alerting 0.007s
ok github.com/carlosframework/platform/internal/analytics 0.034s
ok github.com/carlosframework/platform/internal/appconfig 2.552s
ok github.com/carlosframework/platform/internal/apperr 0.094s
ok github.com/carlosframework/platform/internal/applog 0.225s
ok github.com/carlosframework/platform/internal/awsinv 0.005s
ok github.com/carlosframework/platform/internal/boxchoice 0.005s
ok github.com/carlosframework/platform/internal/boxid 0.583s
? 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.003s
ok github.com/carlosframework/platform/internal/cgroup 0.718s
ok github.com/carlosframework/platform/internal/changelog 0.345s
ok github.com/carlosframework/platform/internal/claimactivation 0.032s
ok github.com/carlosframework/platform/internal/clamd 0.060s
? github.com/carlosframework/platform/internal/cliflag [no test files]
ok github.com/carlosframework/platform/internal/console 407.035s
? 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.240s
ok github.com/carlosframework/platform/internal/dbdoor/dbdoortest 0.013s
ok github.com/carlosframework/platform/internal/dbverify 0.007s
ok github.com/carlosframework/platform/internal/dnsrec 0.005s
ok github.com/carlosframework/platform/internal/docsite 0.007s
ok github.com/carlosframework/platform/internal/domains 2.457s
ok github.com/carlosframework/platform/internal/doorkey 0.010s
ok github.com/carlosframework/platform/internal/economics 0.005s
ok github.com/carlosframework/platform/internal/edge 44.800s
? github.com/carlosframework/platform/internal/edgeproof [no test files]
ok github.com/carlosframework/platform/internal/edgesfile 0.013s
ok github.com/carlosframework/platform/internal/fleetapi 0.145s
ok github.com/carlosframework/platform/internal/gate 1.659s
ok github.com/carlosframework/platform/internal/genselect 0.018s
ok github.com/carlosframework/platform/internal/geo 0.023s
ok github.com/carlosframework/platform/internal/geoip 0.033s
ok github.com/carlosframework/platform/internal/historyreadback 0.166s
ok github.com/carlosframework/platform/internal/host 176.734s
ok github.com/carlosframework/platform/internal/hostactors 1.037s
ok github.com/carlosframework/platform/internal/hostbox 2.699s
ok github.com/carlosframework/platform/internal/hostclaim 0.611s
ok github.com/carlosframework/platform/internal/hostevidence 0.005s
ok github.com/carlosframework/platform/internal/hostexec 4.849s
ok github.com/carlosframework/platform/internal/hostfence 0.039s
ok github.com/carlosframework/platform/internal/hostfiles 10.420s
ok github.com/carlosframework/platform/internal/hostfloor 0.012s
ok github.com/carlosframework/platform/internal/hostguard 0.999s
ok github.com/carlosframework/platform/internal/hostops 1.672s
? github.com/carlosframework/platform/internal/hostops/fencerelease [no test files]
ok github.com/carlosframework/platform/internal/hostops/internal/fencerelease 0.002s
ok github.com/carlosframework/platform/internal/hostquarantine 0.002s
ok github.com/carlosframework/platform/internal/hostrelocate 0.489s
ok github.com/carlosframework/platform/internal/hostresolve 0.153s
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.211s
ok github.com/carlosframework/platform/internal/instanceexec 2.750s
ok github.com/carlosframework/platform/internal/instancefs 0.005s
ok github.com/carlosframework/platform/internal/instancekey 0.002s
ok github.com/carlosframework/platform/internal/instanceupdates 0.008s
ok github.com/carlosframework/platform/internal/layout 1.584s
ok github.com/carlosframework/platform/internal/lbhealth 0.004s
ok github.com/carlosframework/platform/internal/lease 9.801s
ok github.com/carlosframework/platform/internal/lease/leasetest 0.005s
ok github.com/carlosframework/platform/internal/ledger 0.013s
ok github.com/carlosframework/platform/internal/mail 0.158s
ok github.com/carlosframework/platform/internal/maintenance 0.076s
ok github.com/carlosframework/platform/internal/maintenanceexec 5.549s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.068s
ok github.com/carlosframework/platform/internal/metrics 0.005s
ok github.com/carlosframework/platform/internal/moves 20.979s
ok github.com/carlosframework/platform/internal/moves/archive 0.784s
ok github.com/carlosframework/platform/internal/objstore 0.185s
ok github.com/carlosframework/platform/internal/ops 0.179s
ok github.com/carlosframework/platform/internal/park 0.204s
ok github.com/carlosframework/platform/internal/privatefile 0.008s
ok github.com/carlosframework/platform/internal/provision 17.294s
ok github.com/carlosframework/platform/internal/publicrelease 0.008s
? github.com/carlosframework/platform/internal/regionname [no test files]
ok github.com/carlosframework/platform/internal/registry 5.440s
ok github.com/carlosframework/platform/internal/relay 7.229s
ok github.com/carlosframework/platform/internal/release 22.032s
ok github.com/carlosframework/platform/internal/rollout 3.653s
ok github.com/carlosframework/platform/internal/rolloutwords 0.004s
ok github.com/carlosframework/platform/internal/rootaws 0.007s
ok github.com/carlosframework/platform/internal/rootparticipation 0.061s
ok github.com/carlosframework/platform/internal/rootstate 0.026s
ok github.com/carlosframework/platform/internal/runs 0.571s
ok github.com/carlosframework/platform/internal/s3compat 0.008s
ok github.com/carlosframework/platform/internal/safefile 0.004s
ok github.com/carlosframework/platform/internal/schedule 2.499s
ok github.com/carlosframework/platform/internal/seeds 0.012s
ok github.com/carlosframework/platform/internal/sim/hostlife 8.937s
ok github.com/carlosframework/platform/internal/sim/litestreamfacts 4.167s
ok github.com/carlosframework/platform/internal/status 0.042s
ok github.com/carlosframework/platform/internal/steering 0.006s
ok github.com/carlosframework/platform/internal/stopevidence 0.209s
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.002s
ok github.com/carlosframework/platform/internal/vet 1.576s
=== 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 10:03:54 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DF97932199F67A, 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.022s
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 71c494efc..26f57e16b 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 |
| @@ -286,6 +286,7 @@ Paul deferred these for this trial only. They are not blockers here, and they mu |
| - **Dev module drift.** A whole-root `tofu plan` of `platform-infrastructure/dev` on 2026-10-17 showed 15 adds and 1 change (13 adds after leo's `host_buckets` change and the box-records bucket policy were applied for the trial; whole-root plan from main `a0b3f5a` on 2026-10-18: `13 to add, 0 to change, 0 to destroy`) 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`; applied for the trial on 2026-10-18), 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). |
| - **A trusted build attestation for the console.** `publish-identity` (step 6) accepts a console artefact only if its `version.Build` stamp is the release tag. It reads that stamp from the `-ldflags` the Go build info records (`release.ArtifactBuild`), because release binaries are stripped (`-s -w`) and the linked value itself cannot be read. So the phase checks what the linker was told, not what it linked. Production cohorts need an attestation from a trusted builder, tied to the console artefact's sha256, that says which source and which stamp produced those bytes. The trial accepts the gap because only the operator holds the release key, so only the operator can ship and promote a console build the phase would accept. |
| +- **Live denied-operation witnesses.** For the trial, step 3 item 4 ran as IAM policy simulations only (platform-1e's ruling, 2026-10-18). Before production cohorts, the denied writes must be tried live with the boxes' own credentials, through a reviewed runner. Nothing gets installed on the boxes for the trial. |
| ## Step 1. Release and roll, claims off |
| @@ -357,6 +358,33 @@ Paul's go: to apply the dev root. |
| 3. Apply. Re-run P2's IAM lines. |
| 4. Denied-operation witnesses, through a reviewed runner: leo's instance role is refused a put to `control/box-fences/`, the fence-writer role may create there with `If-None-Match`, and nobody may delete there. |
| + **2026-10-18 09:50Z to 09:56Z: simulated, read-only, by platform-1e's ruling. All PASS, no unexpected allow.** The witnesses ran as IAM policy simulations, not live writes. Nothing was written, deleted, read or decrypted. They used profile `kass` (account `640012953448`, caller `user/remote-agent`) and were checked against the live bucket policy of `carlos-dev-640012953448` and the live key policy of `ef26a257-fc1d-4fe3-ad9e-77a4b7b789f6` (`alias/cohort1-fence-leo`). The bucket policy has four statements: `BoxRecordsAreCreateOnly`, `BoxRecordsAreNeverDeleted`, `OnlyBoxRolesWriteTerminationFences` and `OnlyOperatorsWriteBinds`. `aws iam list-roles` confirmed the role names: leo `carlos-dev-host-b`, sophie `carlos-dev-host`, dev-uk `carlos-dev-host-uk`, and the fence writer `carlos-dev-ie-b-fence-writer`. |
| + |
| + How it was run: |
| + - `aws iam simulate-principal-policy` ran against each real role ARN, so each role's live identity policies were evaluated. `--resource-policy` was set to the live bucket policy or key policy. The simulator accepted a role together with a resource policy, so `--caller-arn` was not set and no `simulate-custom-policy` fallback was needed. |
| + - Every S3 and KMS check passed `aws:PrincipalArn` as context, set to the role's ARN. The policies' denies use `ArnNotEquals` or `ArnNotLike` on that key, and a negated condition matches when the key is missing. Without it, a deny could come from the missing key instead of the policy. |
| + - Every put carried `s3:ObjectCreationOperation=true`. "Create-only" rows also carried `s3:if-none-match=*`, the only form of put the fence writer's identity policy allows. "Overwrite" rows left it unset. |
| + - The key policy's statements say `"Resource": "*"`, and the simulator rejects that (`ResourceNames are not in a valid ARN format: *`). So the KMS checks used a scratch copy with `*` replaced by the key ARN, and a diff showed nothing else changed. In a key policy `*` means the key itself, so the copy evaluates the same way. |
| + - Leo's decrypt grant is keyed on `kms:EncryptionContext:PARAMETER_ARN`, the parameter's full ARN, which SSM sends when it decrypts a SecureString. It is not keyed on `aws:ssm:parameterName`. |
| + - Positive controls, to rule out a simulator that denies everything: a create-only put of `control/boxes/carlos-dev-ie-b.json` is `allowed` for all three box roles, and leo and sophie are `allowed` to call `ssm:GetParameter` on the fence parameter. |
| + |
| + | Check | leo `carlos-dev-host-b` | sophie `carlos-dev-host` | dev-uk `carlos-dev-host-uk` | fence writer | Expected | Result | |
| + |---|---|---|---|---|---|---| |
| + | 1. `s3:PutObject` `control/box-fences/carlos-dev-ie-b.json`, create-only | explicitDeny (`OnlyBoxRolesWriteTerminationFences`) | explicitDeny (same) | explicitDeny (same) | allowed (identity `create-termination-fences`) | denied for the box roles, allowed for the fence writer | PASS | |
| + | 1. The same put, overwrite | explicitDeny (`BoxRecordsAreCreateOnly`, `OnlyBoxRolesWriteTerminationFences`) | explicitDeny (same) | explicitDeny (same) | explicitDeny (`BoxRecordsAreCreateOnly`) | denied | PASS | |
| + | 2. `s3:DeleteObject` and `s3:DeleteObjectVersion` on `<prefix>carlos-dev-ie-b.json` for each of `control/boxes/`, `control/box-volumes/`, `control/box-enrollment/` and `control/box-fences/` (8 checks per role) | explicitDeny x8 (`BoxRecordsAreNeverDeleted`) | explicitDeny x8 (same) | explicitDeny x8 (same) | explicitDeny x8 (same) | denied for all four | PASS | |
| + | 3. `s3:PutObject` `control/boxes/carlos-dev-ie-b.bind.json` (policy pattern `control/boxes/*.bind.json`), create-only | explicitDeny (`OnlyOperatorsWriteBinds`) | explicitDeny (same) | explicitDeny (same) | explicitDeny (same) | denied for all four | PASS | |
| + | 3. The same put, overwrite | explicitDeny (`BoxRecordsAreCreateOnly`, `OnlyOperatorsWriteBinds`) | explicitDeny (same) | explicitDeny (same) | explicitDeny (same) | denied for all four | PASS | |
| + | 4. `ssm:GetParameter` `arn:aws:ssm:eu-west-1:640012953448:parameter/carlos/release-key` (identity policies only; SSM parameters have no resource policy) | explicitDeny (`deny-release-key-reads`) | explicitDeny (`deny-release-key-reads`) | explicitDeny (`carlos-dev-host-uk-ssm-parameter-scope`) | implicitDeny (not required) | denied for the box roles | PASS | |
| + | 5. `kms:Decrypt` on the key, `PARAMETER_ARN` = `arn:aws:ssm:eu-west-1:640012953448:parameter/carlos/cohort1/fence/carlos-dev-ie-b` | allowed (`OnlyLeoDecryptsTheTrialParameter`) | explicitDeny (`NobodyElseDecrypts`) | explicitDeny (`NobodyElseDecrypts`) | not run | allowed for leo only | PASS | |
| + | 5. `kms:Decrypt` with any other context: `/carlos/release-key`, `/carlos/cohort1/fence/carlos-dev-ie-a`, no context, and (leo only) the fence name in eu-west-2 | implicitDeny (no allow matches; no identity policy grants `kms:Decrypt`, and the key policy has no root statement) | explicitDeny (`NobodyElseDecrypts`) | explicitDeny (`NobodyElseDecrypts`) | not run | denied | PASS | |
| + |
| + Notes: |
| + - Not simulated: the create-only behaviour itself, meaning S3 refusing an overwrite when the put carries `If-None-Match`. The overwrite rows show only that the policy denies a put without the header. The evidence for the refusal is step 5.0: on 2026-10-18, `publish-record`'s verify re-run on leo and on sophie each reported "nothing was written", so the overwrite path refused. |
| + - Sophie's role is `allowed` `ssm:GetParameter` on the fence parameter, because her identity policy grants `/carlos/*`, so she can fetch its ciphertext. `NobodyElseDecrypts` denies the decrypt. This is the ciphertext exposure Paul accepted in step 7a. Dev-uk's parameter scope denies the fetch as well. |
| + - `describe-parameters` shows that `/carlos/cohort1/fence/carlos-dev-ie-b` does not exist yet in eu-west-1, as expected before 7a.2. There is still no `/carlos/release-key` in eu-west-2. |
| + - These results cover policy, not the boxes' live credentials. Live denied writes are owed before production cohorts (see "Owed before production cohorts"). |
| + |
| The activation tools do not provision IAM. The fence-writer credentials step 7a installs come from here. |
| Expected: P2's IAM items pass, apart from the roster. |