From 52140cee9b0e551c906c4dd7f6f6d284645d6e4a Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 8 Oct 2026 00:14:14 +0200 Subject: [PATCH] Register with State Hub; add MAS v0.1 spec bundle, intent and reference-experiment workplan Co-Authored-By: Claude Opus 5.5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 623586@bnt-lap001 Assistant-Session: 10b30928-5b3b-4c11-9934-5ecca07367d8 --- .custodian-brief.md | 27 + .gitignore | 14 + AGENTS.md | 205 ++++ INTENT.md | 494 ++++++++++ SCOPE.md | 32 + mas-v0.1-spec/CONFORMANCE.md | 83 ++ mas-v0.1-spec/README.md | 31 + mas-v0.1-spec/SPECIFICATION.md | 428 +++++++++ mas-v0.1-spec/conformance/addresses.json | 315 +++++++ .../examples/mas-statute.example.json | 54 ++ mas-v0.1-spec/grammar/mas-v0.1.abnf | 55 ++ mas-v0.1-spec/reference/mas_parser.py | 129 +++ .../schema/mas-statute-v0.1.schema.json | 156 +++ workplans/MAS-WP-0001-statehub-bootstrap.md | 55 ++ workplans/MAS-WP-0002-reference-experiment.md | 892 ++++++++++++++++++ 15 files changed, 2970 insertions(+) create mode 100644 .custodian-brief.md create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 INTENT.md create mode 100644 SCOPE.md create mode 100644 mas-v0.1-spec/CONFORMANCE.md create mode 100644 mas-v0.1-spec/README.md create mode 100644 mas-v0.1-spec/SPECIFICATION.md create mode 100644 mas-v0.1-spec/conformance/addresses.json create mode 100644 mas-v0.1-spec/examples/mas-statute.example.json create mode 100644 mas-v0.1-spec/grammar/mas-v0.1.abnf create mode 100755 mas-v0.1-spec/reference/mas_parser.py create mode 100644 mas-v0.1-spec/schema/mas-statute-v0.1.schema.json create mode 100644 workplans/MAS-WP-0001-statehub-bootstrap.md create mode 100644 workplans/MAS-WP-0002-reference-experiment.md diff --git a/.custodian-brief.md b/.custodian-brief.md new file mode 100644 index 0000000..671de16 --- /dev/null +++ b/.custodian-brief.md @@ -0,0 +1,27 @@ + +# Custodian Brief - mail-address-semantics + +**Project:** mail-address-semantics +**Domain:** communication +**State Hub:** http://127.0.0.1:8000 +**Topic ID:** `36c7421b-c537-4723-bf75-42a3ebc6a1dc` + +## Open Workplans + +### Bootstrap State Hub integration + +Workplan file: `workplans/MAS-WP-0001-statehub-bootstrap.md` + +Open tasks: +- T01 - Review generated integration files +- T02 - Verify local developer workflow +- T03 - Seed first real workplan + +## Session Start + +1. Read `INTENT.md`, `SCOPE.md`, and `AGENTS.md`. +2. Check inbox: `GET /messages/?to_agent=mail-address-semantics&unread_only=true`. +3. Scan `workplans/`. +4. Update task statuses in workplan files as work progresses. + +Last generated: 2026-10-08 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8410865 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# state-hub: track .claude/rules +# Claude Code local state (track shared rules; ignore machine-specific files) +.claude/* +!.claude/rules/ +!.claude/rules/*.md + +# Python +__pycache__/ +*.py[cod] +.venv/ +*.egg-info/ +.pytest_cache/ +build/ +dist/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3f7eed6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,205 @@ +# mail-address-semantics — Agent Instructions + +## Repo Identity + +**Purpose:** Mail Address Semantics (MAS) v0.1 reference implementation: parser, statute discovery, pure policy, CLI, mail gateway and reference ticket service. + +**Domain:** communication +**Repo slug:** mail-address-semantics +**Topic ID:** `36c7421b-c537-4723-bf75-42a3ebc6a1dc` +**Workplan prefix:** `MAS-WP-` + +--- + +## State Hub Integration + +The Custodian State Hub tracks work across all domains. Codex uses HTTP REST and +the `statehub` CLI by default. MCP is opt-in because the current Codex MCP bridge +adds severe call latency; the full administrative MCP surface remains available +to clients that need it. + +| Context | URL | +|---------|-----| +| Local workstation | `http://127.0.0.1:8000` | +| Remote via tunnel | `http://127.0.0.1:18000` | +| Optional local edge relay | http://127.0.0.1:18080 | + +When an operator has enabled the edge relay, set API_BASE to the relay URL. +Queueable writes return an explicit queued receipt if the central hub is +unreachable. Treat that as pending local evidence, then ask the operator to run +statehub outbox status/replay after connectivity returns. + +Codex workspace-write sandboxes need network access enabled to reach the host's +loopback listener. Bootstrap this once with `make -C ~/state-hub configure-codex` +and restart Codex. The canonical REST health endpoint is `/state/health`, not +`/health`. If a sandboxed loopback probe fails, retry it with escalated execution +before declaring State Hub unavailable; a managed Codex permission profile may +still enforce isolated networking. Experimental MCP can be enabled explicitly +with `make -C ~/state-hub configure-codex WITH_MCP=1`. + +### Orient at session start + +```bash +# Offline brief — works without hub connection +cat .custodian-brief.md + +# Active workplans for this domain +curl -s "http://127.0.0.1:8000/workplans/?topic_id=36c7421b-c537-4723-bf75-42a3ebc6a1dc&status=active" \ + | python3 -m json.tool + +# Check inbox +curl -s "http://127.0.0.1:8000/messages/?to_agent=mail-address-semantics&unread_only=true" \ + | python3 -m json.tool +``` + +Mark a message read: +```bash +curl -s -X PATCH "http://127.0.0.1:8000/messages//read" \ + -H "Content-Type: application/json" -d '{}' +``` + +### Log progress (required at session close) + +```bash +curl -s -X POST http://127.0.0.1:8000/progress/ \ + -H "Content-Type: application/json" \ + -d '{ + "summary": "what was done", + "event_type": "note", + "author": "codex", + "workplan_id": "", + "task_id": "" + }' +``` + +Omit `workplan_id` / `task_id` when not applicable. + +### Update task status + +```bash +curl -s -X PATCH "http://127.0.0.1:8000/tasks/" \ + -H "Content-Type: application/json" \ + -d '{"status": "progress"}' +# values: wait | todo | progress | done | cancel +``` + +### Flag a task for human review + +```bash +curl -s -X PATCH "http://127.0.0.1:8000/tasks/" \ + -H "Content-Type: application/json" \ + -d '{"needs_human": true, "intervention_note": "reason"}' +``` + +--- + +## Session Protocol + +**Start:** +1. `cat .custodian-brief.md` — domain goal and open workplans (offline-safe) +2. Check inbox: `GET /messages/?to_agent=mail-address-semantics&unread_only=true`; mark read +3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks +4. Check human-needed tasks: `GET /tasks/?needs_human=true` + +**During work:** +- Update task statuses in workplan files as tasks progress +- Record significant decisions via `POST /decisions/` + +**Close:** +1. Update workplan file task statuses to reflect progress +2. If finishing a workplan: hand off **residuals** as live work records first + (intake with `origin: residual` + `origin_ref: `, or a next workplan / + decision / engagement). Do not park leftovers only in prose or `SCOPE.md`. + Canon: `the-custodian/canon/standards/work-record-types_v0.1.md` § Residuals. +3. Log: `POST /progress/` with a summary of what changed (name handoff ids) +4. After workplan file changes, run: + ```bash + uv run --project ~/repo-manager rmgr sync --path . --push + ``` + This assigns only missing deterministic identifiers, verifies the pushed + Forgejo commit and `primary/railliance01`, then requests one central + reconciliation. A queued receipt is pending evidence; rerun after + connectivity returns. Use `statehub fix-consistency` for a separate deep audit. + +--- + +{CREDENTIAL_ROUTING} + + + + +--- + +## Workplan Convention (ADR-001) + +Work items originate as files in this repo — not in the hub. The hub is a +read/cache/index layer that rebuilds from files. + +**File location:** `workplans/MAS-WP-NNNN-.md` + +**Archived location:** finished workplans may move to +`workplans/archived/YYMMDD-MAS-WP-NNNN-.md`. The `YYMMDD` prefix is +the completion/archive date; the frontmatter `id` does not change. + +**Ad Hoc Tasks:** small opportunistic fixes discovered during a session use +`workplans/ADHOC-YYYY-MM-DD.md`, workplan id +`MAS-WP-ADHOC-YYYY-MM-DD`, and task ids +`MAS-WP-ADHOC-YYYY-MM-DD-T01`, etc. `MAS-WP` includes its final `-WP` +token. Unqualified historic `ADHOC-*` ids are grandfathered and must not be +copied into new records. Use this only for low-risk work completed directly; +create a normal workplan for anything needing analysis, design, approval, +dependencies, or multiple phases. + +**Frontmatter:** + +```yaml +--- +id: MAS-WP-NNNN +type: workplan +title: "..." +domain: communication +repo: mail-address-semantics +status: proposed | ready | active | blocked | backlog | finished | archived +owner: codex +topic_slug: ... +created: "YYYY-MM-DD" +updated: "YYYY-MM-DD" +state_hub_workstream_id: "" # deterministic UUIDv5; managed by Repo Manager +--- +``` + +Use `proposed` for a new draft, `ready` after review against current repo +state, and `finished` after implementation. `stalled` and `needs_review` are +derived health labels, not frontmatter statuses. + +**Terminology:** workplan is the fleet term; `workstream` appears only in legacy +API/MCP/frontmatter bridges until `STATE-WP-0069` retires them — see +`the-custodian/canon/standards/workplan-terminology-fleet_v0.1.md`. + +**Task block format** (one per `##` section): + +``` +## Task Title + +` ` `task +id: MAS-WP-NNNN-T01 +status: wait | todo | progress | done | cancel +priority: high | medium | low +state_hub_task_id: "" # deterministic UUIDv5; managed by Repo Manager +` ` ` + +Task description text. +``` + +Status progression: `todo` → `progress` → `done`; use `wait` for waiting/blocked work and `cancel` for stopped work. + +**Residuals when finishing:** actionable leftovers become live work records +before `status: finished` — usually an intake (`origin: residual`, +`origin_ref: MAS-WP-NNNN`) or a spawned workplan. Residual is a *role*, +not a kind. Fleet list lives on State Hub, not in `SCOPE.md`. + +To create a new workplan: +1. Write the file following the format above +2. Run `uv run --project ~/repo-manager rmgr sync --path . --push`. +3. Run `statehub fix-consistency` only when a separate deep audit is needed. diff --git a/INTENT.md b/INTENT.md new file mode 100644 index 0000000..5ca0c97 --- /dev/null +++ b/INTENT.md @@ -0,0 +1,494 @@ +# MAS — Mail Address Semantics + +## Intent + +Build the smallest practical implementation of **Mail Address Semantics (MAS) v0.1** that can prove whether the specification is useful in real email workflows. + +The project should demonstrate that ordinary email addresses can carry limited, human-readable and machine-parseable semantics for: + +- opaque organizational targets, +- resource addressing, +- facets, +- optional interaction preferences, + +without modifying SMTP and without turning addresses into credentials, commands, contracts, or payment instructions. + +The implementation must preserve the core architectural boundary of MAS: + +> **Parsing is local and deterministic. Discovery is networked. Policy is pure. Delivery remains external.** + +The first implementation should be deliberately narrow. Its purpose is not to build a full MAS ecosystem, but to test the convention against real messages, real routing, resource lifecycle, malformed input, and namespace metadata. + +--- + +## Core hypothesis + +MAS is valuable if the following can be made both simple and reliable: + +```text +ticket!callback@great-service.example +``` + +can express: + +> initiate a ticket interaction with callback as the preferred continuation mode + +and result in a stable resource address: + +```text +ticket=GS-41872@great-service.example +``` + +that can subsequently be used to correlate email with that ticket without embedding application identifiers in subject lines. + +If this lifecycle works cleanly through ordinary email infrastructure, MAS has demonstrated its core value. + +--- + +## Design principles + +### 1. The parser does not use the network + +Parsing operates only on the address syntax. + +It must: + +- validate canonical MAS syntax, +- produce an explicit parse tree, +- identify which MAS profile the syntax requires, +- never perform statute discovery, +- never infer person, actor, or function kind from `.`, `-`, or `_`. + +Example: + +```text +ticket!callback +``` + +parses structurally as an interaction-bearing target and records: + +```text +required_profile = P2 +``` + +Whether the addressed namespace supports P2 is decided later. + +--- + +### 2. Discovery returns facts or typed failure + +MAS discovery is responsible only for retrieving and validating the exact-host statute. + +Given: + +```text +anna.mueller@acme.coulomb.social +``` + +discovery targets: + +```text +https://acme.coulomb.social/.well-known/mas +``` + +It must not walk parent domains implicitly. + +Discovery should return either: + +```text +ValidatedStatute +``` + +or a typed failure such as: + +```text +NotFound +HttpFailure +CrossHostRedirect +SchemaInvalid +NamespaceMismatch +InheritanceCycle +UnsupportedVersion +``` + +Failure to discover MAS metadata does not invalidate the email address. + +--- + +### 3. Policy is pure + +Policy evaluation takes: + +```text +parse tree ++ +validated statute ++ +optional local registry +``` + +and produces semantic interpretation. + +Policy performs no network access. + +This allows the same policy implementation to be exercised entirely through the conformance corpus. + +--- + +### 4. No semantic inference from naming punctuation + +The following remain naming hints only: + +```text +. +- +_ +``` + +Therefore: + +```text +bernd.worsch@ +research-companion@ +invoice_ingest@ +``` + +may visually suggest person, actor, and function, but machine classification requires statute or registry metadata. + +No statute means: + +```text +kind = opaque +``` + +not: + +```text +kind = person-like +``` + +--- + +### 5. Resource semantics are the primary proof + +The initial implementation should strongly support: + +```text +type=id@ +``` + +Resource addresses must remain usable as correlation identifiers while remaining independent of authorization. + +Example: + +```text +ticket=GS-41872@ +``` + +identifies the ticket communication endpoint. + +It does not grant permission to inspect or mutate ticket GS-41872. + +--- + +### 6. Resource addressability outlives mutability + +Closed resources remain addressable. + +A closed ticket may: + +- archive later email, +- reject mutation, +- return lifecycle information, +- create a linked follow-up ticket. + +It must not become mutable merely because mail can still reach its address. + +--- + +### 7. Facets degrade safely + +A facet: + +```text +ticket=GS-41872+comments@ +``` + +may be stripped by existing email infrastructure. + +If so, the remaining address: + +```text +ticket=GS-41872@ +``` + +still identifies the base resource. + +Facet preservation is desirable but never assumed. + +--- + +### 8. Interaction remains a preference + +The P2 syntax: + +```text +ticket!callback@ +``` + +requests that the addressed endpoint continue through `callback`. + +It does not imply: + +- who initiates the callback, +- that callback is available, +- that callback has been accepted, +- an SLA, +- a command. + +The application decides whether and how to satisfy the request. + +--- + +### 9. Economic syntax remains closed + +The characters: + +```text +$ +^ +``` + +remain reserved only. + +No implementation work on price, reward, conditions, offers, contracts, or settlement belongs in this phase. + +--- + +## Initial architecture + +The first repository should contain only the pieces needed for the experiment. + +```text +mas/ +├── mas_core/ +│ ├── parser +│ ├── model +│ ├── discovery +│ ├── policy +│ └── lint +├── cli/ +│ └── mas +├── conformance/ +│ ├── addresses +│ ├── statutes +│ └── properties +├── examples/ +│ ├── statute/ +│ └── ticket_service/ +├── gateway/ +│ └── smtp_or_webhook_to_http/ +└── docs/ +``` + +The existing MAS v0.1 specification bundle is the normative basis. + +--- + +## Initial components + +### `mas-core` + +Python reference implementation. + +Responsibilities: + +- parse canonical MAS syntax, +- expose structured parse objects, +- identify required profile, +- validate statutes, +- discover exact-host statutes, +- apply equivalence rules, +- evaluate semantics from parse tree plus statute, +- lint statutes. + +The first implementation should be clear and auditable rather than clever. + +--- + +### `mas-cli` + +Primary human interface. + +Initial commands: + +```text +mas parse ADDRESS +mas explain ADDRESS +mas discover HOST +mas lint-statute FILE +mas test +``` + +`mas explain` should clearly distinguish: + +```text +syntactic facts +discovered namespace facts +policy-derived semantics +unknowns +``` + +A discovery failure must remain visible. + +--- + +### `mas-conformance` + +The conformance corpus should become a first-class package rather than an incidental test directory. + +It should contain: + +- positive syntax cases, +- negative syntax cases, +- parser expectations, +- generator expectations, +- statute validation cases, +- discovery policy cases, +- property and boundary cases. + +Every future language implementation should consume this corpus unchanged. + +--- + +### Static statute + +The first service does not need a statute server framework. + +A static file served at: + +```text +/.well-known/mas +``` + +is sufficient. + +The point is to test discovery semantics, not middleware. + +--- + +### Generic mail gateway + +The first mail integration should translate inbound mail into a simple HTTP request. + +It may receive mail via: + +- a local SMTP listener, or +- a provider-style inbound webhook. + +Its responsibility should be minimal: + +```text +email recipient +→ MAS parse +→ statute/policy resolution +→ normalized HTTP event +``` + +It should not contain ticket-specific business logic. + +--- + +### Reference ticket service + +A deliberately small example application proving the canonical lifecycle. + +Required behavior: + +#### Initial interaction + +Mail to: + +```text +ticket!callback@great-service.example +``` + +creates a ticket. + +The service generates: + +```text +ticket=GS-41872@great-service.example +``` + +#### Subsequent correlation + +Mail sent to: + +```text +ticket=GS-41872@great-service.example +``` + +is associated with that ticket without depending on the subject line. + +#### Closed lifecycle + +After the ticket is closed, its address remains routable. + +Later mail must not silently reopen or mutate the resource unless application policy explicitly allows it. + +--- + +## Explicit non-goals + +The initial implementation does not include: + +- TypeScript reference library, +- Go implementation, +- Postfix policy daemon, +- central MAS registry, +- mail client plugin, +- browser extension, +- address composer, +- payment semantics, +- electronic signatures, +- generalized service marketplace, +- production identity federation, +- automatic DNS-parent inheritance. + +These should be considered only after the reference experiment succeeds. + +--- + +## Success criteria + +The first experiment succeeds if: + +1. the parser conforms to the shared fixture corpus; +2. parser behavior requires no network access; +3. discovery produces validated statute or typed failure; +4. policy evaluation is deterministic and offline; +5. `mas explain` never invents semantics on discovery failure; +6. the ticket lifecycle works end to end; +7. ticket identifiers no longer need to be encoded in subject lines; +8. closed resources remain addressable without becoming mutable; +9. facet loss degrades safely; +10. the experiment exposes no need to change SMTP itself. + +--- + +## Longer-term direction + +If the experiment validates the concept, MAS may later grow into: + +- additional language libraries, +- infrastructure gateways, +- mail client affordances, +- signed semantic metadata, +- richer interaction discovery, +- economic profiles, +- service-market coordination. + +Those extensions should remain subordinate to the core principle: + +> **MAS should add useful semantics to email without making ordinary email depend on MAS.** \ No newline at end of file diff --git a/SCOPE.md b/SCOPE.md new file mode 100644 index 0000000..cda680d --- /dev/null +++ b/SCOPE.md @@ -0,0 +1,32 @@ +# SCOPE + +> This file was generated by `statehub register`. Refine it as the repository +> boundaries become clearer. + +## One-liner + +Mail Address Semantics (MAS) v0.1 reference implementation: parser, statute discovery, pure policy, CLI, mail gateway and reference ticket service. + +## Core Idea + +mail-address-semantics exists to provide the capability described in INTENT.md. + +## In Scope + +- Maintain the repository's primary implementation. +- Keep docs, tests, and operational metadata current. + +## Out of Scope + +- Own unrelated adjacent systems. +- Make irreversible operational decisions without human approval. + +## Current State + +- Status: active; implementation and stability should be verified by the repo agent. + +## Getting Oriented + +- Start with: INTENT.md +- Agent instructions: AGENTS.md +- Workplans: workplans/ diff --git a/mas-v0.1-spec/CONFORMANCE.md b/mas-v0.1-spec/CONFORMANCE.md new file mode 100644 index 0000000..b717a84 --- /dev/null +++ b/mas-v0.1-spec/CONFORMANCE.md @@ -0,0 +1,83 @@ +# MAS v0.1 Conformance + +## 1. Test surfaces + +An implementation can claim any of the following independently: + +- `MAS-P1-PARSER` +- `MAS-P2-PARSER` +- `MAS-V0.1-GENERATOR` +- `MAS-V0.1-STATUTE` + +## 2. Parser requirements + +A parser claiming `MAS-P1-PARSER` MUST produce the expected result for every +fixture whose `profile` is `p1`. + +A parser claiming `MAS-P2-PARSER` MUST produce the expected result for every +fixture whose `profile` is `p2`, and MUST also satisfy `MAS-P1-PARSER`. + +The `parsed` object in the fixture file is normative for the test corpus. + +A parser MUST NOT infer `person`, `actor`, or `function` from `.`, `-`, `_`. + +## 3. Generator requirements + +A generator: + +- MUST emit only grammar-valid canonical ASCII local-parts; +- MUST NOT emit empty target, resource type, resource id, facet, or interaction; +- MUST emit `+` before `!`; +- MUST NOT emit more than one `+` or one `!`; +- MUST NOT emit `$` or `^`; +- MUST NOT emit both `+` and `!` in v0.1; +- MUST keep the encoded SMTP local-part at or below 64 octets. + +Fixtures with `generator_allowed: false` may still be accepted by a P2 parser, +but a conforming generator MUST NOT create them. + +## 4. Statute requirements + +A statute implementation MUST validate discovery documents using +`schema/mas-statute-v0.1.schema.json`. + +It MUST NOT apply: + +- case folding, +- dot folding, +- address classification, +- resource-type semantics, +- interaction-mode semantics, + +unless the exact-host statute successfully validates and declares them. + +## 5. Discovery failure + +Failure to retrieve or validate a statute: + +- does not invalidate the email address; +- does invalidate any claim that MAS namespace semantics were discovered; +- falls back to ordinary email handling plus exact local configuration. + +## 6. Inheritance + +Implementations are not required to implement `inherits`. + +Implementations that do implement it MUST: + +- fetch the explicit URI only; +- detect cycles; +- treat inherited data as defaults; +- never inherit explicit `addresses` registry entries; +- let the child override inherited resource types and interaction modes. + +## 7. Test command + +The bundled reference parser can be run with: + +```bash +python reference/mas_parser.py --self-test +``` + +The reference parser is non-normative; the grammar, specification, schema, +and fixtures are normative for this draft. diff --git a/mas-v0.1-spec/README.md b/mas-v0.1-spec/README.md new file mode 100644 index 0000000..38d2c63 --- /dev/null +++ b/mas-v0.1-spec/README.md @@ -0,0 +1,31 @@ +# Mail Address Semantics (MAS) v0.1 — Specification Bundle + +This bundle turns the MAS v0.1 concept into an implementable draft. + +## Contents + +- `SPECIFICATION.md` — normative protocol/convention text +- `grammar/mas-v0.1.abnf` — canonical ASCII local-part grammar +- `schema/mas-statute-v0.1.schema.json` — statute discovery document schema +- `examples/mas-statute.example.json` — example statute +- `CONFORMANCE.md` — parser/generator/statute conformance rules +- `conformance/addresses.json` — positive and negative address fixtures +- `reference/mas_parser.py` — small non-normative reference parser + +## Profiles + +- **P1 / Naming + Resource:** naming hints, `=` resource binding, `+` facet +- **P2 / Interaction:** optional `!` interaction preference +- **P3 / Economic:** reserved only; `$` and `^` have no v0.1 semantics + +The normative machine grammar is deliberately ASCII-only. SMTPUTF8/Unicode +addresses may exist, but Unicode MAS semantics are not part of the canonical +v0.1 grammar. + +## Discovery status + +`/.well-known/mas` is specified here as the intended discovery location. +RFC 8615 requires new well-known URI suffixes to be registered for general +Internet use. Until such registration exists, this path should be considered +a draft/controlled-deployment convention rather than an IANA-registered +Internet-wide well-known URI. diff --git a/mas-v0.1-spec/SPECIFICATION.md b/mas-v0.1-spec/SPECIFICATION.md new file mode 100644 index 0000000..1e2f3ec --- /dev/null +++ b/mas-v0.1-spec/SPECIFICATION.md @@ -0,0 +1,428 @@ +# Mail Address Semantics (MAS) v0.1 + +## 1. Status and normative language + +This document defines MAS v0.1. + +The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHOULD**, **SHOULD NOT**, +**RECOMMENDED**, **MAY**, and **OPTIONAL** are to be interpreted as described +in BCP 14 when, and only when, they appear in uppercase. + +MAS is an overlay on ordinary email addressing. It does not replace SMTP, +establish identity, grant authority, define delivery topology, or create +contractual commitments. + +## 2. Scope + +MAS v0.1 standardizes: + +1. non-structural naming hints for people, actors, and functions; +2. structured resource targets using `=`; +3. facets using `+`; +4. an optional interaction profile using `!`; +5. exact-host statute discovery; +6. conservative equivalence behavior; +7. parser and generator conformance. + +`$` and `^` are reserved. They have no MAS v0.1 meaning. + +## 3. Profiles + +### 3.1 Profile P1 — Naming + Resource + +P1 is the base v0.1 profile. + +- `.` MAY be used as a person-style naming separator. +- `-` MAY be used as an actor-style naming separator. +- `_` MAY be used as a function-style naming separator. +- `=` structurally binds a resource type to a resource identifier. +- `+` structurally adds one facet. + +`.`, `-`, and `_` never classify an address by themselves. + +### 3.2 Profile P2 — Interaction + +P2 extends P1 with `!`. + +`TARGET!MODE` requests that the addressed endpoint continue the interaction +using `MODE` as the preferred interaction mode. + +The address does not state which party initiates a non-email leg and does not +constitute acceptance. Acceptance is external to the address. + +### 3.3 Profile P3 — Economic + +P3 is not defined in v0.1. + +`$` and `^` are reserved so that later profiles may define consideration and +conditions without creating v0.1 parsing ambiguity. + +## 4. Canonical ASCII local-part + +The normative v0.1 machine grammar is the ABNF in +`grammar/mas-v0.1.abnf`. + +Canonical MAS v0.1 local-parts are unquoted ASCII dot-atom-like strings. +Quoted local-parts and SMTPUTF8 local-parts are outside the canonical v0.1 +machine grammar. + +A MAS-aware implementation MAY transport such non-canonical email addresses +as ordinary email, but MUST NOT claim that they are canonical MAS v0.1 +addresses. + +### 4.1 Target + +A target is non-empty. + +A target without `=` is opaque. Its semantic kind MUST NOT be inferred from +`.`, `-`, or `_`. + +If a target contains `=`, the target is a resource target: + +- text before the first `=` is `resource-type`; +- text after the first `=` is `resource-id`; +- neither may be empty; +- subsequent `=` characters are resource-id data. + +Examples: + + ticket=PR42 + invoice=RE=2026-4711 + +The second example has resource type `invoice` and resource id +`RE=2026-4711`. + +### 4.2 Facet + +A facet is introduced by `+`. + +`+` MAY occur at most once and its value MUST be non-empty. + + ticket=PR42+comments + +The base target remains meaningful if a mail system strips the facet. MAS +therefore treats facet loss as graceful degradation: + + ticket=PR42+comments -> ticket=PR42 + +This is a semantic design property, not a preservation guarantee. + +### 4.3 Interaction + +An interaction preference is introduced by `!`. + +`!` MAY occur at most once and its value MUST be non-empty. + +P2 examples: + + ticket!callback + ticket=PR42!web + +Canonical operator order is: + + TARGET [+FACET] [!INTERACTION] + +Non-canonical order is invalid MAS syntax. A conforming parser MUST reject it +and MUST NOT silently reorder it. + +### 4.4 Composition rule + +A parser implementing P2 MUST be able to parse the canonical combined form: + + TARGET+FACET!INTERACTION + +A v0.1 generator MUST NOT emit both `+` and `!` in the same generated +local-part. Implementations requiring both semantics SHOULD move one of them +to external metadata. + +This deliberately makes receivers slightly more tolerant than generators +while retaining one canonical parse. + +### 4.5 Reserved characters + +Within canonical MAS v0.1 structured values: + + + ! $ ^ + +are reserved. + +MAS v0.1 defines no escaping mechanism. + +A percent-looking sequence such as `%2B` is ordinary data in v0.1 and MUST NOT +be percent-decoded by the MAS parser. + +## 5. Naming semantics + +The following are style conventions only: + + bernd.worsch@example.org person-like + research-companion@example.org actor-like + invoice_ingest@example.org function-like + +They are not classifier rules. + +Examples such as these remain opaque without statute metadata: + + anne-marie.schmidt@example.org + support@example.org + happy-helper.v2@example.org + +A domain registry or statute metadata determines semantic kind. + +## 6. Resource semantics + +A resource address identifies a communication endpoint associated with a +resource. + + ticket=PR42@example.org + +It does not grant access to, mutation of, or knowledge about the resource. + +Resource addressability and resource mutability are independent. + +A closed resource MAY remain addressable. A receiver MAY archive the mail, +reject mutation, return lifecycle information, or create a linked follow-up +resource. + +### 6.1 Resource types + +A statute SHOULD enumerate resource types it recognizes. + +Unknown resource types MUST NOT acquire application semantics solely because +they use `=` syntax. + +### 6.2 Resource identifiers + +Resource identifiers are correlation identifiers, not credentials. + +Predictable identifiers are enumerable. Deployments SHOULD prefer +non-guessable external identifiers when addresses are widely forwarded or +exposed and enumeration is undesirable. + +High entropy does not transform an identifier into an authorization token. + +## 7. Interaction semantics + +The recommended P2 initiation pattern is: + + ticket!callback@great-service.example + +followed by creation or disclosure of a resource: + + ticket=GS-41872@great-service.example + +The first address expresses interaction intent. The second names the resource +created or selected by the service. + +`!callback` is a preference/request, not a command, guarantee, SLA, or proof +of acceptance. + +## 8. Address equivalence + +MAS does not impose case folding, dot folding, alias folding, or Unicode +normalization in the absence of a successfully discovered statute. + +If no statute is available, comparison is exact over the presented canonical +ASCII local-part. + +A statute MAY declare: + +- `exact` local-part comparison; +- `ascii-insensitive` local-part comparison; +- dot folding for explicitly named address kinds or registry entries. + +Dot folding MUST NOT be assumed by default. + +A-label/U-label domain processing follows the underlying IDNA-aware HTTPS and +DNS stack; MAS does not invent a second IDN mapping algorithm. + +## 9. Unicode + +SMTPUTF8 allows non-ASCII local-parts, but MAS v0.1 does not define a +canonical Unicode local-part grammar. + +A statute may declare `unicode_local_part: true` for experimental/private +extensions. Such addresses MUST NOT be advertised as canonical MAS v0.1 +machine syntax. + +A future MAS version may define NFC normalization and Unicode comparison +rules. v0.1 deliberately avoids doing so. + +## 10. Statute discovery + +The intended discovery URI is: + + https://HOST/.well-known/mas + +where `HOST` is the exact domain appearing after `@`. + +For: + + anna.mueller@acme.coulomb.social + +the lookup host is: + + acme.coulomb.social + +A client MUST NOT walk to parent domains implicitly. + +### 10.1 Registration caveat + +RFC 8615 requires applications minting a new well-known URI suffix to +register it for general Internet use. + +Therefore `/.well-known/mas` is a candidate MAS discovery location in this +draft. General public deployment SHOULD wait for the required registration. +Controlled deployments MAY use it by agreement. + +### 10.2 Retrieval + +A statute-aware client: + +1. constructs the HTTPS URI for the exact host; +2. performs an HTTPS GET; +3. requires a successful 2xx response; +4. requires a document conforming to the v0.1 statute schema; +5. verifies that `namespace` equals the queried host after normal domain-name + canonicalization; +6. otherwise treats discovery as failed. + +Discovery failure does not make the email address invalid. It means MAS +semantics are unconfirmed. + +### 10.3 Redirects + +Clients SHOULD NOT follow cross-origin redirects for statute discovery. + +Same-origin redirects MAY be followed. + +A cross-origin policy transfer requires an explicit future delegation +mechanism and is not defined in v0.1. + +### 10.4 Inheritance + +No parent-domain inheritance is implicit. + +A child statute MAY contain an `inherits` URI. v0.1 inheritance is +**defaults-only**: + +- the parent document is fetched and validated independently; +- the child remains authoritative for its exact namespace; +- child values override inherited defaults; +- address registry entries are never inherited; +- resource types and interaction modes MAY be inherited; +- an inheritance cycle is invalid. + +Implementations MAY choose not to implement inheritance. If they do not, +they MUST ignore `inherits` rather than walking DNS parents. + +## 11. Statute document + +The normative schema is: + + schema/mas-statute-v0.1.schema.json + +The document describes: + +- namespace and version; +- enabled profiles; +- accepted resource types; +- accepted interaction modes; +- equivalence policy; +- optional explicit address classification; +- optional inheritance. + +The statute is metadata, not an authorization database. + +## 12. Deliverability and rewriting + +RFC-validity is weaker than ecosystem compatibility. + +Mailing lists, SRS, VERP, gateways, web forms, validators, and security +products may reject or rewrite otherwise legal local-parts. + +Deployments MUST NOT assume preservation of MAS syntax through arbitrary +rewriting. + +`+` is intentionally degradation-friendly: loss of a facet leaves the base +target. + +`!` is intended primarily for cooperating environments where all relevant +systems are known to preserve it. + +## 13. Length + +An SMTP local-part is limited to 64 octets. + +Generators MUST enforce the underlying transport limit after encoding. + +Because the budget is small, v0.1 permits at most one facet and one +interaction, and generators MUST NOT emit both simultaneously. + +## 14. Security + +1. MAS identifiers are not credentials. +2. Resource knowledge grants no authority. +3. Interaction selectors are not commands. +4. Dynamic addresses MUST define handling for bounces, unknown resources, + expired resources, verification traffic, and abuse. +5. Receivers MUST avoid backscatter behavior. +6. Syntax-derived classifications MUST NOT substitute for statute metadata. +7. `$` and `^` MUST NOT be rendered as payment/contract semantics in v0.1. +8. Authentication remains external to MAS. +9. A statute endpoint is security-relevant configuration and SHOULD be + protected with the same care as other origin-wide metadata. + +## 15. Conformance classes + +### 15.1 P1 parser + +A P1 parser: + +- validates canonical ASCII syntax; +- parses resource binding and facet; +- rejects `!`, `$`, and `^` as MAS operators; +- does not classify opaque target kind by punctuation. + +### 15.2 P2 parser + +A P2 parser additionally parses `!`. + +### 15.3 Generator + +A generator: + +- emits only canonical operator order; +- emits no empty component; +- emits no reserved operator within a value; +- emits at most one `+`; +- emits at most one `!`; +- MUST NOT emit both `+` and `!` in v0.1; +- enforces the SMTP local-part octet limit. + +### 15.4 Statute-aware implementation + +A statute-aware implementation additionally validates the discovery document +against the normative schema and applies only explicitly declared equivalence +and classification rules. + +## 16. Reserved future work + +Future specifications may define: + +- Unicode canonical grammar; +- escaping; +- signed statutes and signed semantic assertions; +- interaction negotiation; +- service discovery; +- economic request/offer/acceptance/settlement; +- condition tokens; +- IANA registrations. + +## 17. One-sentence definition + +**Mail Address Semantics is a voluntary convention for naming people, actors +and functions, and for addressing resources, facets, and optional interaction +preferences through ordinary email addresses while keeping classification, +authority, authentication, equivalence, and delivery implementation explicit +and external to the address syntax.** diff --git a/mas-v0.1-spec/conformance/addresses.json b/mas-v0.1-spec/conformance/addresses.json new file mode 100644 index 0000000..00afc69 --- /dev/null +++ b/mas-v0.1-spec/conformance/addresses.json @@ -0,0 +1,315 @@ +[ + { + "id": "p1-001", + "local_part": "bernd.worsch", + "profile": "p1", + "valid": true, + "parsed": { + "target": "bernd.worsch", + "target_kind": "opaque", + "facet": null, + "interaction": null + } + }, + { + "id": "p1-002", + "local_part": "anne-marie.schmidt", + "profile": "p1", + "valid": true, + "parsed": { + "target": "anne-marie.schmidt", + "target_kind": "opaque", + "facet": null, + "interaction": null + } + }, + { + "id": "p1-003", + "local_part": "support", + "profile": "p1", + "valid": true, + "parsed": { + "target": "support", + "target_kind": "opaque", + "facet": null, + "interaction": null + } + }, + { + "id": "p1-004", + "local_part": "invoice-reviewer_backup", + "profile": "p1", + "valid": true, + "parsed": { + "target": "invoice-reviewer_backup", + "target_kind": "opaque", + "facet": null, + "interaction": null + } + }, + { + "id": "p1-005", + "local_part": "happy-helper.v2", + "profile": "p1", + "valid": true, + "parsed": { + "target": "happy-helper.v2", + "target_kind": "opaque", + "facet": null, + "interaction": null + } + }, + { + "id": "p1-010", + "local_part": "ticket=PR42", + "profile": "p1", + "valid": true, + "parsed": { + "target": "ticket=PR42", + "target_kind": "resource", + "resource_type": "ticket", + "resource_id": "PR42", + "facet": null, + "interaction": null + } + }, + { + "id": "p1-011", + "local_part": "invoice=RE=2026-4711", + "profile": "p1", + "valid": true, + "parsed": { + "target": "invoice=RE=2026-4711", + "target_kind": "resource", + "resource_type": "invoice", + "resource_id": "RE=2026-4711", + "facet": null, + "interaction": null + } + }, + { + "id": "p1-012", + "local_part": "ticket=PR42+comments", + "profile": "p1", + "valid": true, + "parsed": { + "target": "ticket=PR42", + "target_kind": "resource", + "resource_type": "ticket", + "resource_id": "PR42", + "facet": "comments", + "interaction": null + } + }, + { + "id": "p1-013", + "local_part": "bernd.worsch+github", + "profile": "p1", + "valid": true, + "parsed": { + "target": "bernd.worsch", + "target_kind": "opaque", + "facet": "github", + "interaction": null + } + }, + { + "id": "p1-020", + "local_part": "=PR42", + "profile": "p1", + "valid": false, + "error": "empty resource type" + }, + { + "id": "p1-021", + "local_part": "ticket=", + "profile": "p1", + "valid": false, + "error": "empty resource id" + }, + { + "id": "p1-022", + "local_part": "ticket=PR42+", + "profile": "p1", + "valid": false, + "error": "empty facet" + }, + { + "id": "p1-023", + "local_part": "ticket=PR42+comments+attachments", + "profile": "p1", + "valid": false, + "error": "multiple facet operators" + }, + { + "id": "p1-024", + "local_part": "ticket=PR42$4.99", + "profile": "p1", + "valid": false, + "error": "reserved operator" + }, + { + "id": "p1-025", + "local_part": "ticket=PR42^1h", + "profile": "p1", + "valid": false, + "error": "reserved operator" + }, + { + "id": "p1-026", + "local_part": ".bernd", + "profile": "p1", + "valid": false, + "error": "leading dot" + }, + { + "id": "p1-027", + "local_part": "bernd.", + "profile": "p1", + "valid": false, + "error": "trailing dot" + }, + { + "id": "p1-028", + "local_part": "bernd..worsch", + "profile": "p1", + "valid": false, + "error": "repeated dot" + }, + { + "id": "p1-029", + "local_part": "ticket=PR.42", + "profile": "p1", + "valid": true, + "parsed": { + "target": "ticket=PR.42", + "target_kind": "resource", + "resource_type": "ticket", + "resource_id": "PR.42", + "facet": null, + "interaction": null + } + }, + { + "id": "p1-030", + "local_part": "ticket=PR%2B42", + "profile": "p1", + "valid": true, + "parsed": { + "target": "ticket=PR%2B42", + "target_kind": "resource", + "resource_type": "ticket", + "resource_id": "PR%2B42", + "facet": null, + "interaction": null + }, + "note": "%2B is literal data; MAS v0.1 does not percent-decode" + }, + { + "id": "p2-001", + "local_part": "ticket!callback", + "profile": "p2", + "valid": true, + "parsed": { + "target": "ticket", + "target_kind": "opaque", + "facet": null, + "interaction": "callback" + }, + "generator_allowed": true + }, + { + "id": "p2-002", + "local_part": "ticket=PR42!web", + "profile": "p2", + "valid": true, + "parsed": { + "target": "ticket=PR42", + "target_kind": "resource", + "resource_type": "ticket", + "resource_id": "PR42", + "facet": null, + "interaction": "web" + }, + "generator_allowed": true + }, + { + "id": "p2-003", + "local_part": "ticket=PR42+comments!callback", + "profile": "p2", + "valid": true, + "parsed": { + "target": "ticket=PR42", + "target_kind": "resource", + "resource_type": "ticket", + "resource_id": "PR42", + "facet": "comments", + "interaction": "callback" + }, + "generator_allowed": false + }, + { + "id": "p2-010", + "local_part": "ticket!", + "profile": "p2", + "valid": false, + "error": "empty interaction" + }, + { + "id": "p2-011", + "local_part": "ticket!callback!chat", + "profile": "p2", + "valid": false, + "error": "multiple interaction operators" + }, + { + "id": "p2-012", + "local_part": "ticket!callback+comments", + "profile": "p2", + "valid": false, + "error": "non-canonical operator order" + }, + { + "id": "p1-031", + "local_part": "ticket!callback", + "profile": "p1", + "valid": false, + "error": "interaction not enabled in P1" + }, + { + "id": "p2-013", + "local_part": "ticket=PR42+comments!callback$4.99", + "profile": "p2", + "valid": false, + "error": "reserved operator" + }, + { + "id": "p2-014", + "local_part": "ticket=PR42+comments!call.back", + "profile": "p2", + "valid": true, + "parsed": { + "target": "ticket=PR42", + "target_kind": "resource", + "resource_type": "ticket", + "resource_id": "PR42", + "facet": "comments", + "interaction": "call.back" + }, + "generator_allowed": false + }, + { + "id": "p1-040", + "local_part": "\"ticket=PR42\"", + "profile": "p1", + "valid": false, + "error": "quoted local-part outside canonical MAS v0.1" + }, + { + "id": "p1-041", + "local_part": "tïcket=PR42", + "profile": "p1", + "valid": false, + "error": "non-ASCII outside canonical MAS v0.1" + } +] diff --git a/mas-v0.1-spec/examples/mas-statute.example.json b/mas-v0.1-spec/examples/mas-statute.example.json new file mode 100644 index 0000000..a0adc21 --- /dev/null +++ b/mas-v0.1-spec/examples/mas-statute.example.json @@ -0,0 +1,54 @@ +{ + "mas_version": "0.1", + "namespace": "coulomb.social", + "profiles": { + "p1": true, + "p2": true + }, + "resource_types": { + "ticket": { + "enabled": true, + "description": "Customer-service or work ticket", + "identifier_policy": "non-guessable-preferred" + }, + "invoice": { + "enabled": true, + "description": "Invoice communication resource", + "identifier_policy": "opaque" + } + }, + "interaction_modes": { + "callback": { + "enabled": true, + "description": "Continue by a voice callback" + }, + "web": { + "enabled": true, + "description": "Continue in a web interface" + }, + "chat": { + "enabled": true, + "description": "Continue in a chat interface" + } + }, + "equivalence": { + "local_part_case": "exact", + "dot_folding": false + }, + "unicode_local_part": false, + "addresses": { + "support": { + "kind": "function", + "description": "General support intake" + }, + "bernd.worsch": { + "kind": "person" + }, + "research-companion": { + "kind": "actor" + }, + "invoice_ingest": { + "kind": "function" + } + } +} diff --git a/mas-v0.1-spec/grammar/mas-v0.1.abnf b/mas-v0.1-spec/grammar/mas-v0.1.abnf new file mode 100644 index 0000000..b0feedc --- /dev/null +++ b/mas-v0.1-spec/grammar/mas-v0.1.abnf @@ -0,0 +1,55 @@ +; Mail Address Semantics (MAS) v0.1 +; Canonical ASCII local-part grammar +; +; This grammar describes the local-part only. Domain syntax is provided by +; the underlying email specifications. +; +; P1 accepts: target [facet] +; P2 accepts: target [facet] [interaction] +; +; Generators MUST NOT emit both facet and interaction in v0.1 even though +; P2 parsers accept the combined canonical form. + +mas-local-p1 = target [facet] +mas-local-p2 = target [facet] [interaction] + +target = resource-target / opaque-target + +resource-target = resource-type "=" resource-id +opaque-target = dot-string + +resource-type = resource-type-char *resource-type-char + +; Resource identifiers may contain additional "=" characters as data. +resource-id = resource-id-segment *("." resource-id-segment) +resource-id-segment = resource-id-char *resource-id-char + +facet = "+" value +interaction = "!" value + +value = value-segment *("." value-segment) +value-segment = value-char *value-char + +; dot-string prevents leading, trailing, or repeated ".". +dot-string = atom *("." atom) +atom = atom-char *atom-char + +; Conservative canonical ASCII subset derived from RFC 5322 atext while +; reserving MAS operators + ! $ ^ and "=" where structural. +; +; ALPHA / DIGIT are imported from RFC 5234 core rules. + +atom-char = ALPHA / DIGIT / + "#" / "%" / "&" / "'" / "*" / "-" / "/" / "?" / + "_" / "`" / "{" / "|" / "}" / "~" + +resource-type-char = ALPHA / DIGIT / "-" / "_" + +resource-id-char = atom-char / "=" +value-char = atom-char / "=" + +; Explicitly excluded from canonical values: +; "+" "!" "$" "^" "@" +; +; Quoted local-parts are outside canonical MAS v0.1. +; SMTPUTF8 local-parts are outside canonical MAS v0.1 machine grammar. diff --git a/mas-v0.1-spec/reference/mas_parser.py b/mas-v0.1-spec/reference/mas_parser.py new file mode 100755 index 0000000..6b3f8e6 --- /dev/null +++ b/mas-v0.1-spec/reference/mas_parser.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +"""Non-normative MAS v0.1 canonical ASCII reference parser.""" + +import argparse +import json +from pathlib import Path + +ATOM = set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#%&'*-/ ?_`{|}~".replace(" ", "")) +RTYPE = set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") +RESERVED = set("+!$^") + +def _valid_dot_string(s, allowed): + if not s or s.startswith(".") or s.endswith(".") or ".." in s: + return False + return all((c == "." or c in allowed) for c in s) + +def parse(local_part, profile="p1"): + if not isinstance(local_part, str) or not local_part: + raise ValueError("empty local-part") + try: + local_part.encode("ascii") + except UnicodeEncodeError: + raise ValueError("non-ASCII outside canonical MAS v0.1") + + if '"' in local_part or "\\" in local_part or "@" in local_part: + raise ValueError("quoted/escaped/full address outside canonical local-part grammar") + if "$" in local_part or "^" in local_part: + raise ValueError("reserved operator") + + if local_part.count("+") > 1: + raise ValueError("multiple facet operators") + if local_part.count("!") > 1: + raise ValueError("multiple interaction operators") + + plus = local_part.find("+") + bang = local_part.find("!") + if plus >= 0 and bang >= 0 and bang < plus: + raise ValueError("non-canonical operator order") + if profile == "p1" and bang >= 0: + raise ValueError("interaction not enabled in P1") + if profile not in ("p1", "p2"): + raise ValueError("unknown profile") + + interaction = None + s = local_part + if bang >= 0: + interaction = s[bang+1:] + if not interaction: + raise ValueError("empty interaction") + s = s[:bang] + if not _valid_dot_string(interaction, ATOM | set("=")): + raise ValueError("invalid interaction") + + facet = None + plus = s.find("+") + if plus >= 0: + facet = s[plus+1:] + if not facet: + raise ValueError("empty facet") + s = s[:plus] + if not _valid_dot_string(facet, ATOM | set("=")): + raise ValueError("invalid facet") + + target = s + if not target: + raise ValueError("empty target") + + out = { + "target": target, + "target_kind": "opaque", + "facet": facet, + "interaction": interaction, + } + + if "=" in target: + rtype, rid = target.split("=", 1) + if not rtype: + raise ValueError("empty resource type") + if not rid: + raise ValueError("empty resource id") + if not all(c in RTYPE for c in rtype): + raise ValueError("invalid resource type") + if not _valid_dot_string(rid, ATOM | set("=")): + raise ValueError("invalid resource id") + out.update({ + "target_kind": "resource", + "resource_type": rtype, + "resource_id": rid, + }) + else: + if not _valid_dot_string(target, ATOM): + raise ValueError("invalid opaque target") + + if len(local_part.encode("ascii")) > 64: + raise ValueError("local-part exceeds 64 octets") + + return out + +def self_test(): + here = Path(__file__).resolve().parents[1] + fixtures = json.loads((here / "conformance" / "addresses.json").read_text()) + failures = [] + for f in fixtures: + try: + got = parse(f["local_part"], f["profile"]) + if not f["valid"]: + failures.append((f["id"], "expected invalid", got)) + elif got != f["parsed"]: + failures.append((f["id"], "parse mismatch", {"got":got, "expected":f["parsed"]})) + except ValueError as e: + if f["valid"]: + failures.append((f["id"], "unexpected invalid", str(e))) + if failures: + print(json.dumps(failures, indent=2)) + raise SystemExit(1) + print(f"PASS: {len(fixtures)} MAS v0.1 fixtures") + +if __name__ == "__main__": + ap = argparse.ArgumentParser() + ap.add_argument("local_part", nargs="?") + ap.add_argument("--profile", choices=["p1","p2"], default="p1") + ap.add_argument("--self-test", action="store_true") + args = ap.parse_args() + if args.self_test: + self_test() + elif args.local_part: + print(json.dumps(parse(args.local_part, args.profile), indent=2)) + else: + ap.print_help() diff --git a/mas-v0.1-spec/schema/mas-statute-v0.1.schema.json b/mas-v0.1-spec/schema/mas-statute-v0.1.schema.json new file mode 100644 index 0000000..68dc104 --- /dev/null +++ b/mas-v0.1-spec/schema/mas-statute-v0.1.schema.json @@ -0,0 +1,156 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.invalid/mas/schema/mas-statute-v0.1.schema.json", + "title": "Mail Address Semantics v0.1 Statute", + "type": "object", + "additionalProperties": false, + "required": [ + "mas_version", + "namespace", + "profiles", + "equivalence" + ], + "properties": { + "mas_version": { + "const": "0.1" + }, + "namespace": { + "type": "string", + "minLength": 1, + "maxLength": 253, + "description": "Exact host governed by this statute, in its canonical DNS/IDNA presentation form." + }, + "inherits": { + "type": "string", + "format": "uri", + "pattern": "^https://", + "description": "Optional explicit defaults-only parent statute URI. No DNS parent walking is implied." + }, + "profiles": { + "type": "object", + "additionalProperties": false, + "required": [ + "p1", + "p2" + ], + "properties": { + "p1": { + "const": true + }, + "p2": { + "type": "boolean" + } + } + }, + "resource_types": { + "type": "object", + "default": {}, + "propertyNames": { + "pattern": "^[a-z0-9][a-z0-9_-]*$" + }, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "identifier_policy": { + "type": "string", + "enum": [ + "opaque", + "non-guessable-preferred", + "sequential-allowed" + ] + } + } + } + }, + "interaction_modes": { + "type": "object", + "default": {}, + "propertyNames": { + "pattern": "^[A-Za-z0-9_-]+$" + }, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 500 + } + } + } + }, + "equivalence": { + "type": "object", + "additionalProperties": false, + "required": [ + "local_part_case", + "dot_folding" + ], + "properties": { + "local_part_case": { + "type": "string", + "enum": [ + "exact", + "ascii-insensitive" + ] + }, + "dot_folding": { + "type": "boolean" + } + } + }, + "unicode_local_part": { + "type": "boolean", + "default": false, + "description": "Experimental/private extension only; not canonical v0.1 machine syntax." + }, + "addresses": { + "type": "object", + "default": {}, + "propertyNames": { + "pattern": "^[A-Za-z0-9#%&'*\\-/=?_`{|}~.]+$" + }, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "person", + "actor", + "function", + "group", + "system", + "opaque" + ] + }, + "description": { + "type": "string", + "maxLength": 500 + } + } + } + } + } +} diff --git a/workplans/MAS-WP-0001-statehub-bootstrap.md b/workplans/MAS-WP-0001-statehub-bootstrap.md new file mode 100644 index 0000000..c18ca24 --- /dev/null +++ b/workplans/MAS-WP-0001-statehub-bootstrap.md @@ -0,0 +1,55 @@ +--- +id: MAS-WP-0001 +type: workplan +title: "Bootstrap State Hub integration" +domain: communication +repo: mail-address-semantics +status: active +owner: codex +topic_slug: coulomb-social +created: "2026-10-08" +updated: "2026-10-08" +--- + +# Bootstrap State Hub integration + +Mail Address Semantics (MAS) v0.1 reference implementation: parser, statute discovery, pure policy, CLI, mail gateway and reference ticket service. + +## Review Generated Integration Files + +```task +id: MAS-WP-0001-T01 +status: todo +priority: high +``` + +Review `INTENT.md`, `SCOPE.md`, `AGENTS.md`, and `.custodian-brief.md`. +Replace generated placeholders with repo-specific facts where needed. + +## Verify Local Developer Workflow + +```task +id: MAS-WP-0001-T02 +status: todo +priority: high +``` + +Identify the repo's install, test, lint, build, and run commands. Add or refine +those commands in the agent instructions so future coding sessions can verify +changes confidently. + +## Seed First Real Workplan + +```task +id: MAS-WP-0001-T03 +status: done +priority: medium +``` + +Create the first implementation workplan for the repository's most important +next change. After workplan file updates, run the sync locally from this repo +checkout: + +```bash +statehub fix-consistency +``` diff --git a/workplans/MAS-WP-0002-reference-experiment.md b/workplans/MAS-WP-0002-reference-experiment.md new file mode 100644 index 0000000..a4c69cf --- /dev/null +++ b/workplans/MAS-WP-0002-reference-experiment.md @@ -0,0 +1,892 @@ +--- +id: MAS-WP-0002 +type: workplan +title: "MAS v0.1 reference experiment" +domain: communication +repo: mail-address-semantics +status: ready +owner: claude +topic_slug: coulomb-social +created: "2026-10-08" +updated: "2026-10-08" +--- + +# MAS v0.1 reference experiment + +### Objective + +Build one runnable reference experiment for MAS v0.1. + +The experiment should validate the full path: + +```text +address +→ parse +→ discovery +→ policy +→ mail gateway +→ ticket resource +→ subsequent resource-addressed mail +``` + +The plan deliberately avoids parallel language implementations and infrastructure-specific integrations until the model has survived one complete implementation. + +--- + +## Milestone 0 — Establish the implementation baseline + +```task +id: MAS-WP-0002-T01 +status: todo +priority: high +``` + +### Goal + +Turn the specification bundle into executable project inputs. + +### Work + +Import or vendor: + +```text +SPECIFICATION.md +grammar/mas-v0.1.abnf +schema/mas-statute-v0.1.schema.json +conformance/addresses.json +``` + +Establish the repository structure. + +Define Python package boundaries: + +```text +mas_core.model +mas_core.parser +mas_core.discovery +mas_core.policy +mas_core.statute +mas_core.lint +``` + +Define an explicit version: + +```text +MAS 0.1 +implementation 0.1.0-dev +``` + +### Deliverable + +A repository where: + +```text +pytest +``` + +runs the existing fixture corpus. + +--- + +## Milestone 1 — Core parser and model + +```task +id: MAS-WP-0002-T02 +status: todo +priority: high +``` + +### Goal + +Implement the specification without network or namespace policy. + +### Core model + +Define immutable objects such as: + +```text +MasAddress +Target +OpaqueTarget +ResourceTarget +Facet +Interaction +RequiredProfile +``` + +Example: + +```text +ticket=PR42+comments!callback +``` + +should parse to approximately: + +```text +MasAddress + target: + ResourceTarget + type = ticket + id = PR42 + facet = comments + interaction = callback + required_profile = P2 +``` + +Critically: + +```text +ticket!callback +``` + +must parse successfully in the maximal parser and report: + +```text +required_profile = P2 +``` + +The parser must not require knowledge of whether a particular namespace enables P2. + +### Parser APIs + +Recommended: + +```python +parse_local_part(value) -> ParsedLocalPart +parse_address(value) -> ParsedAddress +validate_profile(parsed, profile) -> ValidationResult +``` + +Avoid coupling syntax parsing to statute policy. + +### Property cases to add + +Extend beyond the existing fixtures. + +#### Length boundaries + +Test: + +```text +63 octets +64 octets +65 octets +``` + +The transport limit applies after encoding. + +#### Additional `=` + +Validate: + +```text +ticket==PR42 +``` + +as: + +```text +resource_type = ticket +resource_id = =PR42 +``` + +because only the first `=` is structural. + +#### Reserved operators + +Ensure: + +```text +$ +^ +``` + +always fail canonical v0.1 parsing. + +#### Registry independence + +Ensure a registry key such as: + +```text +support +``` + +is legal while: + +```text +support+foo +``` + +is not interpreted as a registry key lookup for the full faceted address. + +Registry lookup should operate on the base target. + +### Exit criteria + +- all existing fixtures pass; +- new property cases pass; +- parser performs zero I/O; +- parser does not infer person/actor/function kind. + +--- + +## Milestone 2 — Statute model and linting + +```task +id: MAS-WP-0002-T03 +status: todo +priority: high +``` + +### Goal + +Make namespace semantics explicit and verifiable. + +### Work + +Implement statute loading and JSON Schema validation. + +Add semantic validation beyond the schema. + +Checks should include: + +#### Namespace consistency + +A statute fetched for: + +```text +acme.coulomb.social +``` + +must declare: + +```text +namespace = acme.coulomb.social +``` + +or fail with: + +```text +NamespaceMismatch +``` + +#### P2 consistency + +Reject or lint: + +```text +profiles.p2 = false +``` + +together with enabled interaction modes. + +#### Resource type validity + +Resource type names must themselves satisfy MAS resource-type syntax. + +#### Registry key validity + +Explicit address registry keys must refer to base opaque targets only. + +For v0.1, registry keys should not contain: + +```text ++ +! +$ +^ +``` + +#### Inheritance cycles + +Detect: + +```text +A → B → A +``` + +and longer cycles. + +#### Child precedence + +Where inheritance is supported: + +```text +child overrides inherited defaults +``` + +Explicit address registry entries must not inherit. + +### CLI + +Implement: + +```text +mas lint-statute FILE +``` + +Output should distinguish: + +```text +schema error +semantic error +warning +``` + +### Exit criteria + +A malformed but schema-valid statute can still be rejected by semantic linting. + +--- + +## Milestone 3 — Exact-host discovery + +```task +id: MAS-WP-0002-T04 +status: todo +priority: high +``` + +### Goal + +Resolve namespace metadata without contaminating parsing or policy. + +### API + +Recommended: + +```python +discover(host) -> DiscoveryResult +``` + +with successful result: + +```text +ValidatedStatute +``` + +and typed failures such as: + +```text +NotFound +TransportFailure +InvalidContentType +SchemaInvalid +NamespaceMismatch +CrossHostRedirect +InheritanceCycle +UnsupportedVersion +``` + +### Redirect rule + +Before adding caching, define redirect semantics precisely. + +Initial rule: + +> **Follow same-host HTTPS redirects only. Reject cross-host redirects.** + +A redirect must not transfer MAS authority to another hostname. + +Scheme downgrade to HTTP must be rejected. + +### Parent domains + +Never perform implicit lookup such as: + +```text +acme.coulomb.social +→ coulomb.social +``` + +Inheritance occurs only through an explicit statute declaration. + +### Caching + +Add only minimal caching after behavior is correct. + +Cache successful statutes separately from failures. + +Do not let cache behavior hide typed discovery failures. + +### Exit criteria + +Discovery is deterministic enough to test against a local HTTP fixture server. + +--- + +## Milestone 4 — Policy resolver + +```task +id: MAS-WP-0002-T05 +status: todo +priority: high +``` + +### Goal + +Combine syntax and validated metadata without network access. + +### API + +Conceptually: + +```python +resolve( + parsed_address, + statute, + local_registry=None, +) -> AddressSemantics +``` + +Policy must remain pure. + +### Resolution examples + +With no statute: + +```text +bernd.worsch@example.org +``` + +resolves only to syntactic information: + +```text +target = bernd.worsch +kind = opaque +``` + +It must not become: + +```text +kind = person +``` + +from punctuation. + +With statute registry: + +```text +addresses: + bernd.worsch: + kind: person +``` + +the resolver may produce: + +```text +kind = person +``` + +For: + +```text +ticket=PR42 +``` + +and a statute declaring `ticket`: + +```text +kind = resource +resource_type = ticket +resource_id = PR42 +``` + +### Profile validation + +For: + +```text +ticket!callback +``` + +if statute says: + +```text +p2 = false +``` + +policy should return a typed semantic failure such as: + +```text +ProfileNotEnabled +``` + +The syntax remains valid. + +### Exit criteria + +The complete policy test suite runs with network disabled. + +--- + +## Milestone 5 — `mas` CLI + +```task +id: MAS-WP-0002-T06 +status: todo +priority: high +``` + +### Goal + +Provide the main inspection surface for developers and operators. + +### Commands + +#### Parse + +```text +mas parse ADDRESS +``` + +Shows syntax only. + +#### Discover + +```text +mas discover HOST +``` + +Shows validated statute or typed failure. + +#### Explain + +```text +mas explain ADDRESS +``` + +Shows three distinct layers: + +```text +Syntax +Namespace discovery +Semantic resolution +``` + +Example failure: + +```text +Address: + bernd.worsch@example.org + +Syntax: + target: bernd.worsch + required profile: P1 + +Discovery: + no valid MAS statute found + +Semantics: + kind: opaque +``` + +This is preferable to displaying a guessed “person”. + +#### Lint statute + +```text +mas lint-statute FILE +``` + +#### Run conformance suite + +```text +mas test +``` + +### Exit criteria + +A human can understand exactly why an address received its interpretation. + +--- + +## Milestone 6 — Static statute deployment + +```task +id: MAS-WP-0002-T07 +status: todo +priority: medium +``` + +### Goal + +Test real exact-host discovery. + +Serve one validated static statute at: + +```text +/.well-known/mas +``` + +No framework-specific statute server should be built yet. + +The deployment should advertise only the features needed by the reference service. + +Example: + +```text +P1 = enabled +P2 = enabled + +resource_types: + ticket + +interaction_modes: + callback +``` + +### Exit criteria + +`mas discover` retrieves the live/local test statute and validates namespace consistency. + +--- + +## Milestone 7 — Generic mail-to-HTTP gateway + +```task +id: MAS-WP-0002-T08 +status: todo +priority: medium +``` + +### Goal + +Bridge ordinary inbound mail into an application-neutral MAS event. + +### Input + +Either: + +```text +SMTP +``` + +or: + +```text +provider inbound webhook +``` + +Choose whichever produces the smallest reliable prototype. + +### Gateway behavior + +For each inbound recipient: + +1. parse address; +2. discover statute; +3. resolve policy; +4. produce normalized event; +5. POST event to configured HTTP application endpoint. + +Example event: + +```json +{ + "recipient": "ticket=GS-41872@example.org", + "mas": { + "profile": "P1", + "kind": "resource", + "resource_type": "ticket", + "resource_id": "GS-41872" + }, + "message": { + "from": "customer@example.net", + "subject": "The problem happened again" + } +} +``` + +The gateway must not contain ticket-specific logic. + +### Exit criteria + +The same gateway could theoretically route another MAS resource type without code changes. + +--- + +## Milestone 8 — Reference ticket service + +```task +id: MAS-WP-0002-T09 +status: todo +priority: medium +``` + +### Goal + +Prove the canonical lifecycle. + +### Flow A — create + +Customer sends: + +```text +ticket!callback@great-service.example +``` + +The service: + +1. receives a normalized MAS event; +2. verifies P2 is enabled; +3. creates ticket; +4. records callback as requested interaction mode; +5. generates resource address: + +```text +ticket=GS-41872@great-service.example +``` + +6. replies using that resource address. + +The subject does not require a ticket prefix. + +--- + +### Flow B — correlate + +Customer later sends or replies to: + +```text +ticket=GS-41872@great-service.example +``` + +The service: + +1. resolves resource type and id; +2. locates GS-41872; +3. associates the new message with that resource. + +No subject-line parsing is required. + +--- + +### Flow C — closed resource + +Ticket GS-41872 is closed. + +Mail later arrives at: + +```text +ticket=GS-41872@great-service.example +``` + +The address remains valid. + +The service must apply explicit lifecycle policy. + +For the prototype, choose one behavior such as: + +```text +archive message ++ +return "ticket closed" response +``` + +It must not silently mutate or reopen the ticket. + +--- + +### Flow D — unknown resource + +Mail arrives at: + +```text +ticket=DOES-NOT-EXIST@great-service.example +``` + +The service must handle this deliberately and without generating abusive backscatter. + +--- + +## Milestone 9 — Experiment review + +```task +id: MAS-WP-0002-T10 +status: todo +priority: medium +``` + +### Goal + +Decide whether MAS v0.1 survives contact with implementation. + +Review: + +#### Syntax + +Did the parser remain simple? + +#### Discovery + +Was exact-host metadata sufficient? + +#### Policy + +Could all inference remain explicit? + +#### Email infrastructure + +Which characters or address forms were rewritten or rejected? + +#### Resource addressing + +Did resource addresses actually improve correlation? + +#### Subject lines + +Could application identifiers genuinely disappear from subjects? + +#### Lifecycle + +Did closed resources remain understandable? + +#### Interaction + +Was `!callback` useful or did it require more negotiation semantics? + +#### Security + +Did any address accidentally start behaving like a capability? + +--- + +## Decision gate + +Only after the reference experiment should the project decide whether to build: + +```text +TypeScript implementation +Go implementation +Postfix/Exim integration +mail-client inspector +address composer +signed statutes +P3 economic profile +``` + +Each addition should answer a demonstrated need from the experiment. + +--- + +## Initial execution order + +The shortest path is: + +```text +1. mas-core parser/model +2. expanded conformance corpus +3. statute validator/linter +4. exact-host discovery +5. pure policy resolver +6. mas CLI +7. static statute +8. generic inbound gateway +9. reference ticket service +10. experiment review +``` + +The governing rule for this phase is: + +> **One implementation, one lifecycle, one real email path.**