From f07576451abcbbb4a7dcf481c308dcd227657a37 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 19 Oct 2026 13:35:10 +0200 Subject: [PATCH] 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 Assistant: claude-code Assistant-Model: opus Assistant-Process: 5787@bnt-lap001 Assistant-Session: 5db26b82-138b-40cc-9856-4d5eb0f752fd --- SCOPE.md | 43 ++++++++++------- impl/js/src/parser.mjs | 1 + mas-v0.2-spec/CHANGELOG.md | 12 ++++- mas-v0.2-spec/CONFORMANCE.md | 4 +- mas-v0.2-spec/README.md | 8 ++-- mas-v0.2-spec/SPECIFICATION.md | 14 ++++-- mas-v0.2-spec/conformance/addresses.json | 25 +++++++++- mas-v0.2-spec/grammar/mas-v0.2.abnf | 2 +- mas-v0.2-spec/reference/mas_parser.py | 6 +-- mas_conformance/__init__.py | 3 +- mas_conformance/addresses.json | 25 +++++++++- mas_core/__init__.py | 4 +- pyproject.toml | 2 +- tests/test_parser_conformance.py | 2 +- workplans/MAS-WP-0009-declare-v0-2-final.md | 20 +++++++- .../MAS-WP-0014-finalize-mode-contract.md | 47 +++++++++++++++++++ 16 files changed, 178 insertions(+), 40 deletions(-) create mode 100644 workplans/MAS-WP-0014-finalize-mode-contract.md diff --git a/SCOPE.md b/SCOPE.md index 2a8eb9a..bad27be 100644 --- a/SCOPE.md +++ b/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 diff --git a/impl/js/src/parser.mjs b/impl/js/src/parser.mjs index 8f7191a..d1c5ac8 100644 --- a/impl/js/src/parser.mjs +++ b/impl/js/src/parser.mjs @@ -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"); diff --git a/mas-v0.2-spec/CHANGELOG.md b/mas-v0.2-spec/CHANGELOG.md index 9fc4d7a..7063176 100644 --- a/mas-v0.2-spec/CHANGELOG.md +++ b/mas-v0.2-spec/CHANGELOG.md @@ -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 diff --git a/mas-v0.2-spec/CONFORMANCE.md b/mas-v0.2-spec/CONFORMANCE.md index 3b67752..46bfd74 100644 --- a/mas-v0.2-spec/CONFORMANCE.md +++ b/mas-v0.2-spec/CONFORMANCE.md @@ -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. diff --git a/mas-v0.2-spec/README.md b/mas-v0.2-spec/README.md index 1f4206a..820d6e6 100644 --- a/mas-v0.2-spec/README.md +++ b/mas-v0.2-spec/README.md @@ -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 diff --git a/mas-v0.2-spec/SPECIFICATION.md b/mas-v0.2-spec/SPECIFICATION.md index 8801899..4a2a5ed 100644 --- a/mas-v0.2-spec/SPECIFICATION.md +++ b/mas-v0.2-spec/SPECIFICATION.md @@ -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. diff --git a/mas-v0.2-spec/conformance/addresses.json b/mas-v0.2-spec/conformance/addresses.json index 8012efd..6f04eac 100644 --- a/mas-v0.2-spec/conformance/addresses.json +++ b/mas-v0.2-spec/conformance/addresses.json @@ -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)" } ] diff --git a/mas-v0.2-spec/grammar/mas-v0.2.abnf b/mas-v0.2-spec/grammar/mas-v0.2.abnf index ed52e5c..005ee1f 100644 --- a/mas-v0.2-spec/grammar/mas-v0.2.abnf +++ b/mas-v0.2-spec/grammar/mas-v0.2.abnf @@ -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 diff --git a/mas-v0.2-spec/reference/mas_parser.py b/mas-v0.2-spec/reference/mas_parser.py index c90f7ea..a973077 100755 --- a/mas-v0.2-spec/reference/mas_parser.py +++ b/mas-v0.2-spec/reference/mas_parser.py @@ -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() diff --git a/mas_conformance/__init__.py b/mas_conformance/__init__.py index cb3c67a..30502d4 100644 --- a/mas_conformance/__init__.py +++ b/mas_conformance/__init__.py @@ -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 diff --git a/mas_conformance/addresses.json b/mas_conformance/addresses.json index 8012efd..6f04eac 100644 --- a/mas_conformance/addresses.json +++ b/mas_conformance/addresses.json @@ -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)" } ] diff --git a/mas_core/__init__.py b/mas_core/__init__.py index cb69b73..bcae9fb 100644 --- a/mas_core/__init__.py +++ b/mas_core/__init__.py @@ -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" diff --git a/pyproject.toml b/pyproject.toml index 3cd9b77..079106e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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"] diff --git a/tests/test_parser_conformance.py b/tests/test_parser_conformance.py index 9357800..da728ab 100644 --- a/tests/test_parser_conformance.py +++ b/tests/test_parser_conformance.py @@ -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() diff --git a/workplans/MAS-WP-0009-declare-v0-2-final.md b/workplans/MAS-WP-0009-declare-v0-2-final.md index 4098549..51269d0 100644 --- a/workplans/MAS-WP-0009-declare-v0-2-final.md +++ b/workplans/MAS-WP-0009-declare-v0-2-final.md @@ -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). diff --git a/workplans/MAS-WP-0014-finalize-mode-contract.md b/workplans/MAS-WP-0014-finalize-mode-contract.md new file mode 100644 index 0000000..d7d99ab --- /dev/null +++ b/workplans/MAS-WP-0014-finalize-mode-contract.md @@ -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.