diff --git a/docs/experiment-review.md b/docs/experiment-review.md index 740fee8..36f0a0e 100644 --- a/docs/experiment-review.md +++ b/docs/experiment-review.md @@ -1,4 +1,4 @@ -# MAS v0.1 experiment review (first pass, 2026-10-08) +# MAS v0.1 experiment review (2026-10-08, provider pass 2026-10-18) Status of MAS-WP-0002, Milestone 9. The answers below come from building `mas_core`, the `mas` CLI, `mas-gateway` and the reference ticket service, @@ -8,9 +8,15 @@ loopback fixture servers). **Scope:** the MTA half of the email path was tested against a real Postfix (3.7.11, Debian bookworm, in Docker). The setup was SMTP → Postfix → `pipe` transport → `mas-gateway` → reference ticket service -(`experiments/postfix-probe/`). Mailbox providers (Gmail, Outlook, -corporate gateways) and public DNS/MX were not involved. That half moves -to **MAS-WP-0003**. +(`experiments/postfix-probe/`). + +**Provider pass (MAS-WP-0003, 2026-10-18):** the same stack ran as a live MX +for `mas.coulomb.social` on railiance01 (`deploy/railiance/`), with the +statute at `https://mas.coulomb.social/.well-known/mas`. Real mail came from +Gmail and Microsoft 365, and replies went out through Scaleway TEM. The +evidence is the gateway's JSONL event log plus the Postfix log. Forwarding +and mailing-list paths (T04) are still pending. Spec consequences are +collected in `docs/spec-change-proposals.md`. ## Answers to the review questions @@ -95,11 +101,56 @@ The results: decision. The gateway forwarded it as `valid: false`, and the app ignored it. -**Still untested (MAS-WP-0003):** -- whether senders' providers and web forms accept `!` and `=` in recipient - addresses; -- overlap with SRS (`SRS0=...`) and VERP when forwarding; -- facet stripping by providers. +**Providers (tested, MAS-WP-0003):** four address forms were sent from +four sender setups: +- forms: `ticket!callback@`, `ticket=ID@`, `ticket=ID+comments@`, + `ticket=RE=2026-4711@`; +- sender setups: Gmail web, the Gmail mobile app, and a Microsoft 365 tenant via + both Outlook mobile and Outlook desktop. + +| Sender | Compose form | Envelope recipient and To header | Transport | +|---|---|---|---| +| Gmail web | accepted all 4 unquoted | byte-identical | 209.85.x, STARTTLS, BDAT | +| Gmail mobile | accepted all 4 unquoted | byte-identical | same | +| M365, Outlook mobile | accepted all 4 unquoted | byte-identical | EOP 40.107.149.x | +| M365, Outlook desktop | accepted all 4 unquoted | byte-identical | EOP 52.101.171.x | + +- **Nothing was rewritten, quoted, rejected or stripped.** That covers `!`, + a doubled `=` and the `+comments` facet. The gateway classified every + message as the probe predicted: P2 function with `callback` → 201 + created; P1 resource → 202; the facet was recognised. +- **The reply direction also holds.** The service replied From + `ticket=GS-85QJ8WC09P@mas.coulomb.social` through Scaleway TEM. Gmail + showed the `=` From address as sent, with SPF, DKIM + (d=mas.coulomb.social) and DMARC all passing. A plain Gmail "Reply" went + back to that address byte-identical, with correct `In-Reply-To` and + `References`, and was correlated to the ticket (200). +- **Not covered:** consumer Outlook.com (the operator has no account; it + uses the same Exchange Online path as M365), the Outlook-on-the-web + compose form, and other providers such as Apple, Yahoo and GMX. Two + providers is evidence that `!` and `=` work in practice; it is not a + survey. + +**Operational findings from the live run:** +- **Stock Postfix kills P2.** `smtpd_relay_restrictions` must contain a + reject-class entry, or smtpd refuses to start. Also, unless + `allow_untrusted_routing = yes`, Postfix ignores access-map results for + local-parts containing `!` or `%` (it treats them as sender-specified + routing). `reject_unauth_destination` then refuses every P2 address. The + working MAS host config relays only `@DOMAIN` through an explicit + regexp, turns off `swap_bangpath` and `allow_percent_hack`, and ends the + list in `reject`. A host that "just enables MAS" on a default Postfix + will silently lose all `!` mail. +- **A TEM "blackhole" MX swallowed inbound mail.** Scaleway TEM's domain + setup suggests `MX 10 blackhole.tem.scaleway.com`. Added next to the real + MX at the same priority, it sent about half of all inbound mail to a + discard host, and nobody got a bounce. Sending providers' setup wizards + are a deliverability risk for MAS subdomains that receive mail. +- **Outbound port 25 was blocked** at the hosting provider (and on a + second host). Replies need an authenticated submission relay. + +**Still untested (T04):** overlap with SRS (`SRS0=...`) and VERP when +forwarding, and facet stripping by forwarders and lists. ### Resource addressing: did it improve correlation? Yes. The service replies *from* `ticket=GS-…@great-service.example`, so an @@ -133,6 +184,12 @@ should either say this explicitly, or define "type intake" semantics for an opaque target that names a declared resource type. ### Security: did any address start acting like a capability? +(Addendum from the live run: an intake address such as `ticket!callback@` +that auto-replies turns any forged `From:` into outbound mail to a third +party. That is a backscatter/reflection vector at the application layer. +The live deployment therefore caps all replies sent (300 in total, T06) +and discards Postfix's own bounces. See SCP-5.) + Not for reading or control. Knowing a ticket address grants no read access and no admin action. It does let anyone **append correspondence** to an open ticket, which is the intended function of a communication endpoint. @@ -140,10 +197,20 @@ That is also an injection and spam vector. Ticket ids are non-guessable (10 Crockford base32 characters), but sender policy belongs to the application, not to MAS. -## Next step (MAS-WP-0003, before the decision gate) -Run the provider half on a real subdomain: -- an MX pointing to a Postfix configured like the probe; -- the statute served at `https:///.well-known/mas`; -- test mail from Gmail, Outlook and one corporate gateway to - `ticket!callback@`, `ticket=ID@` and `ticket=ID+comments@`, plus replies - to the service's `ticket=ID@` From address. +## Decision gate (MAS-WP-0002), recommendation + +The gate asks which additions answer a need the experiment demonstrated. +This is a recommendation; the decision is the founder's. + +| Candidate | Demonstrated need? | Recommendation | +|---|---|---| +| Postfix/Exim integration | **Yes.** Default Postfix drops every P2 address, and the correct relay config is non-obvious | Build first: a documented, tested Postfix profile (later Exim) based on `deploy/railiance/entrypoint.sh` | +| Spec v0.1.1 errata | **Yes**, SCP-1 to SCP-5 | Do alongside the integration | +| Address composer | Weak. Providers accepted hand-typed addresses; the 64-octet budget and facet/interaction exclusivity are the only traps | Defer; `mas generate` in the CLI covers it | +| Mail-client inspector | Not shown; nothing was rewritten that a user would need to see | Defer | +| TypeScript / Go implementations | Not shown; no consumer outside Python yet | Defer until a second consumer exists | +| Signed statutes | Not shown; pinned local statutes covered the operator case (see Discovery) | Defer | +| P3 economic profile | Not exercised | Defer | + +T04 (SRS/VERP) could change the `!`/`=` row for forwarded mail. Revisit +this table once T04 is done. diff --git a/docs/spec-change-proposals.md b/docs/spec-change-proposals.md new file mode 100644 index 0000000..5c483d2 --- /dev/null +++ b/docs/spec-change-proposals.md @@ -0,0 +1,145 @@ +# MAS v0.1 spec change proposals (from MAS-WP-0002/0003) + +Status: **draft for review**. These are proposals against +`mas-v0.1-spec/`. The spec bundle stays unchanged until they are accepted, +because `mas_conformance/` and `mas_core/schema/` are vendored copies and +the tests check them for drift. Evidence references point to +`docs/experiment-review.md`. + +Proposed target: a v0.1.1 errata release for SCP-1 to SCP-3 and SCP-5, +which clarify or tighten without changing what canonical addresses mean. +SCP-4 is informative only. + +--- + +## SCP-1: quoted local-parts that are valid dot-strings are equivalent + +**Sections:** §4 Canonical ASCII local-part, §8 Address equivalence. + +**Evidence:** Postfix delivered `"ticket=PR42"@…` as `ticket=PR42` in all +five probe configurations (RFC 5321 treats unnecessary quoting as +equivalent). A receiver behind an MTA cannot tell which form the sender +used. + +**Proposed text (§4, after the second paragraph):** + +> A quoted local-part whose content, after removing the quotes and with no +> quoted-pairs, is a valid canonical MAS local-part is equivalent to that +> unquoted form. Receivers MUST treat the two identically. Generators MUST +> emit the unquoted form. + +**Impact:** clarification only. No parser change, since quoted input still +does not parse. A new conformance fixture should record the equivalence. + +--- + +## SCP-2: resource-type names are lower-case + +**Sections:** §6.1 Resource types, grammar `resource-type`, §8. + +**Evidence:** the ABNF accepts `Ticket=PR42`, but the statute schema only +registers lower-case type names. The address parses but can never match a +declared type, even under `ascii-insensitive`. + +**Options:** +- **(a) recommended:** restrict `resource-type` in the grammar to lower-case + ALPHA, DIGIT and `-`. `Ticket=PR42` then parses as an opaque target, which + is the safe outcome under §6.1 ("no semantics solely because of `=`"). +- (b) keep the grammar and state in §8 that the statute's equivalence policy + also applies to the type name. + +(a) keeps the type a protocol token rather than user data, and it matches +the schema. (b) makes type matching depend on the statute, which is +exactly the hidden inference §8 tries to avoid. + +**Impact:** (a) is a grammar tightening. None of the 31 shared conformance +fixtures uses an upper-case type, so a new fixture (`Ticket=PR42` → +opaque) records the change. No repo test uses one either. +Live traffic in MAS-WP-0003 used only lower-case types. + +--- + +## SCP-3: type-intake semantics for `TYPE!MODE` + +**Section:** §7 Interaction semantics. + +**Evidence:** in `ticket!callback`, the target `ticket` is an opaque +target with no defined link to the resource type `ticket`. The canonical +§7 example implies one. In the experiment, the statute had to register +`ticket` as a function (registry key) and the service had to know what +that means. Live, `ticket!callback@mas.coulomb.social` worked from Gmail +and M365 only because of that registry entry. + +**Proposed text (§7, new paragraph):** + +> An opaque target that equals a resource type declared in the statute, +> and that the statute also registers as a function, is that type's +> *intake*: mail to it requests creation or selection of a resource of +> that type. Without such a registration the target has no intake +> semantics, even if it equals a declared resource type. + +Also add a statute-schema field such as `"intake_for": "ticket"` on +registry entries, so the link is declared rather than implied by equal +names. + +**Impact:** additive; the schema gains an optional field. The policy +resolver reports `intake_for` when it is present. + +--- + +## SCP-4 (informative): deliverability evidence for `!` and `=` + +**Section:** §12 Deliverability and rewriting. + +**Evidence:** Gmail (web and app) and Microsoft 365 (Outlook mobile and +desktop) accepted and delivered all four test forms byte-identical. That +includes `!`, a doubled `=` and a `+` facet, both inbound and in a reply to +an `=` From address. Postfix 3.7 never applied the legacy `!`/`%` rewrites +to MAS addresses carrying an `@domain`. Pending: SRS and VERP (MAS-WP-0003 +T04). + +**Proposal:** keep the normative text. Add an informative note recording +the tested paths, and an implementation note that a default Postfix relay +configuration rejects every local-part containing `!` (see SCP-5 and the +review's operational findings). The "cooperating environments" wording for +`!` stays until T04 has covered forwarding. + +--- + +## SCP-5: intake auto-replies are a reflection vector + +**Section:** §14 Security (items 4 and 5). + +**Evidence:** an intake address that answers every request (as +`ticket!callback@` does with "your request is now ticket …") sends mail to +whatever `From:` the request carried. A forged `From:` makes the service +send mail to a third party, which is backscatter in all but name. §14.5 +only says "avoid backscatter" and is usually read as covering MTA bounces. +The live deployment had to add an application-level cap (T06) and discard +MTA bounces to keep the relay bounded. + +**Proposed text (§14, new item):** + +> 10. A receiver that automatically replies to mail sent to an intake or +> interaction address SHOULD reply only when the sender is +> authenticated (for example DMARC alignment pass), and MUST bound +> the volume of such replies (rate limits or caps). Replies MUST NOT +> be sent to automated mail (`Auto-Submitted`, `Precedence`). + +**Impact:** normative for receivers. The reference ticket service already +skips automated mail and has a send cap. A DMARC check would need the +gateway to pass `Authentication-Results` in the event, which is a small +event-schema addition. + +--- + +## Lower-priority gaps (no proposal yet) + +Recorded in the review, not urgent: +- interaction-mode names allowed by the value grammar but not by the schema + (`call.back`, `a=b`); +- registry keys containing `=` or leading/trailing dots, which need semantic + lint; +- discovery: whether the content type is checked, and the exact redirect + same-origin rule; +- statute-declared facets. diff --git a/workplans/MAS-WP-0003-provider-live-test.md b/workplans/MAS-WP-0003-provider-live-test.md index 937e68e..b850bd3 100644 --- a/workplans/MAS-WP-0003-provider-live-test.md +++ b/workplans/MAS-WP-0003-provider-live-test.md @@ -199,7 +199,7 @@ counted. ```task id: MAS-WP-0003-T05 -status: todo +status: progress priority: high state_hub_task_id: "63e86851-9c7d-5ce8-977f-5d09268d9dcc" ``` @@ -208,3 +208,11 @@ Fold the results into `docs/experiment-review.md`. Turn the spec gaps (quoted equivalence, resource-type case, type-intake semantics for `ticket!MODE`) into spec change proposals. Then take the MAS-WP-0002 decision gate. + +- 2026-10-18: provider results (T02/T03) and operational findings are in + `docs/experiment-review.md`. `docs/spec-change-proposals.md` holds + SCP-1 (quoted equivalence), SCP-2 (lower-case resource types), SCP-3 + (type intake), SCP-4 (informative deliverability) and SCP-5 (intake + auto-reply reflection). The review ends with a decision-gate + recommendation; the founder decides. Pending: T04 results, then + revisit SCP-4 and the gate table.