carlos / platform Public

region HA spec 2, rev 13: the provider boundary (core vs AWS adapters, import test, no-AWS path), at Paul's request

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 24dd1a12078c70d9e27c9302bf5de5cc652d2eb9 parent 68b97cd
1 file changed, +34 −1
  • docs/superpowers/specs/2026-10-15-region-ha-two-box-regions-design.md +34 −1
diff --git a/docs/superpowers/specs/2026-10-15-region-ha-two-box-regions-design.md b/docs/superpowers/specs/2026-10-15-region-ha-two-box-regions-design.md
index 927748fad..7d684ee4b 100644
--- a/docs/superpowers/specs/2026-10-15-region-ha-two-box-regions-design.md
+++ b/docs/superpowers/specs/2026-10-15-region-ha-two-box-regions-design.md
@@ -1,6 +1,8 @@
# Region HA, spec 2: two-box regions
-Design, 2026-10-15, **rev 12**. DRAFT. Nothing here is built.
+Design, 2026-10-16, **rev 13**. Approved by Paul on 2026-10-16, subject to this revision. Nothing here is built.
+
+**Rev 13** adds §25, the provider boundary, at Paul's request. Spec 2 must not make the platform depend on AWS: a deployment stays "one object-store bucket + N hosts running the `carlos` binary + DNS" (README), and its AWS-specific parts are adapters.
**Rev 12** answers Astra's round 11 (`CODEX-FINDINGS-REGION-HA-SPEC2-11.md`, **A11-1**, P2, accepted). Rollback's exclusion is set on the region's group, and every box the group launches inherits it at enrolment, so a replacement can't reopen placement. §24.
@@ -540,3 +542,34 @@ All three were answered in Astra's round 1 and folded in:
| Finding | Disposition |
|---|---|
| A11-1 P2: rollback's exclusion doesn't survive box replacement | Accepted: a role on the region's group entry in the roster, inherited at enrolment in the same write, and applied to current members when changed (§7, §9) |
+
+## 25. The provider boundary (rev 13)
+
+Spec 2 is AWS-first because carloku.com runs on AWS. The platform isn't: a self-hosted deployment is one object-store bucket (S3-compatible, MinIO included), N hosts running `carlos`, and DNS. So every part of spec 2 is either **provider-neutral core** or an **AWS adapter** behind a small interface. The core never imports the AWS SDK.
+
+**Core.** These work on any hosts, behind any load balancer or none:
+
+| Piece | Without AWS |
+|---|---|
+| Placement policy, the `legacy`/`platform` box roles, the group role, pending creates (§7) | Unchanged. None of it calls a cloud API |
+| The health answer `/.carlos/lb-health` (§6) | Plain HTTP. Any load balancer (HAProxy, nginx, a hosting provider's) or a monitor can use it |
+| Forwarding by private address in a region, own public address between regions (§6) | Addresses come from an **address source**. Without AWS: `CARLOS_PRIVATE_IP` and `CARLOS_PUBLIC_IP` in `host.env`, or the primary interface's address when unset |
+| Joining and hosting modes (§5) | Generic. Enrolment uses the **membership confirmer** interface. Without AWS, there is none, and enrolment is operator-approved through the existing `hostactors.Enroll` path |
+| `host.env` (§5) | A file, as today. The SSM reader is one way of writing it |
+| The channel-release install and the guard (§5) | Unchanged. They read the bucket and the release key |
+| Single-box assumptions (§8) | Fixed in core, so they apply to every multi-box deployment |
+
+**AWS adapters.** They are loaded only when the deployment declares AWS, and they load the SDK through `internal/rootaws`, as every AWS client does since #267:
+- the **membership confirmer**: Auto Scaling `DescribeAutoScalingInstances` (H16, H17);
+- the **SSM and KMS parameter reader** that writes `host.env`;
+- the **IMDS address source**;
+- **termination evidence** (S1, spec 3);
+- the **`region-host` tofu module's group mode**: launch template, Auto Scaling group, NLB, alarms (§4).
+
+**What a deployment without AWS doesn't get.**
+- A sick box isn't replaced or enrolled automatically. An operator replaces it and approves its enrolment.
+- No automatic failover (spec 3). It triggers on a box's verified termination (H1), which has no equivalent without a provider's record. A provider adapter could supply one later, behind the same interface.
+
+**Enforcement.**
+- An import test: the core packages spec 2 touches (`internal/boxchoice`, `internal/edge`, `internal/host` outside its adapter files, `internal/console`, `internal/hostactors`) import no `github.com/aws/...` package. Adapter files live in clearly named files or packages, for example `internal/host/awsaddr.go` and `internal/hostactors/awsmembership`.
+- A test runs the core path with no AWS configuration at all: two boxes with `CARLOS_PRIVATE_IP`/`CARLOS_PUBLIC_IP`, operator-approved enrolment, placement, forwarding and the health answer.