MAS-WP-0013 finished: mode contract flows runnable from the bundle alone
conformance/FLOWS.md defines setup, steps, captures and expectations, all observable at the contract boundary. flows.json carries setups (peer ids), fixed test keys, statute, scenario and explicit assign steps; scenarios.json holds the callback scenarios (the stub loads them). CONTRACT.md defines the case.assign response. Runner reads only the bundle and rejects undefined keys; new flow s-006 (held over cap). 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
8de8fddf37
commit
ecb1ba47f6
14 changed files with 1118 additions and 269 deletions
|
|
@ -1,9 +1,58 @@
|
|||
{
|
||||
"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. Peers: email, callback-1 (the stub), cms-1 (the stub master); every request is signed (CONTRACT.md section 7). 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'.",
|
||||
"description": "Scripted flows (CONTRACT.md section 8). The format, steps, expectations and captures are defined in conformance/FLOWS.md. Everything a run needs is in this file, scenarios.json and statute.json.",
|
||||
"statute": "statute.json",
|
||||
"peers": {
|
||||
"callback-1": "6d61732d6d6f6465732d76302e312d746573742d6b65792d63616c6c6261636b31",
|
||||
"cms-1": "6d61732d6d6f6465732d76302e312d746573742d6b65792d636d732d312d2d2d2d",
|
||||
"gateway": "6d61732d6d6f6465732d76302e312d746573742d6b65792d676174657761792d2d"
|
||||
},
|
||||
"setups": {
|
||||
"master": {
|
||||
"roles": {
|
||||
"ticket": {
|
||||
"role": "master",
|
||||
"modes": {
|
||||
"callback": {
|
||||
"peer": "callback-1"
|
||||
}
|
||||
},
|
||||
"fields": [
|
||||
"phone",
|
||||
"time",
|
||||
"scenario"
|
||||
],
|
||||
"timeout": 3600
|
||||
}
|
||||
}
|
||||
},
|
||||
"slave": {
|
||||
"roles": {
|
||||
"case": {
|
||||
"role": "slave",
|
||||
"master_peer": "cms-1",
|
||||
"autonomous_send": false,
|
||||
"transfers": "block"
|
||||
}
|
||||
}
|
||||
},
|
||||
"slave-webhook": {
|
||||
"roles": {
|
||||
"case": {
|
||||
"role": "slave",
|
||||
"master_peer": "cms-1",
|
||||
"autonomous_send": false,
|
||||
"transfers": {
|
||||
"webhook": "<mode:callback-1>",
|
||||
"peer": "callback-1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flows": [
|
||||
{
|
||||
"id": "m-001",
|
||||
"role": "master",
|
||||
"setup": "master",
|
||||
"title": "callback completes",
|
||||
"scenario": "completed",
|
||||
"steps": [
|
||||
|
|
@ -13,13 +62,14 @@
|
|||
"text": "Phone: +49 30 1234567\nTime: tomorrow 10:00"
|
||||
},
|
||||
"expect": {
|
||||
"response_status": "created"
|
||||
"event_accepted": true,
|
||||
"mode_received": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"mode": "run",
|
||||
"expect": {
|
||||
"statuses": [
|
||||
"mode_statuses": [
|
||||
"scheduled",
|
||||
"running",
|
||||
"completed"
|
||||
|
|
@ -31,7 +81,7 @@
|
|||
},
|
||||
{
|
||||
"id": "m-002",
|
||||
"role": "master",
|
||||
"setup": "master",
|
||||
"title": "negotiation over email",
|
||||
"scenario": "negotiate",
|
||||
"steps": [
|
||||
|
|
@ -41,13 +91,13 @@
|
|||
"text": "Please call me."
|
||||
},
|
||||
"expect": {
|
||||
"response_status": "created"
|
||||
"event_accepted": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"mode": "run",
|
||||
"expect": {
|
||||
"statuses": [
|
||||
"mode_statuses": [
|
||||
"negotiating"
|
||||
],
|
||||
"last_mail_contains": "Phone: ..."
|
||||
|
|
@ -59,13 +109,14 @@
|
|||
"text": "Phone: +49 30 1234567\nTime: tomorrow 10:00"
|
||||
},
|
||||
"expect": {
|
||||
"response_status": "correlated"
|
||||
"event_accepted": true,
|
||||
"mode_received_last": "handoff.update"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mode": "run",
|
||||
"expect": {
|
||||
"statuses": [
|
||||
"mode_statuses": [
|
||||
"negotiating",
|
||||
"scheduled",
|
||||
"running",
|
||||
|
|
@ -78,7 +129,7 @@
|
|||
},
|
||||
{
|
||||
"id": "m-003",
|
||||
"role": "master",
|
||||
"setup": "master",
|
||||
"title": "no answer, retried, completed",
|
||||
"scenario": "no-answer-retry",
|
||||
"steps": [
|
||||
|
|
@ -91,7 +142,7 @@
|
|||
{
|
||||
"mode": "run",
|
||||
"expect": {
|
||||
"statuses": [
|
||||
"mode_statuses": [
|
||||
"scheduled",
|
||||
"attempted",
|
||||
"scheduled",
|
||||
|
|
@ -104,7 +155,7 @@
|
|||
},
|
||||
{
|
||||
"id": "m-004",
|
||||
"role": "master",
|
||||
"setup": "master",
|
||||
"title": "no answer twice, failed",
|
||||
"scenario": "no-answer-fail",
|
||||
"steps": [
|
||||
|
|
@ -117,7 +168,7 @@
|
|||
{
|
||||
"mode": "run",
|
||||
"expect": {
|
||||
"statuses": [
|
||||
"mode_statuses": [
|
||||
"scheduled",
|
||||
"attempted",
|
||||
"scheduled",
|
||||
|
|
@ -131,7 +182,7 @@
|
|||
},
|
||||
{
|
||||
"id": "m-005",
|
||||
"role": "master",
|
||||
"setup": "master",
|
||||
"title": "refused out of hours",
|
||||
"scenario": "out-of-hours",
|
||||
"steps": [
|
||||
|
|
@ -144,7 +195,7 @@
|
|||
{
|
||||
"mode": "run",
|
||||
"expect": {
|
||||
"statuses": [
|
||||
"mode_statuses": [
|
||||
"refused"
|
||||
],
|
||||
"last_mail_contains": "out-of-hours"
|
||||
|
|
@ -154,7 +205,7 @@
|
|||
},
|
||||
{
|
||||
"id": "m-006",
|
||||
"role": "master",
|
||||
"setup": "master",
|
||||
"title": "email owns the timeout",
|
||||
"scenario": "silent",
|
||||
"steps": [
|
||||
|
|
@ -162,33 +213,44 @@
|
|||
"mail": {
|
||||
"to": "ticket!callback",
|
||||
"text": "Phone: +49 30 1234567\nTime: tomorrow 10:00"
|
||||
},
|
||||
"expect": {
|
||||
"mails_sent": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"mode": "run",
|
||||
"expect": {
|
||||
"statuses": []
|
||||
"mode_statuses": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"advance": 3599,
|
||||
"expect": {
|
||||
"timed_out": 0
|
||||
"mode_received": 1,
|
||||
"mails_sent": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"advance": 2,
|
||||
"expect": {
|
||||
"timed_out": 1,
|
||||
"mode_received_last": "handoff.cancel",
|
||||
"mails_sent": 2,
|
||||
"last_mail_contains": "could not arrange"
|
||||
}
|
||||
},
|
||||
{
|
||||
"advance": 100000,
|
||||
"expect": {
|
||||
"mode_received": 2,
|
||||
"mails_sent": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "m-007",
|
||||
"role": "master",
|
||||
"setup": "master",
|
||||
"title": "mode not enabled by the statute",
|
||||
"scenario": "completed",
|
||||
"steps": [
|
||||
|
|
@ -198,16 +260,16 @@
|
|||
"text": "hi"
|
||||
},
|
||||
"expect": {
|
||||
"response_status": "created",
|
||||
"event_accepted": true,
|
||||
"mode_received": 0,
|
||||
"outbox_count": 1
|
||||
"mails_sent": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "m-008",
|
||||
"role": "master",
|
||||
"setup": "master",
|
||||
"title": "unauthenticated sender gets no mail",
|
||||
"scenario": "negotiate",
|
||||
"steps": [
|
||||
|
|
@ -218,24 +280,25 @@
|
|||
"dmarc": "fail"
|
||||
},
|
||||
"expect": {
|
||||
"response_status": "created"
|
||||
"event_accepted": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"mode": "run",
|
||||
"expect": {
|
||||
"statuses": [
|
||||
"mode_statuses": [
|
||||
"negotiating"
|
||||
],
|
||||
"outbox_count": 0
|
||||
"mails_sent": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "s-001",
|
||||
"role": "slave",
|
||||
"setup": "slave",
|
||||
"title": "master issues the id; email sends only on instruction",
|
||||
"scenario": "completed",
|
||||
"steps": [
|
||||
{
|
||||
"mail": {
|
||||
|
|
@ -243,16 +306,20 @@
|
|||
"text": "My order is late"
|
||||
},
|
||||
"expect": {
|
||||
"response_status": "intake-reported",
|
||||
"event_accepted": true,
|
||||
"master_received": {
|
||||
"case.intake": 1
|
||||
},
|
||||
"outbox_count": 0
|
||||
"mails_sent": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"pump": true,
|
||||
"master": {
|
||||
"command": "assign",
|
||||
"resource_id": "EXT-100"
|
||||
},
|
||||
"expect": {
|
||||
"response_ok": true,
|
||||
"case_address": "case=EXT-100@modes.example"
|
||||
}
|
||||
},
|
||||
|
|
@ -262,11 +329,10 @@
|
|||
"text": "more"
|
||||
},
|
||||
"expect": {
|
||||
"response_status": "reported",
|
||||
"master_received": {
|
||||
"case.message": 1
|
||||
},
|
||||
"outbox_count": 0
|
||||
"mails_sent": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -276,16 +342,17 @@
|
|||
"body": "We are on it."
|
||||
},
|
||||
"expect": {
|
||||
"command_ok": true,
|
||||
"outbox_count": 1
|
||||
"response_ok": true,
|
||||
"mails_sent": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "s-002",
|
||||
"role": "slave",
|
||||
"setup": "slave",
|
||||
"title": "principle 10 refuses a master send",
|
||||
"scenario": "completed",
|
||||
"steps": [
|
||||
{
|
||||
"mail": {
|
||||
|
|
@ -295,7 +362,10 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"pump": true
|
||||
"master": {
|
||||
"command": "assign",
|
||||
"resource_id": "EXT-100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"master": {
|
||||
|
|
@ -304,31 +374,43 @@
|
|||
"body": "x"
|
||||
},
|
||||
"expect": {
|
||||
"command_ok": false,
|
||||
"command_reason": "unauthenticated-sender",
|
||||
"outbox_count": 0
|
||||
"response_ok": false,
|
||||
"response_reason": "unauthenticated-sender",
|
||||
"mails_sent": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "s-003",
|
||||
"role": "slave",
|
||||
"setup": "slave",
|
||||
"title": "transfer blocked",
|
||||
"scenario": "completed",
|
||||
"steps": [
|
||||
{
|
||||
"mail": {
|
||||
"to": "case!callback",
|
||||
"text": "Phone: +49 30 1234567\nTime: tomorrow 10:00"
|
||||
},
|
||||
"expect": {
|
||||
"master_received": {
|
||||
"case.intake": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"pump": true,
|
||||
"master": {
|
||||
"command": "assign",
|
||||
"resource_id": "EXT-100"
|
||||
},
|
||||
"expect": {
|
||||
"master_received": {
|
||||
"handoff.requested": 1,
|
||||
"handoff.status": 1
|
||||
},
|
||||
"relayed": [
|
||||
"refused"
|
||||
],
|
||||
"mode_received": 0
|
||||
}
|
||||
}
|
||||
|
|
@ -336,14 +418,8 @@
|
|||
},
|
||||
{
|
||||
"id": "s-004",
|
||||
"role": "slave",
|
||||
"setup": "slave-webhook",
|
||||
"title": "transfer to the primed webhook; statuses relayed",
|
||||
"config": {
|
||||
"transfers": {
|
||||
"webhook": "{callback}",
|
||||
"peer": "callback-1"
|
||||
}
|
||||
},
|
||||
"scenario": "completed",
|
||||
"steps": [
|
||||
{
|
||||
|
|
@ -353,7 +429,10 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"pump": true,
|
||||
"master": {
|
||||
"command": "assign",
|
||||
"resource_id": "EXT-100"
|
||||
},
|
||||
"expect": {
|
||||
"mode_received": 1
|
||||
}
|
||||
|
|
@ -366,27 +445,21 @@
|
|||
"running",
|
||||
"completed"
|
||||
],
|
||||
"outbox_count": 0
|
||||
"mails_sent": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"advance": 1000000,
|
||||
"expect": {
|
||||
"timed_out": 0
|
||||
"mode_received": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "s-005",
|
||||
"role": "slave",
|
||||
"setup": "slave-webhook",
|
||||
"title": "the master owns the timeout",
|
||||
"config": {
|
||||
"transfers": {
|
||||
"webhook": "{callback}",
|
||||
"peer": "callback-1"
|
||||
}
|
||||
},
|
||||
"scenario": "silent",
|
||||
"steps": [
|
||||
{
|
||||
|
|
@ -396,7 +469,10 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"pump": true
|
||||
"master": {
|
||||
"command": "assign",
|
||||
"resource_id": "EXT-100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mode": "run",
|
||||
|
|
@ -404,13 +480,19 @@
|
|||
"relayed": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"advance": 1000000,
|
||||
"expect": {
|
||||
"mode_received": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"master": {
|
||||
"command": "cancel",
|
||||
"handoff_id": "{handoff_id}"
|
||||
},
|
||||
"expect": {
|
||||
"command_ok": true,
|
||||
"response_ok": true,
|
||||
"mode_received_last": "handoff.cancel"
|
||||
}
|
||||
},
|
||||
|
|
@ -425,9 +507,10 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "a-001",
|
||||
"role": "slave",
|
||||
"title": "unsigned master command refused",
|
||||
"id": "s-006",
|
||||
"setup": "slave",
|
||||
"title": "over the cap a master send is held",
|
||||
"scenario": "completed",
|
||||
"steps": [
|
||||
{
|
||||
"mail": {
|
||||
|
|
@ -436,7 +519,55 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"pump": true
|
||||
"master": {
|
||||
"command": "assign",
|
||||
"resource_id": "EXT-100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"master": {
|
||||
"command": "send",
|
||||
"in_reply_to": "{message_ref}",
|
||||
"body": "one"
|
||||
},
|
||||
"expect": {
|
||||
"response_ok": true,
|
||||
"mails_sent": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"master": {
|
||||
"command": "send",
|
||||
"in_reply_to": "{message_ref}",
|
||||
"body": "two"
|
||||
},
|
||||
"expect": {
|
||||
"response_ok": false,
|
||||
"response_reason": "send-cap-reached",
|
||||
"response_held": true,
|
||||
"mails_sent": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"max_sent": 1
|
||||
},
|
||||
{
|
||||
"id": "a-001",
|
||||
"setup": "slave",
|
||||
"title": "unsigned master command refused",
|
||||
"scenario": "completed",
|
||||
"steps": [
|
||||
{
|
||||
"mail": {
|
||||
"to": "case",
|
||||
"text": "hi"
|
||||
}
|
||||
},
|
||||
{
|
||||
"master": {
|
||||
"command": "assign",
|
||||
"resource_id": "EXT-100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"inject": {
|
||||
|
|
@ -452,23 +583,21 @@
|
|||
"expect": {
|
||||
"http_status": 401,
|
||||
"error": "unauthenticated-peer",
|
||||
"outbox_count": 0
|
||||
"mails_sent": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a-002",
|
||||
"role": "slave",
|
||||
"setup": "slave",
|
||||
"title": "a mode cannot assign case ids",
|
||||
"scenario": "completed",
|
||||
"steps": [
|
||||
{
|
||||
"mail": {
|
||||
"to": "case",
|
||||
"text": "hi"
|
||||
},
|
||||
"expect": {
|
||||
"response_status": "intake-reported"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -487,7 +616,10 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"pump": true,
|
||||
"master": {
|
||||
"command": "assign",
|
||||
"resource_id": "EXT-100"
|
||||
},
|
||||
"expect": {
|
||||
"case_address": "case=EXT-100@modes.example"
|
||||
}
|
||||
|
|
@ -496,8 +628,9 @@
|
|||
},
|
||||
{
|
||||
"id": "a-003",
|
||||
"role": "slave",
|
||||
"setup": "slave",
|
||||
"title": "webhook to a peer email has no key for",
|
||||
"scenario": "completed",
|
||||
"steps": [
|
||||
{
|
||||
"mail": {
|
||||
|
|
@ -506,7 +639,10 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"pump": true
|
||||
"master": {
|
||||
"command": "assign",
|
||||
"resource_id": "EXT-100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"inject": {
|
||||
|
|
@ -530,8 +666,9 @@
|
|||
},
|
||||
{
|
||||
"id": "a-004",
|
||||
"role": "slave",
|
||||
"setup": "slave",
|
||||
"title": "replayed master command",
|
||||
"scenario": "completed",
|
||||
"steps": [
|
||||
{
|
||||
"mail": {
|
||||
|
|
@ -540,7 +677,10 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"pump": true
|
||||
"master": {
|
||||
"command": "assign",
|
||||
"resource_id": "EXT-100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"inject": {
|
||||
|
|
@ -557,15 +697,16 @@
|
|||
"expect": {
|
||||
"http_status": 401,
|
||||
"error": "replayed",
|
||||
"outbox_count": 0
|
||||
"mails_sent": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a-005",
|
||||
"role": "slave",
|
||||
"setup": "slave",
|
||||
"title": "master command signed with the wrong key",
|
||||
"scenario": "completed",
|
||||
"steps": [
|
||||
{
|
||||
"mail": {
|
||||
|
|
@ -574,7 +715,10 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"pump": true
|
||||
"master": {
|
||||
"command": "assign",
|
||||
"resource_id": "EXT-100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"inject": {
|
||||
|
|
@ -591,7 +735,7 @@
|
|||
"expect": {
|
||||
"http_status": 401,
|
||||
"error": "unauthenticated-peer",
|
||||
"outbox_count": 0
|
||||
"mails_sent": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -607,14 +751,14 @@
|
|||
},
|
||||
"expect": {
|
||||
"http_status": 200,
|
||||
"outbox_count": 1
|
||||
"mails_sent": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a-006",
|
||||
"role": "master",
|
||||
"setup": "master",
|
||||
"title": "statuses only from the handoff's peer, at its URL",
|
||||
"scenario": "silent",
|
||||
"steps": [
|
||||
|
|
@ -624,7 +768,7 @@
|
|||
"text": "Phone: +49 30 1234567\nTime: tomorrow 10:00"
|
||||
},
|
||||
"expect": {
|
||||
"outbox_count": 1
|
||||
"mails_sent": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -634,6 +778,7 @@
|
|||
"message": {
|
||||
"type": "handoff.status",
|
||||
"case": "current",
|
||||
"handoff_id": "{handoff_id}",
|
||||
"seq": 1,
|
||||
"status": "completed"
|
||||
}
|
||||
|
|
@ -651,6 +796,7 @@
|
|||
"message": {
|
||||
"type": "handoff.status",
|
||||
"case": "current",
|
||||
"handoff_id": "{handoff_id}",
|
||||
"seq": 1,
|
||||
"status": "completed"
|
||||
}
|
||||
|
|
@ -658,7 +804,7 @@
|
|||
"expect": {
|
||||
"http_status": 404,
|
||||
"error": "unknown-handoff",
|
||||
"outbox_count": 1
|
||||
"mails_sent": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -668,6 +814,7 @@
|
|||
"message": {
|
||||
"type": "handoff.status",
|
||||
"case": "current",
|
||||
"handoff_id": "{handoff_id}",
|
||||
"seq": 1,
|
||||
"status": "scheduled",
|
||||
"at": "x"
|
||||
|
|
@ -681,8 +828,9 @@
|
|||
},
|
||||
{
|
||||
"id": "a-007",
|
||||
"role": "master",
|
||||
"setup": "master",
|
||||
"title": "only the gateway posts events",
|
||||
"scenario": "completed",
|
||||
"steps": [
|
||||
{
|
||||
"inject": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue