- 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
38 lines
1.6 KiB
Python
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)
|