carlos / platform Public

console: fix comments this branch left describing the old email.json shape (final review, item 6)

Four comments this branch falsified:

- mailevents_test.go's TestTheMailLogGoesToTheAppSESSaysSentIt doc
  comment still said counts are "attributed by the domain, as they
  always were" — false since senders; it now says counts follow the
  caller-named sender and fall back to the domain only when SES names
  nobody CARLOS minted.
- apimail.go's handleAPIEmailPause comment still said SetPaused and
  ApplyGrant "write only email.json".
- appsettings.go's MailCard doc comment still named
  apps/<acct>/<app>/email.json as the record it views.
- ownership.go's purge-ordering comment still said release.PurgeApp
  "deletes email.json out from under it".

All four now name the sender record (email/senders/app.json) and, where
relevant, the email.json read fallback.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev fe6b96313198aa883531c28722583ac01490ef2c parent 10165c3
4 files changed, +17 −13
  • internal/console/apimail.go +2 −1
  • internal/console/appsettings.go +3 −2
  • internal/console/mailevents_test.go +5 −4
  • internal/console/ownership.go +7 −6

CI passed — run details

diff --git a/internal/console/apimail.go b/internal/console/apimail.go
index cfee73b3f..5a22ae60a 100644
--- a/internal/console/apimail.go
+++ b/internal/console/apimail.go
@@ -1481,7 +1481,8 @@ func (s *Server) handleAPIEmailPause(w http.ResponseWriter, r *http.Request) {
detail = append(detail, fmt.Sprintf("daily cap raised to %d", req.DailyCap))
}
s.audit(acct, app, addr, "email-pause", strings.Join(detail, "; ")+" (cli)")
- // No bumpSerial: SetPaused and ApplyGrant both write only email.json,
+ // No bumpSerial: SetPaused and ApplyGrant both write only the sender
+ // record (email/senders/app.json, with an email.json read fallback),
// never the app's own delivered config.
s.mailWalkReply(ctx, w, acct, app, rec)
}
diff --git a/internal/console/appsettings.go b/internal/console/appsettings.go
index 907f244da..702388fb1 100644
--- a/internal/console/appsettings.go
+++ b/internal/console/appsettings.go
@@ -189,8 +189,9 @@ type StoreCard struct {
CanDestroy bool
}
-// MailCard is the Settings tab's view of apps/<acct>/<app>/email.json (issue
-// #240, mail.Record): tenant email's Settings card, mirroring StoreCard verb
+// MailCard is the Settings tab's view of the app sender, apps/<acct>/<app>/
+// email/senders/app.json with an email.json read fallback (issue #240,
+// mail.Record): tenant email's Settings card, mirroring StoreCard verb
// for verb — same declare -> provision -> deliver -> rotate shape, same
// read-only presentation, same error discipline (mailCard's doc comment).
//
diff --git a/internal/console/mailevents_test.go b/internal/console/mailevents_test.go
index 7c64489b7..f82a8285c 100644
--- a/internal/console/mailevents_test.go
+++ b/internal/console/mailevents_test.go
@@ -118,10 +118,11 @@ func TestTheMailLogForgetsAfterThirtyDays(t *testing.T) {
}
}
-// Two apps can send from one domain. Their counts are attributed by the
-// domain, as they always were; the log is narrower wherever SES names the
-// sender, because a recipient's address is one app's business, not every
-// app's that happens to share a From domain.
+// Two apps can send from one domain. The log is narrower wherever SES names
+// the sender, because a recipient's address is one app's business, not
+// every app's that happens to share a From domain; the counts follow the
+// same caller-named sender now (design §8) and fall back to charging every
+// app on the domain only when SES names nobody CARLOS minted.
func TestTheMailLogGoesToTheAppSESSaysSentIt(t *testing.T) {
fx := newConsoleFixture(t)
ctx := context.Background()
diff --git a/internal/console/ownership.go b/internal/console/ownership.go
index b479bab17..d183623bb 100644
--- a/internal/console/ownership.go
+++ b/internal/console/ownership.go
@@ -445,12 +445,13 @@ func (s *Server) handlePurgeApp(w http.ResponseWriter, r *http.Request) {
// mail.Teardown is the ONLY place that releases this app's domain
// claims — the create-only global gate at
// email/domain-claims/<domain>.json (spec §4a) — and release.PurgeApp
- // below deletes email.json out from under it. Without this, a purged
- // app leaves a live IAM user with an active key, its SES identity, its
- // published DNS, and — worst — a domain claim that DomainRecord.Apps
- // can never again name this (now-deleted) app to release: the domain
- // is denied to every CARLOS account, including its rightful owner,
- // permanently.
+ // below deletes the sender record (email/senders/app.json, plus a
+ // legacy email.json if one is still there) out from under it. Without
+ // this, a purged app leaves a live IAM user with an active key, its
+ // SES identity, its published DNS, and — worst — a domain claim that
+ // DomainRecord.Apps can never again name this (now-deleted) app to
+ // release: the domain is denied to every CARLOS account, including
+ // its rightful owner, permanently.
//
// mail.Teardown is nil-record-safe (it sweeps this account's domain
// records and this app's ORPHANED claims even when the app never