db-region-forward: record Paul's rulings (2026-10-22)
Ruling 1: ship through the deployment bucket as designed, with the true storage statement; a per-region db-read bucket is a follow-up (plan 5) after the feature works. Rulings 2 to 5 confirmed as Paul's. Section 14 lists plan 4 and plan 5 as follow-ups, not written.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+54
−36
docs/superpowers/specs/2026-10-22-db-region-forward-design.md+54 −36
diff --git a/docs/superpowers/specs/2026-10-22-db-region-forward-design.md b/docs/superpowers/specs/2026-10-22-db-region-forward-design.md| index 22cf1ffef..b471c9f76 100644 |
| --- a/docs/superpowers/specs/2026-10-22-db-region-forward-design.md |
| +++ b/docs/superpowers/specs/2026-10-22-db-region-forward-design.md |
| @@ -6,7 +6,9 @@ four direct fixes and no new mechanism: flushed progress lines and the |
| backhaul header bound (codex 19), the result held until its chunks are out |
| (codex 20), restart reconciliation by refusal and a 5 s worker exit (codex |
| 8), and the retention size wording (codex 7). Design only, nothing built. Written for Paul, |
| -who approved the direction (§2). Review brief: |
| +who approved the direction (§2). Astra accepted rev 5 in round 5, and Paul |
| +approved building it on 2026-10-22, with the rulings in §16 (ruling 1: |
| +ship through the deployment bucket, per-region bucket as plan 5). Review brief: |
| `CODEX-REVIEW-BRIEF-DB-REGION-FORWARD.md`. Rev 1 was `b35b3ab1`, rev 2 |
| `2cf4a88f`, rev 3 `944a61fd`, rev 4 `c07629e4`. Rev 4 answered Astra's round 3 |
| (`CODEX-FINDINGS-DB-REGION-FORWARD-R3.md`) by making a forwarded read one |
| @@ -15,9 +17,8 @@ console writes after the request ends, and the store's lifecycle rule as |
| the only deletion. Five invariants (§5.3) replace rev 3's seven, and every |
| finding is mapped to the invariant that closes it or to the synchronous |
| design that removes it. Each change is marked **(codex N)** where it lands, |
| -and §18 lists what was declined. The spec carries the coordinator's |
| -provisional answers to the open questions, which await Paul (§16); ruling |
| -1's bound does not hold and is flagged. |
| +and §18 lists what was declined. The coordinator's provisional answers to |
| +the open questions are now Paul's rulings (§16, 2026-10-22). |
| **Companion specs:** `2026-09-02-carlos-db-design.md` (the db door, cited |
| below as "db spec"), `2026-10-08-carlos-run-design.md` (the run transport, |
| @@ -63,7 +64,9 @@ working around them: |
| rule expires them (§5.7). |
| In phase 1 that is only a |
| query's rows. Phase 2's pull would send the planned replica files, which |
| - are a pull's result, under provisional ruling 1 (§16), which awaits Paul. |
| + are a pull's result, under ruling 1 (§16, Paul, 2026-10-22). A per-region |
| + bucket that keeps this ciphertext in the data's region is a follow-up |
| + (plan 5, §14). |
| One earlier ruling holds unchanged: the run spec's "the console still never |
| connects to a box". The transport is the run's. |
| @@ -130,14 +133,14 @@ connects to a box". The transport is the run's. |
| | # | Decision | Section | |
| |---|---|---| |
| -| D1 | Phase 1 forwards `query` and `query --at`. `pull` and `pull --at` forward in phase 2, under provisional ruling 1. The claim-mode door and the seeds `--from` door stay refused. | §6 | |
| +| D1 | Phase 1 forwards `query` and `query --at`. `pull` and `pull --at` forward in phase 2, under ruling 1 (§16). The claim-mode door and the seeds `--from` door stay refused. | §6 | |
| | D2 | Any live box in the record's region that publishes `db-read-1` and is not draining may serve. The first to create the read's `picked` marker serves it. The instance's lease holder gets no preference. | §7 | |
| | D3 | Transport is the run's objects in the deployment bucket and a serial bump, but the read is one synchronous HTTP request: the console waits on the box's objects inside it and streams them to the CLI. The request is a root-signed envelope. No record write. Every object is a create-only write under `db-reads/`, never replaced or deleted by the protocol; the store's lifecycle rule is the only deletion (§5.2, §5.3). | §5 | |
| | D4 | Results are sealed to an ephemeral key the CLI makes for each read. The console and the deployment bucket only ever hold the result as ciphertext. The console does see the SQL, as the envelope's signer. | §5.6 | |
| | D5 | The box splits the work. The agent (`carlos`) verifies, resolves, plans and streams objects. Everything that decodes tenant bytes (LTX, SQLite, the member's SQL) runs in a new sandboxed unit, `carlos-dbread@.service`, as a `DynamicUser` with no network. | §8 | |
| | D6 | The worker gets the planned LTX files and nothing else. Its scratch is its own private `/var/tmp`, which systemd removes when the unit stops. | §8.3 | |
| | D7 | Rollout: boxes publish `db-read-1`; the console forwards only with `CARLOS_DB_FORWARD=1` and a capable region. Dormant by default. | §11 | |
| -| D8 | Three plans here. A fourth, after phase 3, moves in-region reads to the box path (provisional ruling 5). | §14 | |
| +| D8 | Three plans here. Two follow-ups are listed, not written: plan 4 moves in-region reads to the box path after phase 3 (ruling 5); plan 5 moves this traffic to a per-region bucket (ruling 1). | §14 | |
| ## 5. Transport and authentication |
| @@ -542,7 +545,7 @@ and reports it. |
| |---|---|---| |
| | `query` | 1 | This is the regression the customer hit. The result is rows, usually small, and only rows leave the region. | |
| | `query --at` | 1 | Same path with a target time. The box lists the replica the way the console's door does today (`listReplicaLevel`, `LastModified` as `CreatedAt`), so the copy stops at the same transaction an in-region read would. Plan 1 tests that parity. | |
| -| `pull` | 2 | The result is the whole database. It rests on provisional ruling 1, whose bound does not hold (§16: a whole copy's ciphertext rests in the deployment bucket's region until lifecycle expiry, typically days), and a measured size cap. In phase 2 the box ships the planned LTX files, sealed, in the same single request, and the CLI restores and checks them locally with today's code. The box never restores for a pull. | |
| +| `pull` | 2 | The result is the whole database. It rests on ruling 1 (§16, Paul, 2026-10-22: a whole copy's ciphertext rests in the deployment bucket's region until lifecycle expiry, typically days, until plan 5's per-region bucket), and a measured size cap. In phase 2 the box ships the planned LTX files, sealed, in the same single request, and the CLI restores and checks them locally with today's code. The box never restores for a pull. | |
| | `pull --at` | 2 | As `pull`, with the target time. | |
| | Claim-mode door (`apidbclaim.go`) | not planned | Claims are not active anywhere. The claim door reads exact object versions through `hostops`'s versioned boundary and signed file handles, and lists no times. A box-side version of that belongs to claim activation (#250), which must add times anyway. A host-claim console keeps today's residency refusal, as it refuses `carlos run`. | |
| | Seeds `--from` (`resolveDBSource`) | not planned | It shares the source checks but restores into a new instance, a different flow. Cross-region seeding is its own design. | |
| @@ -835,7 +838,7 @@ A console with `CARLOS_DB_FORWARD` unset answers byte-identically to main. |
| One `db-read` activity row at admission, in the run's manner, with the |
| detail DBF-21 or DBF-22. It records the SQL's length in bytes and the first |
| -16 hex digits of its SHA-256, not its text (provisional ruling 3). A member |
| +16 hex digits of its SHA-256, not its text (ruling 3). A member |
| who kept the statement can match it to the row; the row itself holds none |
| of the data a query's literals can carry. That differs from `carlos run`, |
| which records argv, and keeps the in-region rule that SQL never reaches the |
| @@ -846,7 +849,7 @@ the timing §5.7 states: typically two to four days, with no hard bound. |
| ## 10. Errors and copy |
| Every new user-facing sentence is below. Error and status strings follow |
| -Paul's voice rule (provisional ruling 4): lower case at the start and after |
| +Paul's voice rule (ruling 4): lower case at the start and after |
| a full stop, no em dashes. Docs prose keeps the CLI page's own sentence |
| case. None of this is approved yet; the copy-review gate runs before any of |
| it is written into code. |
| @@ -1065,17 +1068,28 @@ named. |
| `docs/site/cli.md`, and the `deployment-buckets` lifecycle rule with the |
| approval plan for applying it. Gates: T1, T6, T7, T8, T10, T11, T14, |
| T15, T16, T17, T18, copy review, census overlay. Roll per §11.3. |
| -3. **`pull` and `pull --at`.** On provisional ruling 1, re-confirmed with |
| - the storage statement in §16. Sealed LTX files in the same single |
| +3. **`pull` and `pull --at`.** On ruling 1 (§16), which Paul confirmed |
| + with the storage statement on 2026-10-22. Sealed LTX files in the same single |
| request, local restore and `CheckDBContext` in the CLI, and the pull copy |
| rows. A 2 GiB pull in one request is the case most likely to need a |
| longer request bound; plan 3 measures it before choosing. |
| -After phase 3, in-region reads move to the box path too (provisional ruling |
| -5). That is a fourth plan, written once phase 3 has run on the flagship. It |
| -is not designed here beyond one constraint this spec already keeps: the |
| -box path is region-agnostic, so the console can send an in-region read |
| -through it unchanged. |
| +Two follow-ups are listed here and not written: |
| + |
| +4. **Plan 4 (follow-up): in-region reads onto the box path** (ruling 5). |
| + Written once phase 3 has run on the flagship. It is not designed here |
| + beyond one constraint this spec already keeps: the box path is |
| + region-agnostic, so the console can send an in-region read through it |
| + unchanged. |
| +5. **Plan 5 (follow-up): per-region db-read bucket** (ruling 1). After the |
| + feature works, `db-reads/` traffic moves from the shared deployment |
| + bucket to a bucket in each data region, so that ciphertext and request |
| + SQL never rest outside the data's region. It needs its own design for |
| + the console's write grant into each region's bucket, the boxes' grant on |
| + their own, the queue and serial signal, and the lifecycle rule per |
| + bucket. Plans 1 to 3 keep the protocol's keys relative to one store |
| + (`db-reads/...`) so that plan 5 changes where the store is, not what is |
| + in it. |
| ## 15. Review focus: the five likeliest failure modes |
| @@ -1100,27 +1114,31 @@ through it unchanged. |
| (a proxy, a client, a keep-alive), wait lines that stall behind a slow |
| reader, or a 2 GiB pull that does not fit the request bound. |
| -## 16. Open questions: provisional rulings awaiting Paul |
| +## 16. Rulings |
| The coordinator answered rev 1's five questions provisionally on |
| -2026-10-22. The spec is written to these answers. Each stands until Paul |
| -confirms or changes it. |
| - |
| -1. **Pull through the deployment bucket.** Phase 2's pull may pass |
| - ciphertext through the deployment bucket (eu-west-1 on the flagship). |
| - **The bound in that ruling does not hold and must be re-confirmed before |
| - plan 3.** The ruling said "at most 64 MiB at a time for at most 1 hour". |
| - The deployment bucket is versioned and the protocol deletes nothing, so |
| - the true statement is: **a pull's whole ciphertext, up to its size cap, |
| - lives in the deployment bucket until lifecycle expiry, typically two to |
| - four days, with no guaranteed upper bound** (§5.7). A short or hard bound |
| - would need version-aware deletes, with `s3:DeleteObjectVersion` on the |
| - deployment bucket for the console's role: a new IAM grant and a deleter |
| - this design removed on purpose. Phase 1 is the same statement at a |
| - smaller size: a query's ciphertext is at most 64 MiB of rows. **Paul's |
| - call** in either case: if UK data residency is a legal requirement |
| - rather than a technical one, whether encrypted UK data resting for days |
| - in an Irish bucket is acceptable is for him. |
| +2026-10-22. Paul ruled on 2026-10-22, after Astra accepted rev 5: ruling 1 |
| +as below, and rulings 2 to 5 confirmed as written (he raised no objection). |
| +All five are now Paul's. |
| + |
| +1. **Pull through the deployment bucket (Paul, 2026-10-22).** Ship the |
| + feature through the deployment bucket as designed, for `query`, |
| + `query --at`, `pull` and `pull --at`. The true storage statement stands |
| + and is what Paul ruled on: **a read's whole ciphertext, up to its size |
| + cap, lives in the deployment bucket (eu-west-1 on the flagship) until |
| + lifecycle expiry, typically two to four days, with no guaranteed upper |
| + bound** (§5.7). For a query that is at most 64 MiB of rows; for a pull |
| + it is the planned replica files, up to the pull's cap. The SQL rests in |
| + the request object on the same timing (§9.3). The earlier provisional |
| + bound ("at most 64 MiB at a time for at most 1 hour") does not hold and |
| + is withdrawn. A short or hard bound would need version-aware deletes, |
| + with `s3:DeleteObjectVersion` on the deployment bucket for the console's |
| + role, which this design does not add. |
| + **Follow-up, after the feature works: a per-region bucket for this |
| + traffic** (plan 5, §14), so that ciphertext and request SQL never rest |
| + outside the data's region. Plans 1 to 3 do not build it and do not wait |
| + for it; they keep every `db-reads/` object key relative to one store so |
| + that plan 5 can move the store without changing the protocol. |
| 2. **Limits stand.** 2 GiB restored, 5 minutes, 64 MB of rows, two reads per |
| box. Real database sizes on the flagship are still unmeasured; plan 2's |
| dev check reports them. |