mail-address-semantics/docs/spec-change-proposals.md
tegwick 8351b8a1f0 MAS-WP-0005-T01: SCP-6 decided (statute opt-in, split rule, destination transforms)
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
2026-10-18 17:57:21 +02:00

268 lines
12 KiB
Markdown

# 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, SCP-5 and the
recommendation part of SCP-7. Errata here are changes that clarify or
tighten without changing what canonical addresses mean. SCP-6 is a new
feature and targets v0.2. 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`.
**Decision (founder, 2026-10-18): option (a).** Resource types are
lower-case in the grammar.
**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).
**Counter-evidence (T04):** the IONOS forwarding form refused `=` and
forced lower case. Google Groups accepted `=` but lower-cased the member
address (SCP-6, SCP-7). SRS and list bounce addresses never collided with
`=` or `+`. Mail *clients* preserved MAS syntax;
provider *configuration forms* did not.
**Proposal:** keep the normative text. Add an informative note recording
the tested paths, including the IONOS refusal, 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.
---
## SCP-6: statute-declared alternate resource separator (`--` for `=`)
**Sections:** §6 Resource semantics, §11 Statute document, §12.
**Evidence:** the IONOS forwarding form refuses `=` in a destination
address. The operator compensated with `ticket--GS-…`, and it was
delivered, but under v0.1 it is only an opaque target. The service
correctly could not attach it to the ticket.
**Decisions (founder, 2026-10-18):**
- `=` stays canonical. `--` is only a replacement for endpoints that refuse
`=`.
- It is enabled by **statute opt-in** (option A), not as a spec-wide default.
- The spec stays normative for the mechanism and explains how specific
integrations are done through the statute when needed. Concrete
integrations are not part of the normative text.
**Proposal, receiving side (normative):** a statute MAY declare an
alternate separator for resource targets:
```json
"resource_syntax": { "alternate_separator": "--" }
```
- When it is declared, a target `TYPE--ID`, where `TYPE` is a resource type
declared in the same statute, is equivalent to `TYPE=ID`. The policy
resolver reports `target_syntax: "resource-alternate"` and the canonical
equivalence key `TYPE=ID`. Receivers store, correlate and reply using the
canonical form only.
- **Split rule:** together with SCP-2, resource-type names are lower-case
letters and digits with single hyphens and never contain `--`. The first
`--` therefore always ends the type: `ticket--RE--2026` is type `ticket`,
ID `RE--2026`. IDs may contain `--`, just as they may contain `=`.
- Only a declared type triggers the split. Without the declaration, or
with an undeclared type, `--` has no meaning (§6.1 still applies).
- The separator must be dot-atom-safe and must not contain `.` or `=`. A
namespace whose existing opaque names start with `TYPE--` must not
declare it.
**Proposal, sending side (normative mechanism, informative integrations):**
generators produce addresses for a *destination*, such as a forwarding
form, a mailing list or a reply header. The spec defines a **destination
transform**, the set of changes a generator may apply so that an address
fits a destination's constraints. For v0.1/v0.2 there are two:
| Transform | Effect | Allowed only if the namespace's statute |
|---|---|---|
| `separator` | `TYPE=ID` → `TYPE<alt>ID` | declares `alternate_separator` |
| `lowercase` | fold the whole local-part to lower case | declares case-insensitive identifiers (SCP-7) and lower-case types (SCP-2) |
A generator MUST emit the canonical form when the destination allows it.
It MAY apply the transforms the destination needs, and only those the
namespace's statute permits. If the result would not be accepted by the
namespace, the generator MUST report that the address cannot be expressed
for that destination, instead of emitting a form the receiver would
misread. Outbound service mail (From/Reply-To) stays canonical unless a
destination transform is known to be needed.
An informative section, "Integrating with constrained endpoints", explains
the procedure:
1. observe what the endpoint refuses or rewrites;
2. declare the matching acceptance in the statute;
3. describe the endpoint's constraints as a destination profile in the
generator's configuration.
It uses IONOS forwarding as the worked example: `=` is refused and case is
folded, so the profile is `separator` + `lowercase`, and the result is
`ticket--gs-85qj8wc09p+comments@`. Concrete profiles (e.g. `ionos-forward`)
live in implementation configuration or a catalog, not in the spec.
**Open:** whether transforms beyond separator and case are needed, for
example for `!`. No endpoint has refused `!` so far, but none was tested
with it. Leave the transform list closed in v0.2 and extend it only on
evidence.
**Impact:** additive: an optional schema field, a policy change, a
generator `destination` parameter, and new fixtures. Targets v0.2. The
composer (MAS-WP-0006) is the first consumer of destination profiles.
---
## SCP-7: resource identifiers must survive lower-casing
**Sections:** §6.2 Resource identifiers, §8 Address equivalence, §13.
**Evidence:** the IONOS forwarding form forces destination addresses to
lower case: `GS-85QJ8WC09P` became `gs-85qj8wc09p`. Google Groups did the
same after a member accepted the invitation: the invitation was delivered
mixed-case, but list mail went to the lower-cased address and was
quarantined as an unknown resource. With the spec default
(exact comparison) and a statute declaring `case: exact`, the identifier
no longer matches. Many systems treat local-parts as case-insensitive in
practice, despite RFC 5321.
**Priority:** highest of this set. Two of two forwarding/list providers
lower-cased the recipient.
**Proposal:**
- §6.2: "Generators SHOULD use resource identifiers whose meaning does not
depend on letter case (for example lower-case only, or a case-insensitive
alphabet such as Crockford base32), because forwarders and web forms may
change the case of local-parts."
- Statute: allow `equivalence.resource_id_case: "insensitive"` independently
of the local-part `case` setting. Receivers then fold the identifier for
lookup but must not fold anything else.
- Together with SCP-2, the resource type is already lower-case.
**Impact:** recommendation plus an optional statute field. The reference
service could switch to lower-case IDs today. Crockford base32 decoding is
case-insensitive by design.
---
## 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.