{
  "id": "marsenApprovalLabV1",
  "name": "Marsen approval acceptance lab v1",
  "active": false,
  "nodes": [
    {
      "parameters": {},
      "id": "start",
      "name": "Run lab",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "return [{\"name\": \"approve\", \"proposal\": {\"id\": \"proposal-demo\", \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"reviewer\": \"reviewer-demo\", \"expiresAt\": 2000}, \"events\": [{\"actor\": \"reviewer-demo\", \"at\": 1000, \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"decision\": \"approve\"}], \"expected\": {\"status\": \"approved\", \"outcomes\": [\"approved\"], \"writes\": 1}}, {\"name\": \"reject\", \"proposal\": {\"id\": \"proposal-demo\", \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"reviewer\": \"reviewer-demo\", \"expiresAt\": 2000}, \"events\": [{\"actor\": \"reviewer-demo\", \"at\": 1000, \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"decision\": \"reject\"}], \"expected\": {\"status\": \"rejected\", \"outcomes\": [\"rejected\"], \"writes\": 0}}, {\"name\": \"expiry-at-boundary\", \"proposal\": {\"id\": \"proposal-demo\", \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"reviewer\": \"reviewer-demo\", \"expiresAt\": 2000}, \"events\": [{\"actor\": \"reviewer-demo\", \"at\": 2000, \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"decision\": \"approve\"}], \"expected\": {\"status\": \"expired\", \"outcomes\": [\"expired\"], \"writes\": 0}}, {\"name\": \"duplicate-approval\", \"proposal\": {\"id\": \"proposal-demo\", \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"reviewer\": \"reviewer-demo\", \"expiresAt\": 2000}, \"events\": [{\"actor\": \"reviewer-demo\", \"at\": 1000, \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"decision\": \"approve\"}, {\"actor\": \"reviewer-demo\", \"at\": 1000, \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"decision\": \"approve\"}], \"expected\": {\"status\": \"approved\", \"outcomes\": [\"approved\", \"duplicate\"], \"writes\": 1}}, {\"name\": \"changed-note\", \"proposal\": {\"id\": \"proposal-demo\", \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"reviewer\": \"reviewer-demo\", \"expiresAt\": 2000}, \"events\": [{\"actor\": \"reviewer-demo\", \"at\": 1000, \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Different unreviewed text\", \"decision\": \"approve\"}], \"expected\": {\"status\": \"pending\", \"outcomes\": [\"changed-payload\"], \"writes\": 0}}, {\"name\": \"changed-target\", \"proposal\": {\"id\": \"proposal-demo\", \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"reviewer\": \"reviewer-demo\", \"expiresAt\": 2000}, \"events\": [{\"actor\": \"reviewer-demo\", \"at\": 1000, \"version\": 1, \"target\": \"fictional-lead-002\", \"note\": \"Requested a CRM workflow review.\", \"decision\": \"approve\"}], \"expected\": {\"status\": \"pending\", \"outcomes\": [\"changed-payload\"], \"writes\": 0}}, {\"name\": \"stale-version\", \"proposal\": {\"id\": \"proposal-demo\", \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"reviewer\": \"reviewer-demo\", \"expiresAt\": 2000}, \"events\": [{\"actor\": \"reviewer-demo\", \"at\": 1000, \"version\": 0, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"decision\": \"approve\"}], \"expected\": {\"status\": \"pending\", \"outcomes\": [\"changed-payload\"], \"writes\": 0}}, {\"name\": \"missing-permission\", \"proposal\": {\"id\": \"proposal-demo\", \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"reviewer\": \"reviewer-demo\", \"expiresAt\": 2000}, \"events\": [{\"actor\": \"not-the-reviewer\", \"at\": 1000, \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"decision\": \"approve\"}], \"expected\": {\"status\": \"pending\", \"outcomes\": [\"unauthorized\"], \"writes\": 0}}, {\"name\": \"invalid-decision\", \"proposal\": {\"id\": \"proposal-demo\", \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"reviewer\": \"reviewer-demo\", \"expiresAt\": 2000}, \"events\": [{\"actor\": \"reviewer-demo\", \"at\": 1000, \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"decision\": \"maybe\"}], \"expected\": {\"status\": \"pending\", \"outcomes\": [\"invalid-decision\"], \"writes\": 0}}, {\"name\": \"rejection-is-terminal\", \"proposal\": {\"id\": \"proposal-demo\", \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"reviewer\": \"reviewer-demo\", \"expiresAt\": 2000}, \"events\": [{\"actor\": \"reviewer-demo\", \"at\": 1000, \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"decision\": \"reject\"}, {\"actor\": \"reviewer-demo\", \"at\": 1000, \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"decision\": \"approve\"}], \"expected\": {\"status\": \"rejected\", \"outcomes\": [\"rejected\", \"duplicate\"], \"writes\": 0}}, {\"name\": \"delivery-replay\", \"proposal\": {\"id\": \"proposal-demo\", \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"reviewer\": \"reviewer-demo\", \"expiresAt\": 2000}, \"events\": [{\"actor\": \"reviewer-demo\", \"at\": 1000, \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"decision\": \"approve\"}], \"expected\": {\"status\": \"approved\", \"writes\": 1, \"recovery\": \"existing-receipt\"}, \"deliveries\": 2}, {\"name\": \"downstream-failure\", \"proposal\": {\"id\": \"proposal-demo\", \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"reviewer\": \"reviewer-demo\", \"expiresAt\": 2000}, \"events\": [{\"actor\": \"reviewer-demo\", \"at\": 1000, \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"decision\": \"approve\"}], \"expected\": {\"status\": \"approved\", \"writes\": 0, \"error\": \"destination-unavailable\"}, \"destinationMode\": \"fail-before-write\"}, {\"name\": \"ambiguous-timeout\", \"proposal\": {\"id\": \"proposal-demo\", \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"reviewer\": \"reviewer-demo\", \"expiresAt\": 2000}, \"events\": [{\"actor\": \"reviewer-demo\", \"at\": 1000, \"version\": 1, \"target\": \"fictional-lead-001\", \"note\": \"Requested a CRM workflow review.\", \"decision\": \"approve\"}], \"expected\": {\"status\": \"approved\", \"writes\": 1, \"recovery\": \"reconciled\", \"error\": \"response-timeout\"}, \"destinationMode\": \"timeout-after-write\"}] .map(json => ({json}));",
        "mode": "runOnceForAllItems"
      },
      "id": "step-1",
      "name": "Synthetic cases",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        260,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// n8n Code node: run once for all items. Synthetic, single-execution state only.\nreturn $input.all().map(({json: test}) => {\n  const proposal = test.proposal;\n  let status = 'pending';\n  const decisions = [];\n  for (const event of test.events) {\n    let outcome;\n    if (event.actor !== proposal.reviewer) outcome = 'unauthorized';\n    else if (status !== 'pending') outcome = 'duplicate';\n    else if (!Number.isFinite(event.at) || event.at >= proposal.expiresAt) {\n      status = 'expired'; outcome = 'expired';\n    } else if (event.version !== proposal.version || event.target !== proposal.target || event.note !== proposal.note) {\n      outcome = 'changed-payload';\n    } else if (event.decision === 'reject') {\n      status = 'rejected'; outcome = 'rejected';\n    } else if (event.decision === 'approve') {\n      status = 'approved'; outcome = 'approved';\n    } else outcome = 'invalid-decision';\n    decisions.push({outcome, at: event.at});\n  }\n  return {json: {...test, status, decisions, action: status === 'approved' ? {\n    id: proposal.id + ':' + proposal.version,\n    target: proposal.target, note: proposal.note\n  } : null}};\n});\n",
        "mode": "runOnceForAllItems"
      },
      "id": "step-2",
      "name": "Validate approval",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        520,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Synthetic destination. Each test owns its in-memory receipt map.\nreturn $input.all().map(({json: test}) => {\n  const receipts = new Map();\n  let attempts = 0;\n  let recovery = 'not-needed';\n  let error = null;\n  if (test.action) {\n    for (let delivery = 0; delivery < (test.deliveries || 1); delivery++) {\n      attempts++;\n      if (receipts.has(test.action.id)) { recovery = 'existing-receipt'; continue; }\n      if (test.destinationMode === 'fail-before-write') { error = 'destination-unavailable'; break; }\n      receipts.set(test.action.id, {...test.action, receiptId: 'synthetic-' + test.action.id});\n      if (test.destinationMode === 'timeout-after-write') {\n        error = 'response-timeout';\n        // Simulates querying a destination receipt after an ambiguous response.\n        recovery = receipts.has(test.action.id) ? 'reconciled' : 'unresolved';\n      }\n    }\n  }\n  return {json: {...test, attempts, writes: receipts.size,\n    receipts: [...receipts.values()], recovery, error}};\n});\n",
        "mode": "runOnceForAllItems"
      },
      "id": "step-3",
      "name": "Simulated CRM",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        780,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "return $input.all().map(({json: test}) => {\n  const actual = {status: test.status, outcomes: test.decisions.map(d => d.outcome),\n    writes: test.writes, recovery: test.recovery, error: test.error};\n  for (const [field, expected] of Object.entries(test.expected)) {\n    if (JSON.stringify(actual[field]) !== JSON.stringify(expected)) {\n      throw new Error(test.name + ': ' + field + ' expected ' + JSON.stringify(expected) + ', got ' + JSON.stringify(actual[field]));\n    }\n  }\n  if (test.receipts.some(r => r.target !== test.proposal.target || r.note !== test.proposal.note)) {\n    throw new Error(test.name + ': destination payload differs from reviewed proposal');\n  }\n  return {json: {scenario: test.name, passed: true, actual,\n    expected: test.expected, receipts: test.receipts}};\n});\n",
        "mode": "runOnceForAllItems"
      },
      "id": "step-4",
      "name": "Verify outcomes",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1040,
        0
      ]
    }
  ],
  "connections": {
    "Run lab": {
      "main": [
        [
          {
            "node": "Synthetic cases",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Synthetic cases": {
      "main": [
        [
          {
            "node": "Validate approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate approval": {
      "main": [
        [
          {
            "node": "Simulated CRM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Simulated CRM": {
      "main": [
        [
          {
            "node": "Verify outcomes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {}
}
