mail-address-semantics/docs/experiment-review.md
tegwick 59815701c1 Implement MAS v0.1 reference experiment (MAS-WP-0002 M0-M8, M9 first pass)
- mas_core: P2-maximal parser with required_profile, generator, statute
  schema + semantic lint + defaults-only inheritance, exact-host discovery
  with typed failures and same-origin redirects, pure policy resolver
- mas_conformance: vendored spec fixtures (drift-tested) + property and
  statute cases
- mas CLI: parse / explain / discover / lint-statute / test
- mas-gateway: generic mail -> MAS event -> HTTP (MTA pipe + webhook)
- examples: static great-service.example statute, reference ticket service
- docs/experiment-review.md: first-pass findings; live MTA test still open

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 623586@bnt-lap001
Assistant-Session: 10b30928-5b3b-4c11-9934-5ecca07367d8
2026-10-08 00:24:43 +02:00

6 KiB

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 caveat: every "email path" here is simulated. Raw RFC 5322 messages go through the real mas-gateway pipe entrypoint, but no production MTA or mailbox provider was involved. The Email infrastructure question is therefore still open and needs the live test described at the end.

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?

Not yet tested. Known risks to check:

  • = overlaps with SRS (SRS0=...) and VERP encodings.
  • ! is UUCP bang-path syntax. Some validators and web forms reject it.
  • Postfix recipient_delimiter = + strips the facet for lookups. That fits the facet-degradation design.

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.

Run the live path: a real domain, the static statute served at /.well-known/mas, Postfix with the mas-gateway pipe transport, and test mail sent from Gmail, Outlook and one corporate gateway to ticket!callback@, ticket=ID@ and ticket=ID+comments@. Record which addresses arrive unchanged. That evidence answers the open Email infrastructure question.