| index 72c714762..32a601414 100644 |
| --- a/docs/superpowers/specs/2026-10-24-edge-request-protection-design.md |
| +++ b/docs/superpowers/specs/2026-10-24-edge-request-protection-design.md |
| @@ -486,15 +486,29 @@ backtested. |
| more base64 or hex characters) are masked, because reset links and signed |
| URLs carry their secret in exactly that shape. |
| - What can't be classified is masked, not kept. Calibration loses a little |
| - evidence; nobody's secret lands in a log. |
| + evidence; nobody's secret lands in a log. In practice (final review, |
| + 2026-10-25): any run of 24 or more such characters is masked whatever it |
| + holds, a standard-base64 token split into path segments at `/` is judged |
| + joined, and a query component with no `=` is left out of the names. |
| + - Control characters in recorded request text (path, query names, |
| + fragment) are stored escaped as `\xNN`, so a record cannot forge a log |
| + line or drive the terminal it is printed on. The method is stored as one |
| + of `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `CONNECT`, |
| + `TRACE` or `OTHER`. |
| - **Everything else** is counted in memory per app, per minute (status class, |
| method, probe-catalogue hits, wake caused or not) and flushed in batches. No |
| storage write per request, ever, and a flood must not turn records into its |
| own load: under pressure records are sampled, and the counts say how many |
| - were dropped. |
| + were dropped. Per app and minute at most 60 not-found, 300 block and 300 |
| + watch records are written; blocks and watches are capped separately, so |
| + watches cannot use up the blocks' allowance. Minute records carry the |
| + status classes, per-method counts (`m_get`, `m_head`, `m_post`, `m_put`, |
| + `m_patch`, `m_delete`, `m_options`, `m_other`), wakes, blocks, watches and |
| + the dropped counts. Every record carries the route's environment (`env`) |
| + when it has one. |
| - **Shipping** uses the existing per-app log pipeline (`internal/applog`, |
| `StreamEdge`) and its budgets, so they are readable with `carlos logs` and in |
| - the console. Retention is the logs family's 7 days (`logshipMaxAge`, `internal/host/logship.go`), changed at plan time from 14 because retention is per family and the calibration report can read two 7-day windows. Edge records have their own rate budget in the sink so they never crowd out the app's own lines. |
| + the console. Retention is the logs family's 7 days (`logshipMaxAge`, `internal/host/logship.go`), changed at plan time from 14 because retention is per family and the calibration report can read two 7-day windows. Edge records have their own rate budget in the sink so they never crowd out the app's own lines, and in the sink's buffer they only ever evict each other, never an app or platform line. Minute records bypass the edge rate budget, and the buffer evicts request records first, then blocks, then minute records. |
| - **Reference codes** are short, unguessable, and resolve (for the app's members |
| only) to the record: which rule fired, on what, and the one-line exception |
| that would allow it. |
| @@ -509,7 +523,10 @@ backtested. |
| `edgeproof.LoadKeys`) under the label `carlos-edge-ref`. A box without the |
| key (no deployment store, or a fleet box the store door refuses it to) |
| writes unsealed codes, which the console decodes and marks unverified. The |
| - box is carried as a 2-byte hash. |
| + box is carried as a 2-byte hash. A code is 25 bytes with an 8-byte random |
| + nonce, written as 40 characters of Crockford base32. The console answers |
| + from the seal even when the logs cannot be read, and says whether the |
| + record was found, is absent, or was unavailable. |
| - **Calibration needs more:** during the watch window (§8), probe-catalogue and |
| attack-tier matches also record the upstream response status, so learned |
| exemptions (§5.3) and promotion decisions have the evidence. |