Commit Graph
2 Commits
Author SHA1 Message Date
mixandClaude Opus 5.5 a618ffb0e4 send log: rows belong to a domain by id (audit L12, 0008)
A domain user's log, the message page, a domain's statistics, the level-2
count and the notifier's refusal count picked send-log rows by domain name,
so a domain deleted and re-added under its old name showed its earlier mail
to the next assignee and went on counting it.

Migration 0008 (not run on the live database yet, so extended here):
send_log.domain_id and inbound_spam_log.inbound_domain_id, REFERENCES ...
ON DELETE SET NULL, filled once by name from the registry as it is at
migration time, each with an (id, created_at) index.

- Writes: InsertQueued / InsertRejected set domain_id to the domain of the
  row's login (loginDomainID) - the journal milter and the sender check's
  refusal rows both go through them; the inbound journal sets
  inbound_domain_id from the inbound domain's name at insert.
- Reads: SendLogFilter.DomainIDs (was Domains, by name) and
  InboundSpamFilter.DomainIDs are the scope; the global role still reads
  everything. The message page checks row.DomainID against the user's
  domains. DomainSendStats and CountMessages keep their name argument and
  select domain_id = (SELECT id FROM domains WHERE name = ?). The notifier
  joins send_log.domain_id to the domain and its application.
- DMARC reports stay by name on purpose (a report is about the DNS name).

Tests: store - scope by ids; a re-added domain's log, level-2 count and
statistics hold its own row only, the global log both; inbound scope by
ids. Handlers - the new assignee gets 404 for the old row's page and does
not see it in the log, the global role sees both. Tests that wrote rows for
a login that is no application now register it (a row is a domain's through
its application). Schema at 0008 with the two columns and indexes.

schema-migrations row 8, CHANGELOG [Unreleased], audit plan line ticked,
roadmap 19/62.
Evidence: go vet ./... clean; go test ./... ok locally but for the two
Windows-only tests; the server run follows before the push.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:37:26 +03:00
mixandClaude Opus 5.5 f51f5db245 app: logins folded to lower case, unique without regard to case (audit H4, 0008)
Postfix's reject_sender_login_mismatch compares the SASL login with the
sender map without regard to case - seen on the e2e stand this time (a
one-off probe, not committed: map rewritten to "APP1", client authenticated
as "app1", RCPT accepted). The panel stored logins as typed under a BINARY
UNIQUE, so "Ops" and "ops" were two applications to the panel and one sender
to Postfix: a tenant could send with another tenant's envelope sender.

- app.foldLogin: Service.Create lower-cases the login (owner: fold, do not
  refuse); CreateWithSettings rolls back the stored (folded) login.
- ImportApplication keeps the login as written: folding it would rename the
  SASL account and break the imported application's working credentials;
  the index below refuses a case clash there too.
- Migration 0008 (DDL): CREATE UNIQUE INDEX idx_applications_login_nocase ON
  applications (login COLLATE NOCASE). The column's own BINARY UNIQUE stays
  (SQLite cannot change a collation without a table rebuild, which a
  migration transaction cannot do with foreign keys on). Checked on the live
  installation first: 4 logins, no case clash; "Quieter3C" keeps its case.
  0008 is not run anywhere yet; send_log.domain_id joins it later in stage 2.

Tests: "Ops" created as ops, SASL account and map binding in lower case,
OPS in another domain and an imported oPs refused; the store refuses ops
beside Ops; schema at user_version 8 with the index.

CHANGELOG [Unreleased], audit plan line ticked, roadmap 15/61.
Evidence on the test server for this tree (with the probe): go vet clean,
go test ./... 23 packages ok, make e2e exit 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:55:24 +03:00