Grammar, statute schema key pattern, parser (is_resource_type, also used by statute lint), reference parser, spec 6.1 and a new CHANGELOG. Fixtures p1-042..045; prop-case-001 Ticket=PR42 is now invalid. 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
311 lines
14 KiB
Markdown
311 lines
14 KiB
Markdown
# MAS v0.1 spec change proposals (from MAS-WP-0002/0003)
|
|
|
|
Status: **accepted by the founder (2026-10-18)**: SCP-1 to SCP-7 as written
|
|
below, including the decisions recorded under SCP-2 and SCP-6.
|
|
Implementation is tracked in MAS-WP-0005. 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 `-`. (Correction during implementation: `Ticket=PR42`
|
|
does **not** become an opaque target, because opaque targets cannot
|
|
contain `=`. It becomes a non-canonical address, rejected with
|
|
`invalid-resource-type` and carried as ordinary email. That is stricter
|
|
and still safe under §6.1.)
|
|
- (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-enabled stand-ins and destination transforms (`--` for `=`, `---` 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.
|
|
|
|
- Similar endpoint behaviours are covered in the same mechanism: a stand-in
|
|
for `!`, plus case, facet, interaction, quoting, dot and length handling
|
|
(founder confirmed, 2026-10-18).
|
|
- Stand-ins use **dashes only**: `--` for `=` and `---` for `!`. No other
|
|
stand-in characters are added. `+` is considered functional by default
|
|
and has no stand-in (founder, 2026-10-18).
|
|
|
|
**Proposal, receiving side (normative):** the spec defines two fixed
|
|
stand-ins. A statute MAY enable each one:
|
|
|
|
```json
|
|
"alternate_syntax": { "resource": true, "interaction": true }
|
|
```
|
|
|
|
The stand-ins are fixed by the spec, not configurable, so every generator
|
|
and receiver uses the same forms.
|
|
|
|
- **Resource stand-in `--` for `=`.** In a target `TYPE--ID`, where `TYPE`
|
|
is a resource type declared in the same statute, the form is equivalent
|
|
to `TYPE=ID`. The policy resolver reports `target_syntax:
|
|
"resource-alternate"` and the canonical equivalence key `TYPE=ID`.
|
|
- **Interaction stand-in `---` for `!`.** A local-part ending in
|
|
`---MODE`, where `MODE` is an interaction mode the statute enables, is
|
|
equivalent to `!MODE`: `ticket---callback` ≡ `ticket!callback`.
|
|
- **Matching order (normative):**
|
|
1. **Interaction first.** If the local-part ends in `---MODE` with an
|
|
enabled `MODE`, that suffix (at the last such `---`) is the
|
|
interaction.
|
|
2. **Resource next.** In the remainder, `TYPE--ID` with a declared type
|
|
is a resource. The split is at the first `--`.
|
|
|
|
Examples:
|
|
- `ticket---callback` ≡ `ticket!callback`
|
|
- `ticket--GS1---callback` ≡ `ticket=GS1!callback`
|
|
- `ticket--A---B` ≡ `ticket=A---B` if `B` is not an enabled mode
|
|
- **Constraints:**
|
|
- Resource-type names (lower-case after SCP-2) never contain `--`.
|
|
- Interaction mode names do not start with `-`.
|
|
- A namespace that enables the interaction stand-in MUST NOT issue
|
|
resource IDs containing `---`.
|
|
- A namespace with existing opaque names matching either pattern must
|
|
not enable that stand-in.
|
|
- **No stand-in for `+`.** `+` is functional by default: every endpoint
|
|
tested accepted it, and facets degrade safely when stripped (§12).
|
|
- Without the statute switch, or for an undeclared type or mode, `--` and
|
|
`---` have no meaning (§6.1 still applies). Receivers store, correlate
|
|
and reply using the canonical form only.
|
|
|
|
**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. The set is closed and covers the
|
|
endpoint behaviours seen or expected:
|
|
|
|
| Transform | Effect | Allowed only if |
|
|
|---|---|---|
|
|
| `resource-alternate` | `TYPE=ID` → `TYPE--ID` | statute enables `alternate_syntax.resource` |
|
|
| `interaction-alternate` | `!MODE` → `---MODE` | statute enables `alternate_syntax.interaction` and the mode `MODE` |
|
|
| `lowercase` | fold the whole local-part to lower case | statute declares case-insensitive identifiers (SCP-7); types are lower-case (SCP-2) |
|
|
| `drop-facet` | remove `+FACET` (loses information) | always; only when explicitly requested |
|
|
| `drop-interaction` | remove `!MODE` (loses the requested mode; the mail still reaches intake) | always; only when explicitly requested, with a warning |
|
|
|
|
Endpoint behaviours that need no transform:
|
|
- **Quoting:** generators never quote, and receivers treat quoted and
|
|
unquoted forms as equal (SCP-1).
|
|
- **Dots:** generators never insert dots. Dot handling is statute dot
|
|
folding (§8).
|
|
- **Length:** after all transforms, the 64-octet limit is checked again
|
|
(`--` adds one octet and `---` adds two). A generator MUST refuse rather than
|
|
truncate.
|
|
- **`$` and `^`:** remain reserved; P3 is out of scope.
|
|
|
|
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 `resource-alternate` + `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.
|
|
|
|
**Impact:** additive: an optional schema object (`alternate_syntax`, two
|
|
booleans),
|
|
parser and policy support for both stand-ins, a generator `destination`
|
|
parameter with the five transforms, 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.
|