carlos / platform Public

tenant email spec: DMARC reports go to cribado.report via CARLOS_DMARC_RUA

Paul's ruling 2026-08-19, superseding the earlier "no rua in v1". The
address is configuration, never a constant, so a deployment that sets
nothing publishes no rua and degrades to a missing report rather than a
broken record.

Records RFC 7489 §7.1 external-destination verification as a precondition:
cribado.report must publish *._report._dmarc.cribado.report before rua is
switched on, or conforming reporters discard every report while the DNS
record looks correct.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell 1ceedc6f4011923cf3ee2517e210e4e52a2f76a6 parent 3e48ca9
1 file changed, +36 −17
  • docs/superpowers/specs/2026-08-19-tenant-email-design.md +36 −17
diff --git a/docs/superpowers/specs/2026-08-19-tenant-email-design.md b/docs/superpowers/specs/2026-08-19-tenant-email-design.md
index 268134014..8e214ff46 100644
--- a/docs/superpowers/specs/2026-08-19-tenant-email-design.md
+++ b/docs/superpowers/specs/2026-08-19-tenant-email-design.md
@@ -296,7 +296,7 @@ action, which is what makes `carlos email enable` a one-command story:
| `<token>._domainkey.<host>` ×3 | CNAME | `<token>.dkim.amazonses.com` |
| `mail-eu-west-1.<host>` | MX | `10 feedback-smtp.eu-west-1.amazonses.com` |
| `mail-eu-west-1.<host>` | TXT | `v=spf1 include:amazonses.com ~all` |
-| `_dmarc.<host>` | TXT | `v=DMARC1; p=none; adkim=s; aspf=s` |
+| `_dmarc.<host>` | TXT | `v=DMARC1; p=none; adkim=s; aspf=s; rua=mailto:$CARLOS_DMARC_RUA` |
**Custom domain** — CARLOS creates the identity and *emits* the same record
set; the customer publishes it; CARLOS polls until green.
@@ -327,17 +327,37 @@ to change later: for a custom domain it lives in the **customer's** zone,
so getting it wrong means asking every customer to republish DNS. It is
region-qualified on day one for that reason alone.
-**`p=none` and no `rua`.** `p=none` because a tenant whose first send is
-rejected by its own DMARC record learns nothing useful. No `rua` because
-aggregate reports need a mailbox to arrive at, and inbound mail is out of
-scope (§1) — publishing a reporting address CARLOS cannot receive at would
-be a lie in DNS, and a lie every receiver on earth would act on. The
-consequence is real and worth naming: **without `rua` there are no
-aggregate reports, so nothing tells us whether tightening the policy is
-safe.** Arranging a reporting destination is therefore a precondition of
-the tightening follow-up, not an afterthought to it (§13). A custom
-domain's policy is the customer's to set; CARLOS proposes and does not
-impose.
+**`p=none` to start, with `rua` pointing at an external processor.**
+`p=none` because a tenant whose first send is rejected by its own DMARC
+record learns nothing useful; aggregate reports are what eventually justify
+tightening it (§13).
+
+Reports go to **cribado.report** (Paul, 2026-08-19). The address is
+**configuration, never a constant**: `CARLOS_DMARC_RUA` holds the mailto
+target, so the reporting destination can change without a release and a
+deployment that sets nothing simply publishes no `rua` — the degradation is
+a missing report, not a broken record.
+
+**External-destination verification is a precondition, and it is the part
+that is easy to miss.** RFC 7489 §7.1: because `cribado.report` is a
+different organizational domain from the domain being reported on, it must
+itself publish
+
+```
+<reported-domain>._report._dmarc.cribado.report TXT "v=DMARC1"
+```
+
+or conforming reporters discard the report rather than send it. That record
+is keyed by **each reported domain**, so with one identity per app it does
+not scale as individual records — `cribado.report` MUST carry the wildcard
+form `*._report._dmarc.cribado.report`. **Confirm the wildcard is published
+before `rua` is switched on**; until it is, publishing `rua` produces a
+DNS record that looks correct and silently yields no reports at all, which
+is worse than publishing none. The same wildcard covers custom domains, so
+this is confirmed once and never again.
+
+A custom domain's policy remains the customer's to set; CARLOS proposes
+and does not impose.
### The seam
@@ -498,11 +518,10 @@ before.
- **Confirm the SES region for the estate** before writing the AWS
implementation. `eu-west-1` is assumed throughout, matching `objstore`.
- **Verify SES tenant attribution over SMTP** against live AWS docs (§3).
-- **A DMARC reporting destination, then a tighter policy.** v1 publishes
- no `rua` (§7), so there is no evidence on which to tighten `p=none`.
- Arranging somewhere aggregate reports can land is the first half of this
- follow-up; moving the platform domain to `quarantine` is the second, and
- it must not happen first.
+- **Tighten the platform domain's DMARC policy past `p=none`** once
+ cribado.report's aggregate reports show it is safe. Blocked on the
+ external-destination wildcard in §7 actually being in place — without it
+ there are no reports, and tightening on no evidence is guessing.
- **Tito's estate.** Whether the titogo account gets its own SES estate or
reaches carloku's (§2).
- **Per-instance credentials.** §1's contract is worded per-credential so