MAS-WP-0010 T01-T05: mode contract v0.1, schemas, callback stub, email as master
mas-modes-v0.1-spec/ (contract, message + gateway-event schemas, transition table, message fixtures, statute) vendored into mas_modes. EmailMode implements the master role over the reference ticket service (MasterStore): handoff, negotiation by email, statuses in the thread, timeouts, resume by email, principle 10 shared via mas_modes.mailer. 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
This commit is contained in:
parent
fb36aaef81
commit
e91cd01a02
22 changed files with 4327 additions and 13 deletions
|
|
@ -31,6 +31,7 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|||
from typing import Callable, Optional
|
||||
|
||||
from mas_core.generator import resource_local_part
|
||||
from mas_modes.mailer import refusal
|
||||
|
||||
RESOURCE_TYPE = "ticket"
|
||||
INTAKE_TARGET = "ticket"
|
||||
|
|
@ -147,12 +148,8 @@ class TicketService:
|
|||
|
||||
def _reply(self, tid, mail_from, msg, subject, body) -> bool:
|
||||
to = msg.get("from")
|
||||
auto = (msg.get("auto_submitted") or "no").lower() != "no"
|
||||
bulk = (msg.get("precedence") or "").lower() in ("bulk", "list", "junk")
|
||||
if not to or auto or bulk:
|
||||
return False # never auto-reply to automated mail
|
||||
if self.require_dmarc_pass and (msg.get("authentication") or {}).get("dmarc") != "pass":
|
||||
return False # unauthenticated sender: keep the ticket, send nothing
|
||||
if refusal(msg, self.require_dmarc_pass):
|
||||
return False # automated or unauthenticated sender: keep the ticket, send nothing
|
||||
if not subject.lower().startswith("re:"):
|
||||
subject = "Re: " + subject
|
||||
self.db.execute("INSERT INTO outbox (ticket_id, mail_from, mail_to, subject, body, in_reply_to)"
|
||||
|
|
@ -179,6 +176,26 @@ class TicketService:
|
|||
with smtplib.SMTP(*self.smtp) as s:
|
||||
s.send_message(m)
|
||||
|
||||
# -- MasterStore (mas_modes.email_mode): email as master for tickets -------------
|
||||
|
||||
def open_case(self, response: dict) -> Optional[str]:
|
||||
return response.get("ticket_id") if response.get("status") in ("created", "correlated") else None
|
||||
|
||||
def case_address(self, tid: str) -> str:
|
||||
row = self.db.execute("SELECT domain FROM tickets WHERE id=?", (tid,)).fetchone()
|
||||
return f"{resource_local_part(RESOURCE_TYPE, tid)}@{row['domain']}"
|
||||
|
||||
def record(self, tid: str, kind: str, text: str) -> None:
|
||||
"""A mode status or update in the ticket thread."""
|
||||
with self.lock, self.db:
|
||||
self.db.execute("INSERT INTO messages (ticket_id, disposition, body) VALUES (?,?,?)",
|
||||
(tid, kind, text))
|
||||
|
||||
def reply(self, tid: str, msg: dict, subject: str, body: str) -> bool:
|
||||
"""Mail the participant of `msg` from the ticket address, under principle 10."""
|
||||
with self.lock, self.db:
|
||||
return self._reply(tid, self.case_address(tid), msg, subject, body)
|
||||
|
||||
# -- admin / inspection ----------------------------------------------------------
|
||||
|
||||
def close(self, tid: str) -> bool:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue