docs: cohort-1 runbook step 7a, fence credentials by one-time SecureString
Rewrites the fence-credentials step per platform-1e's ruling, option (a) with two tightenings. Runbook text only; nothing created. - New step 7a. 7a.1 is its own Paul's-go gate: an OpenTofu file for the dev root with a dedicated KMS key (only carlos-dev-host-b may decrypt, only for the one parameter's encryption context; the operator administers and encrypts but cannot decrypt; no account-root statement), its alias, and leo's temporary ssm:GetParameter on /carlos-staging/fence/carlos-dev-ie-b, with checks and rollback. - 7a.2: the trusted host writes the SecureString from a file (--value file://), never a command line; leo reads it with get-parameter --with-decryption straight into a root 0600 file, never echoed, and installs it with fence-credentials --source (main has no stdin option: gap noted for platform-1e); then delete the parameter, verify it is gone, and remove the grant and schedule the key's deletion straight after. - box_put is no longer used for the credentials. Explains why not (b): leo's role is reachable through IMDS by the agent. Notes that the independent writer stays deferred, so this protects the trial's credentials, not Plan 15's boundary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+195
−20
docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md+195 −20
CI cancelled — superseded by a newer push — run details
CI log
=== gate === go: downloading github.com/aws/aws-sdk-go-v2 v1.44.0 go: downloading github.com/aws/aws-sdk-go-v2/service/route53 v1.64.1 go: downloading github.com/aws/aws-sdk-go-v2/service/sns v1.43.0 go: downloading github.com/aws/aws-sdk-go-v2/service/ec2 v1.324.0 go: downloading github.com/aws/aws-sdk-go-v2/service/acm v1.45.0 go: downloading github.com/benbjohnson/litestream v0.5.14 go: downloading github.com/aws/aws-sdk-go-v2/service/s3 v1.105.0 go: downloading github.com/aws/aws-sdk-go-v2/config v1.32.6 go: downloading github.com/superfly/ltx v0.5.1 go: downloading golang.org/x/sys v0.46.0 go: downloading github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.68.0 go: downloading modernc.org/sqlite v1.55.0 go: downloading github.com/oschwald/maxminddb-golang v1.13.1 go: downloading golang.org/x/crypto v0.53.0 go: downloading github.com/aws/aws-sdk-go-v2/service/iam v1.58.2 go: downloading github.com/aws/aws-sdk-go-v2/credentials v1.19.6 go: downloading github.com/aws/smithy-go v1.28.1 go: downloading github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.37.0 go: downloading github.com/aws/aws-sdk-go-v2/service/sts v1.45.6 go: downloading github.com/klauspost/compress v1.18.0 go: downloading github.com/pion/logging v0.2.4 go: downloading github.com/pion/stun/v3 v3.0.1 go: downloading github.com/aws/aws-sdk-go-v2/service/sesv2 v1.66.6 go: downloading golang.org/x/net v0.56.0 go: downloading github.com/aws/aws-sdk-go-v2/service/costexplorer v1.67.5 go: downloading github.com/pion/turn/v4 v4.1.4 go: downloading github.com/pierrec/lz4/v4 v4.1.22 go: downloading github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 go: downloading github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 go: downloading github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 go: downloading github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 go: downloading github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 go: downloading github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.40 go: downloading github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.38 go: downloading github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 go: downloading github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.40 go: downloading github.com/pion/dtls/v3 v3.0.7 go: downloading github.com/pion/transport/v3 v3.0.8 go: downloading github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.40 go: downloading github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14 go: downloading github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.23 go: downloading github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.31 go: downloading github.com/pion/randutil v0.1.0 go: downloading github.com/pion/transport/v4 v4.0.1 go: downloading github.com/prometheus/client_golang v1.17.0 go: downloading golang.org/x/sync v0.21.0 go: downloading github.com/mattn/go-isatty v0.0.20 go: downloading github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.18 go: downloading github.com/lmittmann/tint v1.1.3 go: downloading github.com/wlynxg/anet v0.0.5 go: downloading github.com/prometheus/client_model v0.5.0 go: downloading github.com/prometheus/common v0.45.0 go: downloading github.com/beorn7/perks v1.0.1 go: downloading google.golang.org/protobuf v1.36.10 go: downloading github.com/prometheus/procfs v0.12.0 go: downloading github.com/cespare/xxhash/v2 v2.3.0 go: downloading github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 go: downloading golang.org/x/text v0.38.0 go: downloading modernc.org/libc v1.74.1 go: downloading modernc.org/memory v1.11.0 go: downloading github.com/dustin/go-humanize v1.0.1 go: downloading modernc.org/mathutil v1.7.1 go: downloading github.com/google/uuid v1.6.0 go: downloading github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec 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 ./... go: downloading golang.org/x/tools v0.47.0 go: downloading golang.org/x/mod v0.37.0 gofmt -l .: clean go test ./... -count=1 -timeout 30m amadan: job cancelled — a newer push superseded it
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 63d33223a..b01d169d7 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 |
| @@ -262,7 +262,7 @@ 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. |
| -The activation tools do not provision IAM. The fence-writer credentials step 7 installs come from here. |
| +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. |
| @@ -507,7 +507,7 @@ Leo's host claims stay off until step 8. Get real fresh box evidence from both e |
| On failure: hold. Arming is one-way: keep the local armed policy, and never delete the enrollment or the guard. Restore only an image at or above the floor. |
| -## Step 7. Readers, floor, home, leo's key and fence credentials |
| +## Step 7. Readers, floor, home and leo's key |
| Paul's go: once for step 7. `home_region` becomes write-once as soon as claims are on. |
| @@ -576,29 +576,203 @@ Paul's go: once for step 7. `home_region` becomes write-once as soon as claims a |
| --expect-revision "$REV" --console-id "$CID" --public-key "$PUB" |
| ``` |
| -5. **Fence credentials.** On the trusted host, stage `fence-credentials.json` and `fence-role-arn` from step 3 on leo with `box_put` (to `/root/claim-trial/`). Then on leo: |
| - |
| - ```bash |
| - set -euo pipefail |
| - umask 077 |
| - cd /root/claim-trial |
| - FENCE_ROLE=$(cat fence-role-arn) |
| - /opt/carlos/carlos system claims fence-credentials --source /root/claim-trial/fence-credentials.json \ |
| - --bucket carlos-dev-640012953448 --aws-region eu-west-1 --role "$FENCE_ROLE" --dry-run |
| - /opt/carlos/carlos system claims fence-credentials --source /root/claim-trial/fence-credentials.json \ |
| - --bucket carlos-dev-640012953448 --aws-region eu-west-1 --role "$FENCE_ROLE" |
| - ``` |
| - |
| - The credentials file holds a secret. `box_put` puts its compressed bytes in an SSM command, so they appear in SSM command history. If that is not acceptable, use the approved secret transport instead and check the file's sha256 on both sides. |
| - |
| Expected: |
| - the `jq -e` in 3 prints `true`; |
| - `/etc/carlos/host-evidence.key` exists on leo (`root`, `600`, one link); |
| - `console.service` is `active` and its report publishes; if publication loses a race with the agent's own report, run `box_inspect` again, report again and retry with the current revision; |
| -- `/etc/carlos/fence-writer/credentials.json` exists on leo (`root`, `600`, as the command prints). |
| 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. |
| +## 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`. |
| + |
| +Scope: the independent claim writer (Plan 15) is deferred for this trial. This step protects the trial's fence credentials in transit. It does not provide Plan 15's writer boundary. |
| + |
| +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. |
| + |
| +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. |
| + |
| +Residual exposure: between 7a.2's write and its delete, leo's role can read the parameter, so any process on leo with IMDS access could too. Keep that stretch to minutes and attended. |
| + |
| +### 7a.1 The dedicated KMS key and leo's read grant |
| + |
| +Paul's go: separate gate, for creating the key, its policy and leo's temporary read grant. |
| + |
| +Dev's infrastructure is OpenTofu in `platform-infrastructure/dev`, so the key is declared there. Add this file on a reviewed branch (the operator ARN is the principal behind profile `kass` today, `arn:aws:iam::640012953448:user/remote-agent`; confirm it at approval, and it must also be the principal that runs `tofu apply`, or the key policy lockout check refuses the key): |
| + |
| +```hcl |
| +# platform-infrastructure/dev/cohort1-fence-kms.tf |
| +# Dev cohort-1 mechanics trial only. Remove this file after step 7a.2. |
| +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"] |
| +} |
| + |
| +resource "aws_kms_key" "cohort1_fence" { |
| + description = "Dev cohort-1 trial: leo's fence-writer credentials in transit" |
| + deletion_window_in_days = 7 |
| + policy = jsonencode({ |
| + Version = "2012-10-17" |
| + Statement = [ |
| + { |
| + Sid = "OperatorAdministersAndEncrypts" |
| + Effect = "Allow" |
| + Principal = { AWS = local.cohort1_operator_arns } |
| + Action = [ |
| + "kms:DescribeKey", "kms:GetKeyPolicy", "kms:PutKeyPolicy", |
| + "kms:ListResourceTags", "kms:TagResource", "kms:UntagResource", |
| + "kms:EnableKey", "kms:DisableKey", "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion", |
| + "kms:CreateAlias", "kms:UpdateAlias", "kms:DeleteAlias", "kms:ListGrants", "kms:RevokeGrant", |
| + "kms:Encrypt", "kms:GenerateDataKey", |
| + ] |
| + Resource = "*" |
| + }, |
| + { |
| + Sid = "OnlyLeoDecryptsTheTrialParameter" |
| + Effect = "Allow" |
| + Principal = { AWS = local.cohort1_leo_role_arn } |
| + Action = "kms:Decrypt" |
| + Resource = "*" |
| + Condition = { StringEquals = { "kms:EncryptionContext:PARAMETER_ARN" = local.cohort1_fence_param } } |
| + }, |
| + { |
| + Sid = "NobodyElseDecrypts" |
| + Effect = "Deny" |
| + Principal = "*" |
| + Action = "kms:Decrypt" |
| + Resource = "*" |
| + Condition = { ArnNotEquals = { "aws:PrincipalArn" = local.cohort1_leo_role_arn } } |
| + }, |
| + ] |
| + }) |
| +} |
| + |
| +resource "aws_kms_alias" "cohort1_fence" { |
| + name = "alias/cohort1-fence-leo" |
| + target_key_id = aws_kms_key.cohort1_fence.key_id |
| +} |
| + |
| +resource "aws_iam_role_policy" "cohort1_fence_read" { |
| + name = "cohort1-fence-read" |
| + role = "carlos-dev-host-b" |
| + policy = jsonencode({ |
| + Version = "2012-10-17" |
| + Statement = [{ Effect = "Allow", Action = "ssm:GetParameter", Resource = local.cohort1_fence_param }] |
| + }) |
| +} |
| +``` |
| + |
| +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. |
| + |
| +```bash |
| +cd platform-infrastructure/dev |
| +tofu plan -out=cohort1-fence.plan |
| +tofu apply cohort1-fence.plan |
| +``` |
| + |
| +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. |
| + |
| +Checks after apply, read-only, from the operator host: |
| + |
| +```bash |
| +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 \ |
| + --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. |
| + |
| +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. |
| + |
| +### 7a.2 Transfer, install, delete, revoke |
| + |
| +Paul's go: separate, once 7a.1 passes. Do the five parts back to back, in one attended sitting. |
| + |
| +**1. The trusted host writes the parameter from the file.** `fence-credentials.json` is the root-owned `0600` file from step 3's secret workflow. `file://` makes the CLI read the value from the file, so it never appears on the command line: |
| + |
| +```bash |
| +cd /root/claim-trial |
| +aws --profile kass --region eu-west-1 ssm put-parameter --name /carlos-staging/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 |
| +box_put "$LEO" fence-role-arn /root/claim-trial/fence-role-arn |
| +``` |
| + |
| +Expected: version `1`, an 8-digit fingerprint to compare in part 2, and `staged` for the role ARN (an ARN, not a secret). |
| + |
| +**2. On leo, read it without echoing it, straight into a protected file, and install it.** Main's `carlos system claims fence-credentials` has no stdin option: `--source` must be a root-owned regular file with mode `0600` or `0400`. So the value goes from `aws ssm get-parameter` straight into such a file, never to the terminal, then `fence-credentials` installs it. The AWS CLI on leo (v2, `/usr/bin/aws`) uses leo's instance role; do not set a profile. In the attended root shell on leo: |
| + |
| +```bash |
| +set -euo pipefail |
| +umask 077 |
| +cd /root/claim-trial |
| +test ! -e fence-credentials.json |
| +aws --region eu-west-1 ssm get-parameter --name /carlos-staging/fence/carlos-dev-ie-b --with-decryption \ |
| + --query Parameter.Value --output text > fence-credentials.json |
| +stat -c '%U %a %h %s' fence-credentials.json |
| +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 |
| +FENCE_ROLE=$(cat fence-role-arn) |
| +/opt/carlos/carlos system claims fence-credentials --source /root/claim-trial/fence-credentials.json \ |
| + --bucket carlos-dev-640012953448 --aws-region eu-west-1 --role "$FENCE_ROLE" --dry-run |
| +/opt/carlos/carlos system claims fence-credentials --source /root/claim-trial/fence-credentials.json \ |
| + --bucket carlos-dev-640012953448 --aws-region eu-west-1 --role "$FENCE_ROLE" |
| +rm -- /root/claim-trial/fence-credentials.json |
| +stat -c '%U %a %h' /etc/carlos/fence-writer/credentials.json |
| +``` |
| + |
| +Expected: `root 600 1 <size>`; the same fingerprint as part 1; both `fence-credentials` runs print their JSON summary (`"target": "/etc/carlos/fence-writer/credentials.json"`, `"mode": "0600"`); the staging file is removed; the installed file is `root 600 1`. Nothing prints a credential value. If anything fails, remove the staging file anyway, then go on to parts 3 to 5 before investigating. |
| + |
| +Gap for platform-1e: because `fence-credentials` has no stdin source, the plaintext sits in a root-only staging file (`/root/claim-trial`, `0700`) for the seconds between the read and the install. A `--source -` that reads standard input would remove that file. |
| + |
| +**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 |
| +``` |
| + |
| +**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 |
| +``` |
| + |
| +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: |
| + |
| +```bash |
| +cd platform-infrastructure/dev |
| +tofu plan -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 \ |
| + --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: |
| + |
| +```bash |
| +aws --profile kass iam delete-role-policy --role-name carlos-dev-host-b --policy-name cohort1-fence-read |
| +aws --profile kass --region eu-west-1 kms schedule-key-deletion --key-id <key-id> --pending-window-in-days 7 |
| +``` |
| + |
| +Afterwards, record in the trial notes that the CloudTrail events for `PutParameter`, `GetParameter` and `kms:Decrypt` carry no credential value, and that the only `kms:Decrypt` caller was `carlos-dev-host-b`. |
| + |
| +On failure: hold, but always finish parts 3 to 5 first, so no readable copy of the credentials is left in SSM and no decrypt grant stays open. |
| + |
| ## Step 8. Leo's attended offline window |
| What: Plan 9 milestone 4 on leo, with the files seed inside the same window. The commands are quoted from platform-1e's run 11 report ("Leo's attended offline window"); the quoted text in each `[...]` block is the files seed copy Paul approved on 2026-10-15, verbatim. This is an attended procedure: there is no automatic transaction and no resume after a reboot. |
| @@ -606,7 +780,7 @@ What: Plan 9 milestone 4 on leo, with the files seed inside the same window. The |
| Paul's go: for the window, with a written deadline. Step 9 needs its own go. |
| Before the window: |
| -- blockers 1 to 3 closed and steps 4 to 7 done; |
| +- blockers 1 to 3 closed and steps 4 to 7a done; |
| - a confirmed drained and replicated stop is possible for `claimtrial`, and nothing else is on leo; |
| - an external traffic barrier that survives a reboot is in place; |
| - a second root session is open to enforce the deadline; |
| @@ -1137,8 +1311,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. |
| +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). |
| 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. `carlos system claims fence-credentials` reads only a `--source` file. Should it also read standard input (`--source -`), so step 7a.2 needs no plaintext staging file on leo? |