carlos / platform Public

mail: fix SenderOfCaller build and add its missing test (plan 2, task 6)

The prior commit for task 6 did not compile: sender.go called log.Printf
without importing "log", and gofmt flagged a doubled blank line before
the new doc comment. Neither problem showed up in the report, and the
brief's test, TestSenderOfCallerParsesBeforeItReads, was never written.

Add the missing import, run gofmt, and add the test verbatim from the
brief. Verified the order is load-bearing by swapping the resolver to a
get-only (index-first) shape: the test fails on exactly the delivered
and standalone names that have no index entry today, then passes again
once parse-first is restored.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 0e56440de155759d547bd570e5fa0ab936d63ec1 parent 419761a
2 files changed, +47 −1
  • internal/mail/sender.go +1 −1
  • internal/mail/sender_test.go +46 −0
diff --git a/internal/mail/sender.go b/internal/mail/sender.go
index e08ba79bc..6c99c6967 100644
--- a/internal/mail/sender.go
+++ b/internal/mail/sender.go
@@ -19,6 +19,7 @@ import (
"encoding/json"
"errors"
"fmt"
+ "log"
"os"
"regexp"
"strings"
@@ -374,7 +375,6 @@ func ResolveSenderNames(ctx context.Context, s release.Store, acct, app string,
return SenderNameSet{}, salted, fmt.Errorf("mail: could not find a free IAM name for sender %s/%s/%s after four attempts", acct, app, sn.ID)
}
-
// SenderOfCaller resolves the IAM identity SES stamps on an event
// (mail.tags["ses:caller-identity"]) to the sender that owns it.
//
diff --git a/internal/mail/sender_test.go b/internal/mail/sender_test.go
index 245b55464..f9c39ac1f 100644
--- a/internal/mail/sender_test.go
+++ b/internal/mail/sender_test.go
@@ -246,3 +246,49 @@ func TestResolveSenderNamesRetriesACollisionWithASalt(t *testing.T) {
t.Fatalf("the app sender's name changed: %q", appNames.IAMUser)
}
}
+
+// Parse first, index second. Every app enabled today short-circuits Enable
+// before MintKey, so it has no index entry until it next rotates: a
+// Get-only resolver would silently stop attributing their events the moment
+// it shipped.
+func TestSenderOfCallerParsesBeforeItReads(t *testing.T) {
+ ctx := context.Background()
+ s := release.NewDirStore(t.TempDir())
+ now := time.Date(2026, 9, 15, 12, 0, 0, 0, time.UTC)
+
+ // No index entry exists at all, and the delivered and standalone names
+ // still resolve, to the app sender.
+ for _, caller := range []string{
+ UserName("bag", "harbourmail"),
+ StandaloneUserName("bag", "harbourmail", "laptop"),
+ "arn:aws:iam::123456789012:user/carlos-mail/carlos-mail-bag-harbourmail",
+ } {
+ acct, app, sender, ok := SenderOfCaller(ctx, s, caller)
+ if !ok || acct != "bag" || app != "harbourmail" || sender != SenderAppID {
+ t.Errorf("SenderOfCaller(%q) = %q %q %q %v", caller, acct, app, sender, ok)
+ }
+ }
+
+ // A name nobody CARLOS minted stays unknown, and costs no read.
+ for _, caller := range []string{"", "someone-else", "carlos-mail-bag-harbourmail+s+"} {
+ if _, _, _, ok := SenderOfCaller(ctx, s, caller); ok {
+ t.Errorf("SenderOfCaller(%q) claimed to know it", caller)
+ }
+ }
+
+ // A slugged name is unparseable by construction and comes from the index.
+ names, err := SenderNames("bag", "harbourmail", "book.example.com", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, _, _, ok := SenderOfCaller(ctx, s, names.IAMUser); ok {
+ t.Fatal("a slugged name resolved before it was ever indexed")
+ }
+ if err := PutCallerIndex(ctx, s, names.IAMUser, "bag", "harbourmail", "book.example.com", now); err != nil {
+ t.Fatal(err)
+ }
+ acct, app, sender, ok := SenderOfCaller(ctx, s, names.IAMUser)
+ if !ok || acct != "bag" || app != "harbourmail" || sender != "book.example.com" {
+ t.Fatalf("SenderOfCaller(slug) = %q %q %q %v", acct, app, sender, ok)
+ }
+}