carlos / platform Public

Design: two prerequisites before any tenant-attributed send

Found while building step 2. Domains provisioned before the association
work have none, and only domains add creates them, so the first send
carrying the tenant header would fail SES validation for every existing
sender. And the control-plane role needs the two association actions, or
teardown returns before it releases the global domain claim.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 5e8e319cc30ead616d3d179c14977b908e7f33e3 parent bf04ed9
1 file changed, +25 −0
  • docs/superpowers/specs/2026-09-15-tenant-email-senders-design.md +25 −0
diff --git a/docs/superpowers/specs/2026-09-15-tenant-email-senders-design.md b/docs/superpowers/specs/2026-09-15-tenant-email-senders-design.md
index 614acc3bc..5590d8a03 100644
--- a/docs/superpowers/specs/2026-09-15-tenant-email-senders-design.md
+++ b/docs/superpowers/specs/2026-09-15-tenant-email-senders-design.md
@@ -175,6 +175,31 @@ than silently "resumed" by CARLOS on the next day roll.
`ses:caller-identity: ["spike-tenant-user"]`, the bare IAM user name,
which is what §8's attribution reads.
+### 4.2 Two prerequisites before any tenant-attributed send
+
+Found while building step 2, and both bite at roll time rather than build
+time.
+
+1. **Existing domains have no tenant association, and nothing will ever
+ create one.** Step 2 associates an identity and the account's
+ configuration set inside `EnsureDomain`, which is reached only from
+ `carlos email domains add`. `VerifyDomain` does not associate. So every
+ domain already in the estate keeps a verified identity with no
+ association, and the first send that carries `X-SES-TENANT` fails SES's
+ validation. Before step 3 or step 5 rolls, either a backfill pass
+ associates every recorded identity and configuration set, or
+ `VerifyDomain` learns to associate too. This is the failure the standing
+ warning in `aws.go` exists to prevent, so it is not optional.
+2. **The control-plane role needs two new IAM actions**,
+ `ses:CreateTenantResourceAssociation` and
+ `ses:DeleteTenantResourceAssociation`. That policy does not live in this
+ repo. If the grant is missing, `AccessDenied` is neither `AlreadyExists`
+ nor `NotFound`, so it is not swallowed: `domains add` fails after the
+ record and the DNS are already written, and `Teardown` returns before
+ `DeleteIdentity` and before releasing the global domain claim, which
+ leaves that claim stranded until the permission lands. Confirm the grant
+ before rolling.
+
## 5. The sender
A **sender** owns a From address, a credential, a cap, a pause and a set