Register with State Hub; add MAS v0.1 spec bundle, intent and reference-experiment workplan

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 623586@bnt-lap001
Assistant-Session: 10b30928-5b3b-4c11-9934-5ecca07367d8
This commit is contained in:
tegwick 2026-10-08 00:14:14 +02:00
parent 41e2b1dfd1
commit 52140cee9b
15 changed files with 2970 additions and 0 deletions

27
.custodian-brief.md Normal file
View file

@ -0,0 +1,27 @@
<!-- custodian-brief: generated by statehub register; fix-consistency may replace this file -->
# 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

14
.gitignore vendored Normal file
View file

@ -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/

205
AGENTS.md Normal file
View file

@ -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/<id>/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": "<uuid>",
"task_id": "<uuid>"
}'
```
Omit `workplan_id` / `task_id` when not applicable.
### Update task status
```bash
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
-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/<task_id>" \
-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: <WP-id>`, 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}
<!-- REPO-AGENTS-EXTENSIONS -->
<!-- Append repo-specific agent instructions below this marker.
The state-hub template sync preserves content after this line. -->
---
## 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-<slug>.md`
**Archived location:** finished workplans may move to
`workplans/archived/YYMMDD-MAS-WP-NNNN-<slug>.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: "<uuid>" # 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: "<uuid>" # 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.

494
INTENT.md Normal file
View file

@ -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.**

32
SCOPE.md Normal file
View file

@ -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/

View file

@ -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.

31
mas-v0.1-spec/README.md Normal file
View file

@ -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.

View file

@ -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.**

View file

@ -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"
}
]

View file

@ -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"
}
}
}

View file

@ -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.

View file

@ -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()

View file

@ -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
}
}
}
}
}
}

View file

@ -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
```

View file

@ -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.**