- mas_core: P2-maximal parser with required_profile, generator, statute schema + semantic lint + defaults-only inheritance, exact-host discovery with typed failures and same-origin redirects, pure policy resolver - mas_conformance: vendored spec fixtures (drift-tested) + property and statute cases - mas CLI: parse / explain / discover / lint-statute / test - mas-gateway: generic mail -> MAS event -> HTTP (MTA pipe + webhook) - examples: static great-service.example statute, reference ticket service - docs/experiment-review.md: first-pass findings; live MTA test still open 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
131 lines
3.4 KiB
Python
131 lines
3.4 KiB
Python
"""Immutable syntax model for MAS v0.1 addresses.
|
|
|
|
These objects carry syntactic facts only. Nothing here encodes a semantic
|
|
kind (person, actor, function): that requires statute or registry metadata.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from enum import Enum
|
|
from typing import Optional, Union
|
|
|
|
|
|
class RequiredProfile(str, Enum):
|
|
"""The minimum MAS profile whose grammar the syntax requires."""
|
|
|
|
P1 = "P1"
|
|
P2 = "P2"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class OpaqueTarget:
|
|
value: str
|
|
|
|
kind = "opaque"
|
|
|
|
def __str__(self) -> str:
|
|
return self.value
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ResourceTarget:
|
|
resource_type: str
|
|
resource_id: str
|
|
|
|
kind = "resource"
|
|
|
|
def __str__(self) -> str:
|
|
return f"{self.resource_type}={self.resource_id}"
|
|
|
|
|
|
Target = Union[OpaqueTarget, ResourceTarget]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Facet:
|
|
value: str
|
|
|
|
def __str__(self) -> str:
|
|
return self.value
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Interaction:
|
|
mode: str
|
|
|
|
def __str__(self) -> str:
|
|
return self.mode
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ParsedLocalPart:
|
|
"""A canonical MAS v0.1 local-part, parsed with the maximal (P2) grammar."""
|
|
|
|
raw: str
|
|
target: Target
|
|
facet: Optional[Facet] = None
|
|
interaction: Optional[Interaction] = None
|
|
|
|
@property
|
|
def required_profile(self) -> RequiredProfile:
|
|
return RequiredProfile.P2 if self.interaction is not None else RequiredProfile.P1
|
|
|
|
@property
|
|
def base(self) -> str:
|
|
"""The base target: what remains if a mail system strips the facet."""
|
|
return str(self.target)
|
|
|
|
@property
|
|
def generator_allowed(self) -> bool:
|
|
"""v0.1 generators MUST NOT emit both a facet and an interaction."""
|
|
return not (self.facet is not None and self.interaction is not None)
|
|
|
|
def without_facet(self) -> "ParsedLocalPart":
|
|
return ParsedLocalPart(raw=_render(self.target, None, self.interaction),
|
|
target=self.target, interaction=self.interaction)
|
|
|
|
def to_fixture_dict(self) -> dict:
|
|
"""Shape used by the conformance corpus (`parsed` objects)."""
|
|
out = {
|
|
"target": str(self.target),
|
|
"target_kind": self.target.kind,
|
|
"facet": self.facet.value if self.facet else None,
|
|
"interaction": self.interaction.mode if self.interaction else None,
|
|
}
|
|
if isinstance(self.target, ResourceTarget):
|
|
out["resource_type"] = self.target.resource_type
|
|
out["resource_id"] = self.target.resource_id
|
|
return out
|
|
|
|
def to_dict(self) -> dict:
|
|
out = self.to_fixture_dict()
|
|
out["required_profile"] = self.required_profile.value
|
|
out["base"] = self.base
|
|
out["generator_allowed"] = self.generator_allowed
|
|
return out
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ParsedAddress:
|
|
local: ParsedLocalPart
|
|
domain: str
|
|
|
|
@property
|
|
def required_profile(self) -> RequiredProfile:
|
|
return self.local.required_profile
|
|
|
|
def __str__(self) -> str:
|
|
return f"{self.local.raw}@{self.domain}"
|
|
|
|
def to_dict(self) -> dict:
|
|
return {"address": str(self), "domain": self.domain, **self.local.to_dict()}
|
|
|
|
|
|
def _render(target: Target, facet: Optional[Facet], interaction: Optional[Interaction]) -> str:
|
|
s = str(target)
|
|
if facet is not None:
|
|
s += "+" + facet.value
|
|
if interaction is not None:
|
|
s += "!" + interaction.mode
|
|
return s
|