Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 5787@bnt-lap001 Assistant-Session: 5db26b82-138b-40cc-9856-4d5eb0f752fd
3.2 KiB
| id | type | title | domain | repo | status | owner | topic_slug | created | updated | origin | origin_ref | state_hub_workstream_id |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| MAS-WP-0012 | workplan | One outbound path and one send cap | communication | mail-address-semantics | finished | claude | coulomb-social | 2026-10-19 | 2026-10-19 | residual | MAS-WP-0010 | be2db53f-9acc-5bf0-a6dc-fb99cbc8e46b |
One outbound path and one send cap
Risk C in history/2026-10-19T090725Z-scope-vs-intent.md. With both
roles in one deployment, the ticket service and mas_modes.mailer each
keep their own outbox and cap. Total outbound can then reach about twice
the configured cap. They also act differently over the cap: the ticket
service keeps replies unsent, while the mailer refuses with
send-cap-reached. Principle 10 says "caps how much it can send in
total".
Route all mail through the mailer
id: MAS-WP-0012-T01
status: done
priority: medium
state_hub_task_id: "5d9fefac-684d-5fe0-bead-f664952047e0"
Make mas_modes.mailer.Mailer the only outbound path. The ticket service
then sends through a Mailer it is given, sharing the DB or an outbox
service, instead of its own outbox table and cap. Settle one behavior
over the cap, and record it in CONTRACT.md §6. Keep the ticket service's
/outbox inspection working, and keep the deployment's --max-sent
semantics.
Test the shared cap
id: MAS-WP-0012-T02
status: done
priority: medium
state_hub_task_id: "431cc528-cf9f-5991-8044-7cbab2ac0021"
Add a test that a deployment with both roles stops at exactly N sends in
total, and that the live entrypoint still passes MAS_MAX_SENT to the
single cap.
Outcome
- Founder interview (2026-10-19). Over the cap, mail is held and
reported. Held mail is purged after
email-held-duration-max(default 7d) and not stored beyondemails-held-storage-max(default 100MB). The live counter is migrated once at start. mas_modes.mailer.Maileris the one outbound path: its own SQLite file and lock, principle 10, one lifetime cap, holding with a purge on every send andpurge_held(), a storage limit,import_legacy()with a once-only marker, andparse_duration/parse_size.- Ticket service. It sends through a Mailer it is given, or builds one.
/outboxshows the shared outbox (ticket_id= case ref). Its legacyoutboxtable is imported once and then left alone. New CLI options:--outbox-db(default<db>-outbox.sqlite3),--email-held-duration-maxand--emails-held-storage-max. Over the cap the closed notice reportsnotified: false. - EmailMode takes the shared Mailer.
mail.sendover the cap answerssend-cap-reachedwithheld. CONTRACT.md §5 and §6 are updated. - Deploy. The entrypoint passes
MAS_MAX_SENT(300) and the two hold limits (MAS_EMAIL_HELD_DURATION_MAX,MAS_EMAILS_HELD_STORAGE_MAX). The migration was checked on a copy of the live DB: 9 legacy rows, 4 sent, carried over; no second import on restart. - Tests.
tests/test_outbound.py(15): exactly N sends across both roles, purge by age, storage limit, sent mail never purged, the legacy migration, parsers, entrypoint flags. - Deployed 2026-10-19 with
mas-live:v0.2-480e69a. The live counter was migrated: 4 of 300 (see MAS-WP-0007).