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:
parent
a841ceec73
commit
f07576451a
16 changed files with 178 additions and 40 deletions
43
SCOPE.md
43
SCOPE.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
47
workplans/MAS-WP-0014-finalize-mode-contract.md
Normal file
47
workplans/MAS-WP-0014-finalize-mode-contract.md
Normal 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue