mail-address-semantics/mas_modes/stubs/master.py
tegwick bdf200a600 MAS-WP-0011 finished: peer authentication in mode contract v0.1
CONTRACT.md section 7 (founder-reviewed): pairwise HMAC-SHA256 over
TIMESTAMP.BODY in MAS-Modes-* headers, 300 s window; master commands only
from the configured master; statuses only at the handoff's tokenized URL
from its peer; primed webhooks must name a known peer. mas_modes.signing,
signed contract.post, authenticating json_server, EmailMode/stubs/gateway
signing. conformance/auth.json vectors + 7 authorization flows.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5787@bnt-lap001
Assistant-Session: 5db26b82-138b-40cc-9856-4d5eb0f752fd
2026-10-19 11:25:24 +02:00

84 lines
3.6 KiB
Python

"""A minimal outside master (CMS / ticketing stand-in) for email in the slave role.
It records every message email sends it, issues case identifiers, and
drives email through the contract: assign, policy, mail.send, cancel.
Nothing happens on its own: `pump()` performs queued auto-assignments, so
tests stay deterministic and no handler calls back into email.
master = StubMaster({"email": key}, email_url="http://127.0.0.1:PORT/master", auto_assign=True)
base = serve(master.server()) # POST /events (signed by email)
"""
from __future__ import annotations
import itertools
import threading
from typing import Optional
from .. import message_errors
from ..contract import Idempotent, json_server, message, post, refuse
class StubMaster:
def __init__(self, keys: dict[str, bytes], email_url: Optional[str] = None,
auto_assign: bool = False, id_prefix: str = "EXT-", peer_id: str = "cms-1",
email_peer: str = "email"):
self.keys, self.peer_id, self.email_peer = keys, peer_id, email_peer
self.email_url = email_url
self.auto_assign = auto_assign
self.ids = (f"{id_prefix}{n}" for n in itertools.count(100))
self.received: list[dict] = []
self.pending_intakes: list[str] = []
self.lock = threading.Lock()
self._once = Idempotent()
def receive(self, msg: dict, peer: Optional[str] = None, rest: str = "") -> tuple[int, dict]:
if peer != self.email_peer:
return refuse("wrong-peer", "events come from email only", 403)
return self._once(msg, self._receive)
def _receive(self, msg):
errors = message_errors(msg)
if errors:
return refuse("schema", "; ".join(errors), 400)
with self.lock:
self.received.append(msg)
if msg["type"] == "case.intake":
self.pending_intakes.append(msg["intake_ref"])
return 200, {"ok": True}
def of_type(self, type_: str) -> list[dict]:
return [m for m in self.received if m["type"] == type_]
# -- commands to email -------------------------------------------------------
def command(self, type_: str, case: Optional[dict] = None, **fields) -> tuple[Optional[int], object]:
return post(self.email_url, message(type_, case, **fields),
(self.peer_id, self.keys[self.email_peer]))
def pump(self) -> list[tuple]:
"""Assign case ids to queued intakes (when auto_assign is on)."""
out = []
while self.auto_assign:
with self.lock:
if not self.pending_intakes:
break
ref = self.pending_intakes.pop(0)
out.append(self.assign(ref, next(self.ids)))
return out
def assign(self, intake_ref: str, resource_id: str):
return self.command("case.assign", intake_ref=intake_ref, resource_id=resource_id)
def policy(self, case: dict, autonomous_send: Optional[bool] = None, transfers=None):
return self.command("case.policy", case, autonomous_send=autonomous_send, transfers=transfers)
def send(self, case: dict, in_reply_to: str, body: str, subject: Optional[str] = None):
return self.command("mail.send", case, in_reply_to=in_reply_to, body=body, subject=subject)
def cancel(self, case: dict, handoff_id: str, reason: str = "timed-out"):
"""The master owns timeouts in the slave role: it ends a handoff itself."""
return self.command("handoff.cancel", case, handoff_id=handoff_id, reason=reason)
def server(self, host="127.0.0.1", port=0):
return json_server({"/events": self.receive}, self.keys, host=host, port=port)