# MAS v0.1 experiment review (first pass, 2026-10-08) 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, and from running the test suite (135 tests, all offline except the 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**. ## Answers to the review questions ### Syntax: did the parser stay simple? Yes. The parser is about 100 lines with no I/O. It always uses the maximal (P2) grammar and records `required_profile`, so `ticket!callback` parses without knowing anything about the namespace. All 31 spec fixtures and 27 added property cases pass. Gaps found in the spec: - **Resource-type case.** The ABNF allows `Ticket=PR42`, but the statute schema only registers lower-case type names. Such an address parses, but it can never match a declared type, even when the statute says `ascii-insensitive`. The spec should either make resource types lower-case in the grammar, or state that the equivalence policy also applies to the type name. - **Interaction-mode names.** The value grammar allows `call.back` and `a=b`. The schema's mode-name pattern does not. Those modes parse but can never be enabled. This is harmless, but it should be documented. - **Registry keys.** The schema pattern already blocks `+ ! $ ^`, but it still allows `=` and leading or trailing dots. Semantic lint is needed to reject a key like `ticket=GS-1` (`registry-key-resource`). ### Discovery: was exact-host metadata sufficient? For this experiment, yes. Implementation choices the spec does not settle: - `application/json` (or `+json`) is required as the content type (`InvalidContentType`). The spec only asks for 2xx plus the schema. It should say whether the content type is checked. - Redirects are followed only when scheme, host and port all match. That is stricter than "same host". A port change counts as `CrossHostRedirect` and an https→http change as `SchemeDowngrade`. - `mas_version` is checked before the schema runs, so a future version is reported as `UnsupportedVersion` and not as a schema error. - **Operational finding:** a service must not depend on discovering *its own* statute. If the statute endpoint is down, the ticket service sees `namespace_confirmed = false` and correctly refuses to act. The mail is then declined. Operators should pin their own statute in local configuration (`mas-gateway --statute`). Discovery is for third-party namespaces. ### Policy: could all inference stay explicit? Yes. Policy is a pure function. Without a statute, `kind` is always `opaque`, `=` alone gives no resource semantics, and `!` stays "unconfirmed". `mas explain` lists each of these as an *unknown*. Folding is applied only when a statute declares it, and dot folding never touches resource identifiers. Facets have no statute declaration at all, so they are always reported as "application-defined". A later version could let statutes list the facets they recognise. ### Email infrastructure: which forms were rewritten or rejected? **Postfix (tested):** 14 address forms were sent through 5 Postfix configurations (`experiments/postfix-probe/results/`): - relay domain with defaults; - local rewrite context, with and without `swap_bangpath` / `allow_percent_hack`; - domain in `mydestination` (local delivery class), with and without those two settings. The results: - **13 of 14 forms arrived byte-identical in every configuration.** That includes `ticket!callback`, `ticket=ID+comments`, `ticket==X1`, `ticket=PR%2B42`, `ticket=ID+comments!callback` and `ticket=PR42$4.99`. `recipient_delimiter = +` does not change the envelope recipient that `pipe` passes on as `${recipient}`. Upper case is preserved. - **The quoted local-part `"ticket=PR42"` arrived unquoted** as `ticket=PR42`. Postfix drops quotes that are not needed (RFC 5321 treats the two forms as equivalent), so a form the spec calls non-canonical became canonical in transit. **Spec gap:** §4 should say that a quoted local-part whose content is a valid dot-string is equivalent to the unquoted form, because an MAS receiver after an MTA cannot tell them apart. - **The `!` and `%` legacy rewrites did not trigger** for any MAS address, even with Postfix defaults and the domain in `mydestination`. In 3.7 the bang-path swap fired only for a bare `site!user` with no `@domain` (checked via local `sendmail`: it was rerouted to `site`). MAS addresses always carry an `@domain`. Setting `swap_bangpath = no` and `allow_percent_hack = no` is still a sensible hardening default for MAS hosts. - `$` passed through SMTP untouched. Rejecting it is purely a MAS parser 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. ### Resource addressing: did it improve correlation? Yes. The service replies *from* `ticket=GS-…@great-service.example`, so an ordinary reply lands on the resource address. Correlation uses the address only. The tests send an unrelated subject and a facet-stripped address, and both correlate. ### Subject lines: could identifiers disappear? Yes. Replies use the customer's own subject, and no ticket id is ever put in it (asserted in the tests). ### Lifecycle: did closed resources stay understandable? Yes. A closed ticket stays addressable. Mail to it is archived separately and the ticket is not changed. A single notice goes only to the original requester, and never to automated senders (`Auto-Submitted`, `Precedence`). Mail to an unknown resource is quarantined with no reply. For an MTA pipe, the gateway exits 0 when the app declines and 75 when the app is unreachable, so it never generates a bounce (no backscatter). ### Interaction: was `!callback` useful? Partly. It expresses intent cleanly, but the service still needs a phone number and a time. The address cannot carry those, so the first reply has to ask by email. `!callback` therefore starts a negotiation; it does not replace one. The bare `ticket` in `ticket!callback` is an **opaque** target. It has no defined link to the resource type `ticket`. The spec's canonical example (§7) implies such a link. The statute has to register `ticket` as an intake function, and the service has to know what that means. The spec 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? 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. 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.