mail-address-semantics/mas_core/generator.py
tegwick 59815701c1 Implement MAS v0.1 reference experiment (MAS-WP-0002 M0-M8, M9 first pass)
- 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
2026-10-08 00:24:43 +02:00

38 lines
1.6 KiB
Python

"""Canonical MAS v0.1 local-part generator.
Generators are stricter than parsers: they never emit both a facet and an
interaction, and they enforce the 64-octet SMTP limit after encoding.
"""
from __future__ import annotations
from typing import Optional
from .parser import MasSyntaxError, parse_local_part
def generate_local_part(target: str, *, facet: Optional[str] = None,
interaction: Optional[str] = None) -> str:
if facet is not None and interaction is not None:
raise MasSyntaxError("generator-facet-and-interaction",
"v0.1 generators MUST NOT emit both '+' and '!'")
s = target
if facet is not None:
s += "+" + facet
if interaction is not None:
s += "!" + interaction
parsed = parse_local_part(s) # enforces grammar, emptiness, reserved ops, length
if str(parsed.target) != target or (parsed.facet and parsed.facet.value) != facet \
or (parsed.interaction and parsed.interaction.mode) != interaction:
raise MasSyntaxError("generator-component", "a component contains a MAS operator")
return s
def resource_local_part(resource_type: str, resource_id: str, *, facet: Optional[str] = None,
interaction: Optional[str] = None) -> str:
if not resource_type or not resource_id:
raise MasSyntaxError("generator-empty", "resource type and id must be non-empty")
if "=" in resource_type:
raise MasSyntaxError("invalid-resource-type", "resource type must not contain '='")
return generate_local_part(f"{resource_type}={resource_id}", facet=facet,
interaction=interaction)