MAS-WP-0003-T05: provider results in review, spec change proposals, gate recommendation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 252470@bnt-lap001 Assistant-Session: ad628c8f-4be2-4979-94c9-76c7ed6f8241
This commit is contained in:
parent
d4585eb542
commit
d7264e7ec5
3 changed files with 237 additions and 17 deletions
|
|
@ -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://<sub>/.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.
|
||||
|
|
|
|||
145
docs/spec-change-proposals.md
Normal file
145
docs/spec-change-proposals.md
Normal file
|
|
@ -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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue