- EmailMode slave role: master-issued case ids, events to the master, sends only on instruction or autonomous policy, transfers blocked or to a primed webhook, timeouts left to the master; principle 10 refusals reported back. - conformance/flows.json (13 flows) + mas_modes.runner; mas test runs the mode corpus (172/172). - SCP-8 / v0.2 section 7.1: !MODE requests a handoff defined by the mode contract. docs/reuse-surface.md. - Deploy image copies mas_modes (ticket service imports its 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
426 lines
9.6 KiB
JSON
426 lines
9.6 KiB
JSON
{
|
|
"description": "Scripted flows (CONTRACT.md section 7). Each runs against a fresh email side with the callback stub and, for 'slave', a stub outside master that assigns EXT-100, EXT-101, ... The statute is statute.json. Placeholders: {case_id}, {message_ref}, {handoff_id} come from the latest response that carried them; {callback} is the stub's handoff URL. Master role config: ticket, modes {callback}, fields phone/time, timeout 3600 s. Slave role config: case, defaults autonomous_send false, transfers block, overridden by 'config'.",
|
|
"flows": [
|
|
{
|
|
"id": "m-001",
|
|
"role": "master",
|
|
"title": "callback completes",
|
|
"scenario": "completed",
|
|
"steps": [
|
|
{
|
|
"mail": {
|
|
"to": "ticket!callback",
|
|
"text": "Phone: +49 30 1234567\nTime: tomorrow 10:00"
|
|
},
|
|
"expect": {
|
|
"response_status": "created"
|
|
}
|
|
},
|
|
{
|
|
"mode": "run",
|
|
"expect": {
|
|
"statuses": [
|
|
"scheduled",
|
|
"running",
|
|
"completed"
|
|
],
|
|
"last_mail_contains": "has taken place"
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "m-002",
|
|
"role": "master",
|
|
"title": "negotiation over email",
|
|
"scenario": "negotiate",
|
|
"steps": [
|
|
{
|
|
"mail": {
|
|
"to": "ticket!callback",
|
|
"text": "Please call me."
|
|
},
|
|
"expect": {
|
|
"response_status": "created"
|
|
}
|
|
},
|
|
{
|
|
"mode": "run",
|
|
"expect": {
|
|
"statuses": [
|
|
"negotiating"
|
|
],
|
|
"last_mail_contains": "Phone: ..."
|
|
}
|
|
},
|
|
{
|
|
"mail": {
|
|
"to": "ticket={case_id}",
|
|
"text": "Phone: +49 30 1234567\nTime: tomorrow 10:00"
|
|
},
|
|
"expect": {
|
|
"response_status": "correlated"
|
|
}
|
|
},
|
|
{
|
|
"mode": "run",
|
|
"expect": {
|
|
"statuses": [
|
|
"negotiating",
|
|
"scheduled",
|
|
"running",
|
|
"completed"
|
|
],
|
|
"last_mail_contains": "has taken place"
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "m-003",
|
|
"role": "master",
|
|
"title": "no answer, retried, completed",
|
|
"scenario": "no-answer-retry",
|
|
"steps": [
|
|
{
|
|
"mail": {
|
|
"to": "ticket!callback",
|
|
"text": "Phone: +49 30 1234567\nTime: tomorrow 10:00"
|
|
}
|
|
},
|
|
{
|
|
"mode": "run",
|
|
"expect": {
|
|
"statuses": [
|
|
"scheduled",
|
|
"attempted",
|
|
"scheduled",
|
|
"running",
|
|
"completed"
|
|
]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "m-004",
|
|
"role": "master",
|
|
"title": "no answer twice, failed",
|
|
"scenario": "no-answer-fail",
|
|
"steps": [
|
|
{
|
|
"mail": {
|
|
"to": "ticket!callback",
|
|
"text": "Phone: +49 30 1234567\nTime: tomorrow 10:00"
|
|
}
|
|
},
|
|
{
|
|
"mode": "run",
|
|
"expect": {
|
|
"statuses": [
|
|
"scheduled",
|
|
"attempted",
|
|
"scheduled",
|
|
"attempted",
|
|
"failed"
|
|
],
|
|
"last_mail_contains": "did not succeed"
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "m-005",
|
|
"role": "master",
|
|
"title": "refused out of hours",
|
|
"scenario": "out-of-hours",
|
|
"steps": [
|
|
{
|
|
"mail": {
|
|
"to": "ticket!callback",
|
|
"text": "Phone: +49 30 1234567\nTime: tomorrow 10:00"
|
|
}
|
|
},
|
|
{
|
|
"mode": "run",
|
|
"expect": {
|
|
"statuses": [
|
|
"refused"
|
|
],
|
|
"last_mail_contains": "out-of-hours"
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "m-006",
|
|
"role": "master",
|
|
"title": "email owns the timeout",
|
|
"scenario": "silent",
|
|
"steps": [
|
|
{
|
|
"mail": {
|
|
"to": "ticket!callback",
|
|
"text": "Phone: +49 30 1234567\nTime: tomorrow 10:00"
|
|
}
|
|
},
|
|
{
|
|
"mode": "run",
|
|
"expect": {
|
|
"statuses": []
|
|
}
|
|
},
|
|
{
|
|
"advance": 3599,
|
|
"expect": {
|
|
"timed_out": 0
|
|
}
|
|
},
|
|
{
|
|
"advance": 2,
|
|
"expect": {
|
|
"timed_out": 1,
|
|
"mode_received_last": "handoff.cancel",
|
|
"last_mail_contains": "could not arrange"
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "m-007",
|
|
"role": "master",
|
|
"title": "mode not enabled by the statute",
|
|
"scenario": "completed",
|
|
"steps": [
|
|
{
|
|
"mail": {
|
|
"to": "ticket!chat",
|
|
"text": "hi"
|
|
},
|
|
"expect": {
|
|
"response_status": "created",
|
|
"mode_received": 0,
|
|
"outbox_count": 1
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "m-008",
|
|
"role": "master",
|
|
"title": "unauthenticated sender gets no mail",
|
|
"scenario": "negotiate",
|
|
"steps": [
|
|
{
|
|
"mail": {
|
|
"to": "ticket!callback",
|
|
"text": "call me",
|
|
"dmarc": "fail"
|
|
},
|
|
"expect": {
|
|
"response_status": "created"
|
|
}
|
|
},
|
|
{
|
|
"mode": "run",
|
|
"expect": {
|
|
"statuses": [
|
|
"negotiating"
|
|
],
|
|
"outbox_count": 0
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "s-001",
|
|
"role": "slave",
|
|
"title": "master issues the id; email sends only on instruction",
|
|
"steps": [
|
|
{
|
|
"mail": {
|
|
"to": "case",
|
|
"text": "My order is late"
|
|
},
|
|
"expect": {
|
|
"response_status": "intake-reported",
|
|
"master_received": {
|
|
"case.intake": 1
|
|
},
|
|
"outbox_count": 0
|
|
}
|
|
},
|
|
{
|
|
"pump": true,
|
|
"expect": {
|
|
"case_address": "case=EXT-100@modes.example"
|
|
}
|
|
},
|
|
{
|
|
"mail": {
|
|
"to": "case={case_id}",
|
|
"text": "more"
|
|
},
|
|
"expect": {
|
|
"response_status": "reported",
|
|
"master_received": {
|
|
"case.message": 1
|
|
},
|
|
"outbox_count": 0
|
|
}
|
|
},
|
|
{
|
|
"master": {
|
|
"command": "send",
|
|
"in_reply_to": "{message_ref}",
|
|
"body": "We are on it."
|
|
},
|
|
"expect": {
|
|
"command_ok": true,
|
|
"outbox_count": 1
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "s-002",
|
|
"role": "slave",
|
|
"title": "principle 10 refuses a master send",
|
|
"steps": [
|
|
{
|
|
"mail": {
|
|
"to": "case",
|
|
"text": "hi",
|
|
"dmarc": "fail"
|
|
}
|
|
},
|
|
{
|
|
"pump": true
|
|
},
|
|
{
|
|
"master": {
|
|
"command": "send",
|
|
"in_reply_to": "{message_ref}",
|
|
"body": "x"
|
|
},
|
|
"expect": {
|
|
"command_ok": false,
|
|
"command_reason": "unauthenticated-sender",
|
|
"outbox_count": 0
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "s-003",
|
|
"role": "slave",
|
|
"title": "transfer blocked",
|
|
"steps": [
|
|
{
|
|
"mail": {
|
|
"to": "case!callback",
|
|
"text": "Phone: +49 30 1234567\nTime: tomorrow 10:00"
|
|
}
|
|
},
|
|
{
|
|
"pump": true,
|
|
"expect": {
|
|
"master_received": {
|
|
"handoff.requested": 1,
|
|
"handoff.status": 1
|
|
},
|
|
"mode_received": 0
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "s-004",
|
|
"role": "slave",
|
|
"title": "transfer to the primed webhook; statuses relayed",
|
|
"config": {
|
|
"transfers": {
|
|
"webhook": "{callback}"
|
|
}
|
|
},
|
|
"scenario": "completed",
|
|
"steps": [
|
|
{
|
|
"mail": {
|
|
"to": "case!callback",
|
|
"text": "Phone: +49 30 1234567\nTime: tomorrow 10:00"
|
|
}
|
|
},
|
|
{
|
|
"pump": true,
|
|
"expect": {
|
|
"mode_received": 1
|
|
}
|
|
},
|
|
{
|
|
"mode": "run",
|
|
"expect": {
|
|
"relayed": [
|
|
"scheduled",
|
|
"running",
|
|
"completed"
|
|
],
|
|
"outbox_count": 0
|
|
}
|
|
},
|
|
{
|
|
"advance": 1000000,
|
|
"expect": {
|
|
"timed_out": 0
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "s-005",
|
|
"role": "slave",
|
|
"title": "the master owns the timeout",
|
|
"config": {
|
|
"transfers": {
|
|
"webhook": "{callback}"
|
|
}
|
|
},
|
|
"scenario": "silent",
|
|
"steps": [
|
|
{
|
|
"mail": {
|
|
"to": "case!callback",
|
|
"text": "Phone: +49 30 1234567\nTime: tomorrow 10:00"
|
|
}
|
|
},
|
|
{
|
|
"pump": true
|
|
},
|
|
{
|
|
"mode": "run",
|
|
"expect": {
|
|
"relayed": []
|
|
}
|
|
},
|
|
{
|
|
"master": {
|
|
"command": "cancel",
|
|
"handoff_id": "{handoff_id}"
|
|
},
|
|
"expect": {
|
|
"command_ok": true,
|
|
"mode_received_last": "handoff.cancel"
|
|
}
|
|
},
|
|
{
|
|
"mode": "run",
|
|
"expect": {
|
|
"relayed": [
|
|
"cancelled"
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|