carlos / platform Public

docs: cohort-1 step 4 done: orphan row, corrected flow, cert step

The pool change leaves leo's already-minted wakecheck row as an orphan
(the removal phase skips out-of-scope rows); Paul approved a one-off
carlos remove on leo. instances create is signed-in only, so enable in
store mode and create through the dev console with --channel edge. A
host homed only on a non-issuer box needs one TLS hello to the issuer
before --resolve writes work. claimtrial serves on leo only: three 200s
at hits 3, 4 and 5 (two probe rows from the first wake).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev de60f544f16f7cc2c46a077ff948a05554755ddf parent b2719ad
1 file changed, +38 −7
  • docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md +38 −7

CI cancelled — superseded by a newer push — 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	323.845s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	2.859s
ok  	github.com/carlosframework/platform/cmd/moveprobe	93.302s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.723s
ok  	github.com/carlosframework/platform/internal/activator	251.098s
ok  	github.com/carlosframework/platform/internal/agentsock	0.032s
ok  	github.com/carlosframework/platform/internal/alerting	0.003s
ok  	github.com/carlosframework/platform/internal/analytics	0.021s
ok  	github.com/carlosframework/platform/internal/appconfig	2.530s
ok  	github.com/carlosframework/platform/internal/apperr	0.091s
ok  	github.com/carlosframework/platform/internal/applog	0.173s
ok  	github.com/carlosframework/platform/internal/awsinv	0.003s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.008s
ok  	github.com/carlosframework/platform/internal/boxid	0.588s
?   	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.021s
ok  	github.com/carlosframework/platform/internal/certwarm	0.006s
ok  	github.com/carlosframework/platform/internal/cgroup	0.722s
ok  	github.com/carlosframework/platform/internal/changelog	0.339s
ok  	github.com/carlosframework/platform/internal/claimactivation	0.032s
ok  	github.com/carlosframework/platform/internal/clamd	0.058s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]

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 26f57e16b..131f10185 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
@@ -406,22 +406,53 @@ Why a pool: dev's two home boxes both mint every unregioned, pool-less record. A
systemctl restart carlos-edge.service && systemctl is-active carlos-edge.service'
```
-3. After the next provision pass (up to 5 minutes), run P4. Expected: wakecheck is gone from leo's registry. Sophie still serves it through DNS.
-4. Create the disposable app `claimtrial` in account `bab`, from wakecheck's own build (version `wc4`), so each request writes one SQLite row and replies with the host and the row count (`hits=`). Ship and promote it the same way wakecheck was, in store mode, then:
+3. Run P4 after the next provision pass (up to 5 minutes). **Corrected 2026-10-18:** the pool change does NOT take wakecheck off leo. It stops leo minting `""`-pool records, but the reconciler's removal phase skips every row outside the box's scope (`internal/provision/reconcile.go`: `if !rt.Provisioned || !cfg.inScope(rt.Region, rt.Pool, ...) { continue }`, and `poolServed([cohort1], "")` is false). So a row leo minted before the change stays as an orphan: never converged, never retired. Clear it with a one-off removal on leo only, as the carlos user (Paul's go needed; given 2026-10-18), then re-run P4:
```bash
- export AWS_PROFILE=kass CARLOS_DEPLOYMENT_BUCKET=carlos-dev-640012953448
- carlos instances enable --app claimtrial --account bab --domain dev.carlosframework.com --pool cohort1
- carlos instances create --app claimtrial --account bab --host claimtrial.dev.carlosframework.com
+ leo 'runuser -u carlos -- carlos remove --registry /data/registry.db wakecheck.dev.carlosframework.com; systemctl is-active carlos-edge.service'
```
-5. After the next provision pass, the route is on leo only. Write three rows, outside the sandbox:
+ This removes leo's registry row only. The shared record and sophie's route are untouched, and leo's stale local wakecheck database stays on disk. Expected: P4 shows no routes on leo; sophie still serves wakecheck through DNS.
+4. Create the disposable app `claimtrial` in account `bab`, from wakecheck's own build (version `wc4`), so each request writes one SQLite row and replies with the host and the row count (`hits=`). **Corrected 2026-10-18:** `instances create` refuses store mode (it is a console-authorized write, `requireAPISession` in `instances_cli.go`), and `instances enable --pool` is store-mode only. So enable in store mode, then create signed in with the store env unset. This machine's default console is flagship, so every signed-in command names the dev console. Store-mode enable defaults the channel to `stable`, so pass `--channel edge` to match the app's only channel. `--account bab` (the sqid) resolves without a local registry.
```bash
+ # wakecheck's shipped wc4 artifact; check it against releases/wc4/manifest.json
+ aws --profile kass s3 cp s3://carlos-dev-640012953448/apps/bab/wakecheck/releases/wc4/linux-arm64 "$TMPDIR/cohort1/linux-arm64"
+ sha256sum "$TMPDIR/cohort1/linux-arm64" # ee276c083a04b6de11fc81b3a219f587394efd9600b46d21185398e44a4abcbb
+
+ DEV=https://platform.dev.carlosframework.com
+ carlos apps create --app claimtrial --account bab --console $DEV
+ carlos ship --app claimtrial --account bab --console $DEV --version wc4 \
+ --label "dev claim trial tenant, wakecheck wc4 build" --no-prompt "$TMPDIR/cohort1/linux-arm64"
+ carlos promote --app claimtrial --account bab --console $DEV --no-prompt wc4 edge
+
+ AWS_PROFILE=kass CARLOS_DEPLOYMENT_BUCKET=carlos-dev-640012953448 \
+ carlos instances enable --app claimtrial --account bab --domain dev.carlosframework.com --pool cohort1 --channel edge
+
+ # signed in: CARLOS_DEPLOYMENT_BUCKET must be unset here
+ carlos instances create --app claimtrial --account bab --host claimtrial.dev.carlosframework.com --console $DEV --no-prompt
+ ```
+
+5. After the next provision pass, the route is on leo only (read-only check on both boxes). **Corrected 2026-10-18:** before writing, get the host a certificate. Only the box holding the housekeeper lease (`control/housekeeper-lease/lock.json`, sophie on 2026-10-18) orders certificates, and only when a TLS hello for the name reaches it (#277). Every other edge serves only what it mirrors from `control/certs/`, so a `--resolve` to leo alone answers `tlsv1 alert internal error` (leo logs `acme: not available here`). Send one handshake-only hello to the issuer, wait for `control/certs/claimtrial.dev.carlosframework.com` and leo's 30-second mirror, then write three rows, outside the sandbox:
+
+ ```bash
+ openssl s_client -connect 99.80.78.4:443 -servername claimtrial.dev.carlosframework.com </dev/null
+ aws --profile kass s3 ls s3://carlos-dev-640012953448/control/certs/claimtrial.dev.carlosframework.com
for i in 1 2 3; do curl -s --resolve claimtrial.dev.carlosframework.com:443:18.200.150.210 https://claimtrial.dev.carlosframework.com/; echo; done
```
- Expected: three 200 answers with `host=ip-172-31-19-180` and `hits=1`, `2`, `3`. Sophie's registry has no `claimtrial` route.
+ Expected: three 200 answers with `host=ip-172-31-19-180` and `hits=` rising by one each time. The first values are not 1, 2 and 3: the app's first wake writes rows of its own (probe requests). Sophie's registry has no `claimtrial` route.
+
+**2026-10-18, done.** platform-d8 was told (H2) and the pool set as in 4.2 (backup `host.env.bak-pool-20261018`; the edge restarted at 09:48:57Z and logged `instances loop armed (5min + jitter), region "" pools [cohort1]`).
+- P4, 09:51Z to 10:00Z, polled every 30 s: wakecheck stayed on leo, `provisioned=1, pool='', region='', channel=edge, hibernate=1`, no instance unit. Leo's edges file said `serves_shared_pool: false` from 09:49:59Z. That is the orphan row described in 4.3.
+- Paul approved clearing it. The controller ran `carlos remove --registry /data/registry.db wakecheck.dev.carlosframework.com` on leo only, as the carlos user, which output `removed`; the edge stayed active. Sophie still served wakecheck (`build=wc4 host=ip-172-31-14-16`, hits 1037 then 1038).
+- P4 re-run 10:02Z: leo has 0 routes, no instance units, one carlos-user process, edge active.
+- claimtrial, built with the commands in 4.4 (CLI v0.28.0): the wc4 artifact's sha256 `ee276c08…4abcbb` (9437346 bytes) matched wakecheck's manifest and claimtrial's own `releases/wc4/manifest.json` (built by `console-api 3b69c2bd`, program `wakecheck`). `apps create` gave the default single `edge` channel; `ship` gave `shipped claimtrial wc4`; `promote` gave `promoted claimtrial wc4 -> edge` (10:03:13Z). `instances.json` reads `{"schema":1,"app":"claimtrial","domain":"dev.carlosframework.com","pool":"cohort1","channel":"edge"}`. `instances create` gave `created instance claimtrial.dev.carlosframework.com (channel edge, Ireland · Dublin)`, and the record carries `pool: cohort1` and no region.
+- Leo minted the route at 10:03:33Z: `('claimtrial.dev.carlosframework.com', 'claimtrial', provisioned=1, pool='cohort1', region='', channel='edge', hibernate=1)`. The first stage of wc4 logged `chmod /opt/carlos/versions/wc4: read-only file system` and asked for a root pass, which ran by itself: `repointed -> wc4 (edge; hibernating — runs it on next wake)`.
+- Sophie, 10:03:48Z: 0 claimtrial rows, no `CARLOS_POOLS` in host.env.
+- The first three `--resolve` curls (10:03:56Z) answered HTTP 000 with `tlsv1 alert internal error`. Leo logged `acme: not available here`, and `control/certs/` had nothing for claimtrial. Sophie held the housekeeper lease (`owner ip-172-31-14-16`).
+- The controller sent one handshake-only `openssl s_client` to sophie with SNI claimtrial. Sophie got a Let's Encrypt YE2 certificate and published it to `control/certs/claimtrial.dev.carlosframework.com` at 10:05:53Z.
+- After leo's mirror picked it up, the three `--resolve` curls to leo each answered 200 with `host=ip-172-31-19-180`, at `hits=3`, `4` and `5`. Two rows were already written when the app first woke, most likely by wake or build probe requests to `/` or `/healthz`. The count rose by one on every call and leo served all three: PASS.
`claimtrial` is the only host the files seed and acceptance touch. It holds nothing anyone needs.