MAS v0.2 final (MAS-WP-0009 finished); residual MAS-WP-0014

Founder decision after the second implementation: close the two open gaps,
then declare v0.2 final. Section 13: parsers MUST reject over 64 octets
(fixtures p1-050/051); section 8: equivalence is between targets, key format
not normative. Draft markers removed, CHANGELOG dated 2026-10-19,
MAS_VERSION 0.2, mas_conformance vendors the v0.2 addresses.json. Mode
contract v0.1 stays draft: MAS-WP-0014 (independent email side).

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5787@bnt-lap001
Assistant-Session: 5db26b82-138b-40cc-9856-4d5eb0f752fd
This commit is contained in:
tegwick 2026-10-19 13:35:10 +02:00
parent a841ceec73
commit f07576451a
16 changed files with 178 additions and 40 deletions

View file

@ -7,8 +7,10 @@ Reference implementation and spec workbench for Mail Address Semantics
framework. Contents: parser, statute discovery, pure policy, generator,
CLI, mail gateway, reference ticket service, a tested Postfix integration
profile, and the mode contract with an email side that works as master or
slave. It implements address spec v0.1.1, the v0.2 draft and mode contract
v0.1 (draft).
slave. It implements address spec v0.1.1 and v0.2 (final), and mode
contract v0.1 (draft). A second, independent implementation in
JavaScript (`impl/js/`) validates v0.2 and the master side of the mode
contract.
## Core Idea
@ -23,9 +25,13 @@ delivery stays external.
## In Scope
- **Normative bundles.** `mas-v0.1-spec/` (v0.1.1 errata) and
`mas-v0.2-spec/` (draft: stand-ins, destination transforms,
`mas-v0.2-spec/` (final 2026-10-19: stand-ins, destination transforms,
`resource_id_case`). Spec changes go through
`docs/spec-change-proposals.md`.
- **`impl/js/`.** The second implementation: parser, statute-aware rules,
signing, outside master. Zero dependencies; `tests/test_js_impl.py` and
the differential test `tests/test_differential.py` run it when Node is
available.
- **`mas_core`.** Parser, model, statute validation and lint, exact-host
discovery with explicit `inherits`, policy, and the generator with v0.2
destination transforms.
@ -62,9 +68,8 @@ delivery stays external.
- Real non-email modes (telephony, chat, SMS, video), connectors to real
CMS or ticketing products, and the multimodal framework itself. Other
modes exist here only as contract-speaking test stubs.
- Implementations in other languages (TypeScript, Go), until a second
consumer exists. That is also the gate for declaring v0.2 final
(MAS-WP-0009).
- Further language implementations beyond `impl/js/`, and `impl/js/` as
a published package.
- Postfix policy daemon, Exim profile (until there is demand), mail-client
plugins and inspectors, browser extensions, a web composer.
- Central MAS registry, signed statutes, implicit parent-domain
@ -81,26 +86,28 @@ delivery stays external.
- Status: active. The reference experiment (MAS-WP-0002) passed its
decision gate on 2026-10-18; the findings are in
`docs/experiment-review.md`.
- Finished: MAS-WP-0001 to 0006, 0008, 0010 and 0011 (peer
authentication in the mode contract). Postfix profile, v0.1.1
- Finished: MAS-WP-0001 to 0006 and 0008 to 0013. That covers v0.2,
which is final; the mode contract with authentication, one outbound
path and harness-independent flows; and the second implementation. Postfix profile, v0.1.1
errata, v0.2 draft, address composer, v0.2 conformance cases, and the
mode contract with its reference implementation.
- Open: MAS-WP-0007 (retire the live MX; backlog, kept by decision),
MAS-WP-0009 (declare v0.2 final; waits for a second implementation),
and MAS-WP-0012 and 0013 (proposed).
- v0.2 stays a draft (founder, 2026-10-19).
- Open: MAS-WP-0007 (retire the live MX; backlog, kept by decision) and
MAS-WP-0014 (finalize mode contract v0.1; waits for an independent email
side).
- v0.2 is final (founder, 2026-10-19, after MAS-WP-0009). Mode contract
v0.1 stays a draft.
- P2 is decided: `!MODE` requests a handoff (founder, 2026-10-19; SCP-8,
v0.2 §7.1). The mode contract v0.1 draft and the reference
implementation are done (MAS-WP-0010); `mas test` runs the contract
corpus.
- The live MX runs image `mas-live:v0.2-a95f848` (redeployed 2026-10-19)
and is kept up by founder decision (MAS-WP-0007 stays in backlog).
Open follow-ups: MAS-WP-0012 (one outbound path and cap), 0013
(harness-independent flows), 0009 (second implementation, now covering
both drafts).
- Tests: `.venv/bin/pytest` runs 249 tests, offline apart from loopback
fixtures and the Docker probe. `mas test` runs four corpora: parser
62, statute 16, v0.2 statute-aware 29, mode contract 194.
The deployment with MAS-WP-0012 (one outbound path) is recorded in
MAS-WP-0007.
- Tests: `.venv/bin/pytest` runs 274 tests, offline apart from loopback
fixtures, the Docker probe and Node for `impl/js/`. `mas test` runs
four corpora: parser 64, statute 16, v0.2 statute-aware 33, mode
contract 195. `cd impl/js && node --test` runs 85 more.
- Assessments: `history/` (latest: `2026-10-19T090725Z-scope-vs-intent.md`).
## Getting Oriented

View file

@ -26,6 +26,7 @@ export class MasSyntaxError extends Error {
*/
export function parseLocalPart(local, profile = "p2") {
if (typeof local !== "string" || local.length === 0) throw new MasSyntaxError("empty");
if (octets(local) > MAX_OCTETS) throw new MasSyntaxError("too-long", "over 64 octets (section 13)");
if (/[^\x21-\x7e]/.test(local)) throw new MasSyntaxError("non-ascii", "outside the ASCII grammar");
if (/[$^]/.test(local)) throw new MasSyntaxError("reserved-operator");

View file

@ -1,6 +1,16 @@
# MAS specification changelog
## v0.2 (draft)
## v0.2 (2026-10-19)
Declared final by the founder after the second implementation
(MAS-WP-0009): `impl/js`, written from the texts alone, passes every
fixture unchanged and agrees with `mas_core` on 1500 generated
local-parts. Closed before release:
- **Length, §13.** A parser MUST reject a local-part over 64 octets.
Fixtures p1-050 (64 octets, valid) and p1-051 (65 octets, invalid).
- **Equivalence, §8.** Equivalence is between targets (facets and
interactions take no part). The key format is not normative.
New features, opt-in per statute with `"mas_version": "0.2"`
(mail-address-semantics MAS-WP-0005; proposals SCP-6 and SCP-7 in

View file

@ -1,4 +1,4 @@
# MAS v0.2 Conformance (draft)
# MAS v0.2 Conformance
## 1. Test surfaces
@ -102,4 +102,4 @@ python reference/mas_parser.py --self-test
```
The reference parser is non-normative; the grammar, specification, schema,
and fixtures are normative for this draft.
and fixtures are normative for v0.2.

View file

@ -1,10 +1,12 @@
# Mail Address Semantics (MAS) v0.2 (draft) — Specification Bundle
# Mail Address Semantics (MAS) v0.2 — Specification Bundle
This bundle is the **draft** of MAS v0.2. It extends v0.1.1 (see `CHANGELOG.md`).
This bundle is MAS v0.2, final as of 2026-10-19. It extends v0.1.1 (see
`CHANGELOG.md`). Two independent implementations pass its fixtures
unchanged: `mas_core` (Python) and `impl/js` (JavaScript).
## Contents
- `SPECIFICATION.md` — normative protocol/convention text (v0.2 draft)
- `SPECIFICATION.md` — normative protocol/convention text (v0.2)
- `CHANGELOG.md` — errata applied since v0.1
- `grammar/mas-v0.2.abnf` — canonical ASCII local-part grammar (unchanged from v0.1.1)
- `schema/mas-statute-v0.2.schema.json` — statute discovery document schema

View file

@ -1,8 +1,8 @@
# Mail Address Semantics (MAS) v0.2 (draft)
# Mail Address Semantics (MAS) v0.2
## 1. Status and normative language
This document is a **draft** of MAS v0.2. It includes v0.1.1 and adds
This document is MAS v0.2 (final, 2026-10-19). It includes v0.1.1 and adds
statute-enabled stand-ins for `=` and `!`, destination transforms for
generators, and a statute-declared resource-identifier case rule (see
`CHANGELOG.md`). The canonical grammar is unchanged from v0.1.1.
@ -344,6 +344,13 @@ A statute MAY declare:
Dot folding MUST NOT be assumed by default.
Equivalence is a property of **targets**. Two addresses are equivalent when
their targets are, after any stand-ins are read (§6.3) and under the rules
above. Facets and interactions take no part: `ticket=PR42+comments` and
`ticket=PR42` name the same target (§4.2). How an implementation compares
targets, for example through a normalized key, is not normative. Only the
resulting equivalence is.
A v0.2 statute MAY declare `equivalence.resource_id_case`:
- `exact` (default): resource identifiers compare exactly, subject to
@ -527,7 +534,8 @@ members in lower case, so it needs only `lowercase`.
## 13. Length
An SMTP local-part is limited to 64 octets.
An SMTP local-part is limited to 64 octets. A parser MUST reject a longer
local-part as non-canonical: it cannot be transported over SMTP.
Generators MUST enforce the underlying transport limit after encoding and
after destination transforms. `--` adds one octet and `---` adds two.

View file

@ -308,7 +308,7 @@
},
{
"id": "p1-041",
"local_part": "tïcket=PR42",
"local_part": "t\u00efcket=PR42",
"profile": "p1",
"valid": false,
"error": "non-ASCII outside canonical MAS v0.1"
@ -350,5 +350,28 @@
"facet": null,
"interaction": null
}
},
{
"id": "p1-050",
"local_part": "ticket=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"profile": "p1",
"valid": true,
"parsed": {
"target": "ticket=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"target_kind": "resource",
"resource_type": "ticket",
"resource_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"facet": null,
"interaction": null
},
"note": "64 octets: the SMTP limit (section 13)"
},
{
"id": "p1-051",
"local_part": "ticket=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"profile": "p1",
"valid": false,
"error": "longer than 64 octets",
"note": "v0.2: parsers MUST reject over 64 octets (section 13)"
}
]

View file

@ -1,4 +1,4 @@
; Mail Address Semantics (MAS) v0.2 (draft)
; Mail Address Semantics (MAS) v0.2
; The canonical grammar is unchanged from v0.1.1. The v0.2 stand-ins
; ("--" for "=", "---" for "!") are not canonical syntax: they parse as
; opaque targets and are interpreted only by statute-aware

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Non-normative MAS v0.1 canonical ASCII reference parser."""
"""Non-normative MAS canonical ASCII reference parser (grammar unchanged since v0.1.1)."""
import argparse
import json
@ -21,7 +21,7 @@ def parse(local_part, profile="p1"):
try:
local_part.encode("ascii")
except UnicodeEncodeError:
raise ValueError("non-ASCII outside canonical MAS v0.1")
raise ValueError("non-ASCII outside canonical MAS")
if '"' in local_part or "\\" in local_part or "@" in local_part:
raise ValueError("quoted/escaped/full address outside canonical local-part grammar")
@ -114,7 +114,7 @@ def self_test():
if failures:
print(json.dumps(failures, indent=2))
raise SystemExit(1)
print(f"PASS: {len(fixtures)} MAS v0.1 fixtures")
print(f"PASS: {len(fixtures)} MAS v0.2 fixtures")
if __name__ == "__main__":
ap = argparse.ArgumentParser()

View file

@ -1,7 +1,8 @@
"""MAS v0.1 conformance corpus.
`addresses.json` is vendored unchanged from the spec bundle
(`mas-v0.1-spec/conformance/addresses.json`) and is normative.
(`mas-v0.2-spec/conformance/addresses.json`, a superset of v0.1.1's) and is
normative.
`properties.json` adds implementation-independent property and boundary
cases. `statutes/` holds statute validation cases. `statute_aware.json` is
vendored unchanged from the v0.2 draft bundle

View file

@ -308,7 +308,7 @@
},
{
"id": "p1-041",
"local_part": "tïcket=PR42",
"local_part": "t\u00efcket=PR42",
"profile": "p1",
"valid": false,
"error": "non-ASCII outside canonical MAS v0.1"
@ -350,5 +350,28 @@
"facet": null,
"interaction": null
}
},
{
"id": "p1-050",
"local_part": "ticket=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"profile": "p1",
"valid": true,
"parsed": {
"target": "ticket=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"target_kind": "resource",
"resource_type": "ticket",
"resource_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"facet": null,
"interaction": null
},
"note": "64 octets: the SMTP limit (section 13)"
},
{
"id": "p1-051",
"local_part": "ticket=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"profile": "p1",
"valid": false,
"error": "longer than 64 octets",
"note": "v0.2: parsers MUST reject over 64 octets (section 13)"
}
]

View file

@ -1,8 +1,8 @@
"""Mail Address Semantics (MAS) v0.1 reference implementation.
"""Mail Address Semantics (MAS) reference implementation: address spec v0.2, mode contract v0.1.
Parsing is local and deterministic. Discovery is networked. Policy is pure.
Delivery remains external.
"""
MAS_VERSION = "0.1"
MAS_VERSION = "0.2"
__version__ = "0.1.0.dev0"

View file

@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "mas-core"
version = "0.1.0.dev0"
description = "Mail Address Semantics (MAS) v0.1 reference implementation"
description = "Mail Address Semantics (MAS) reference implementation (address spec v0.2, mode contract v0.1)"
requires-python = ">=3.11"
dependencies = ["jsonschema>=4.18"]

View file

@ -21,7 +21,7 @@ FIXTURES = address_fixtures() + property_fixtures()
def test_vendored_corpus_matches_spec_bundle():
spec = json.loads((ROOT / "mas-v0.1-spec/conformance/addresses.json").read_text())
spec = json.loads((ROOT / "mas-v0.2-spec/conformance/addresses.json").read_text())
assert spec == address_fixtures()

View file

@ -4,7 +4,7 @@ type: workplan
title: "Declare MAS v0.2 final"
domain: communication
repo: mail-address-semantics
status: active
status: finished
owner: claude
topic_slug: coulomb-social
created: "2026-10-19"
@ -43,7 +43,7 @@ read in a way `mas_core` does not.
```task
id: MAS-WP-0009-T02
status: progress
status: done
priority: low
state_hub_task_id: "a30c1b69-4b41-5636-865a-82341175668b"
```
@ -80,3 +80,19 @@ in the changelog.
and the JS parser does not. No fixture covers it.
- **Not independently validated:** the email side of the mode contract.
It exists only in Python; the JS work covers the master side.
## Outcome of T02 (2026-10-19)
- **Founder decision: close the open gaps, then declare v0.2 final.**
- Length (§13): parsers MUST reject over 64 octets. Fixtures p1-050 and
p1-051. The JS parser was aligned; `mas_core` already did this.
- Equivalence (§8): it is between targets (facets and interactions take
no part), and the key format is not normative.
- **v0.2 is final** (2026-10-19). "Draft" is removed from the bundle and
`MAS_VERSION` is 0.2. `mas_conformance/addresses.json` now vendors the
v0.2 bundle (a superset of v0.1.1's).
- **Results.** Both implementations pass every fixture: `mas test` parser
64/64 and statute-aware 33/33; JS `node --test` 85/85. They agree on
every generated case in the differential test.
- **Mode contract v0.1 stays a draft** (founder). Residual: MAS-WP-0014
(an independent email side).

View file

@ -0,0 +1,47 @@
---
id: MAS-WP-0014
type: workplan
title: "Finalize mode contract v0.1: an independent email side"
domain: communication
repo: mail-address-semantics
status: backlog
owner: claude
topic_slug: coulomb-social
created: "2026-10-19"
updated: "2026-10-19"
origin: residual
origin_ref: MAS-WP-0009
---
# Finalize mode contract v0.1
Residual of MAS-WP-0009. The founder decided on 2026-10-19 that mode
contract v0.1 stays a draft. Its master side is validated independently
(`impl/js/src/master.mjs` passes the slave-setup flows), but the email side
exists only in Python (`mas_modes.email_mode`).
## An independent email side, or a real integration
```task
id: MAS-WP-0014-T01
status: wait
priority: low
```
Do one of these. Either build an email side independent of
`mas_modes.email_mode`, in another language or by another team, that
passes `mas-modes-v0.1-spec/conformance/flows.json` (both roles) from the
bundle alone. Or run a real outside master or mode integration (a CMS,
ticketing, telephony) against the contract and record the findings. Fold
the findings into CONTRACT.md as proposals, as SCP-9 did for v0.2.
## Declare final
```task
id: MAS-WP-0014-T02
status: wait
priority: low
```
With the findings folded in, ask the founder to declare mode contract v0.1
final. Then remove "draft" from the bundle and v0.2 §7.1.