{
  "id": "aicp-incident-responder",
  "name": "Incident responder (event-driven skills, AG-3)",
  "meta": {
    "description": "Source-agnostic incident lane: a webhook accepts PagerDuty, Alertmanager, Slack slash-command, or generic payloads; the agent diagnoses using the scope's skills; a human approves before any fix is applied (via the AG-2 worktree fan-out, merge stays human); the learning is encoded back into a skill. Set the approval-gate workflow ID and tokens; see README."
  },
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "incident",
        "options": {}
      },
      "id": "adadadad-0000-0000-0000-000000000001",
      "name": "Incident webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        200,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Normalize ANY supported event source into one shape:\n//   { id, title, description, severity, service, scope, category }\n// CONFIG below maps services->scopes and keywords->skill categories.\nconst CFG = {\n  serviceScopeMap: { 'default': 'data-team' },   // e.g. 'billing-api': 'engineering'\n  categoryRules: [\n    { re: /stream|kafka|pipeline/i, cat: 'streaming' },\n    { re: /bill|invoice|payment/i, cat: 'billing' },\n    { re: /auth|login|token/i, cat: 'auth' }\n  ],\n  repo: 'repo',            // git repo under workspace/ for the apply step\n  base: 'main',\n  applyVerify: 'npm test', // self-verify command for the fix worktree\n  encodeLearning: true\n};\nconst raw = $input.first().json;\nconst b = raw.body || raw;\nlet ev;\nif (b.event && (b.event.event_type || b.event.eventType)) {\n  // PagerDuty v3 webhook\n  const d = b.event.data || {};\n  ev = { title: d.title || b.event.event_type, description: JSON.stringify(d).slice(0, 1500),\n         severity: d.urgency === 'high' ? 'error' : 'warn', service: d.service?.summary || 'default' };\n} else if (Array.isArray(b.alerts)) {\n  // Prometheus Alertmanager / Grafana\n  const firing = b.alerts.filter(a => a.status === 'firing');\n  const a0 = firing[0] || b.alerts[0] || {};\n  ev = { title: a0.labels?.alertname || 'alert', description: firing.map(a => `${a.labels?.alertname}: ${a.annotations?.summary || a.annotations?.description || ''}`).join('\\n').slice(0, 1500),\n         severity: 'error', service: a0.labels?.service || a0.labels?.job || 'default' };\n} else if (b.command || (b.event && b.event.type === 'app_mention')) {\n  // Slack slash command (form fields) or Events-API mention\n  const text = b.text || b.event?.text || '';\n  ev = { title: `chat request: ${text.slice(0, 80)}`, description: text,\n         severity: 'warn', service: 'default' };\n} else {\n  // generic { title, description, severity?, service? }\n  ev = { title: b.title || 'incident', description: b.description || JSON.stringify(b).slice(0, 1500),\n         severity: b.severity || 'warn', service: b.service || 'default' };\n}\nconst hay = `${ev.title} ${ev.description}`;\nconst category = (CFG.categoryRules.find(r => r.re.test(hay)) || {}).cat || 'general';\nconst scope = CFG.serviceScopeMap[ev.service] || CFG.serviceScopeMap['default'];\nreturn [{ json: { ...ev, id: `inc-${$execution.id}`, scope, category, cfg: CFG } }];"
      },
      "id": "adadadad-0000-0000-0000-000000000002",
      "name": "Normalize + classify",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        420,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://router:8080/route",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-router-token",
              "value": "change-me-to-a-long-random-secret"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { \"prompt\": 'INCIDENT ' + $json.id + ' [' + $json.category + ']: ' + $json.title + '\\n\\n' + $json.description + '\\n\\nConsult the skills/ directories in your working scope (including inherited ancestor scopes) for runbooks matching this category. Diagnose the likely root cause using available context. Write a report to deliverables/' + $json.id + '.md containing: symptom, evidence, root cause hypothesis, PROPOSED fix (do NOT apply anything), and rollback considerations.', \"task_type\": \"debug\", \"scope\": $json.scope, \"maxTurns\": 30 } }}",
        "options": {
          "timeout": 900000
        }
      },
      "id": "adadadad-0000-0000-0000-000000000003",
      "name": "Diagnose (router)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        640,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Typed approval contract (G3). The human must be able to judge this without\n// opening another tab \u2014 that is the whole point of the gate.\nconst i = $input.first().json;\nconst d = i.decision || {};          // the router's decision block, computed upstream\nconst n = $('Normalize + classify').first().json;\n\nreturn [{ json: {\n  action: `Apply an automated fix to ${n.repo || 'the repo'} on branch ${n.base || 'main'}`,\n  scope: d.scope || n.scope || 'unknown',\n  riskTier: d.risk?.tier || d.risk || n.severity || 'unknown',\n  // WHICH dimensions fired, not just the resulting tier. A tier alone tells the\n  // approver how worried to be; the dimensions tell them WHY, which is what they\n  // are actually being asked to check.\n  dimensions: [\n    d.risk?.data && `data=${d.risk.data}`,\n    d.risk?.access && `access=${d.risk.access}`,\n    d.risk?.autonomy && `autonomy=${d.risk.autonomy}`,\n    d.pii && d.pii !== 'none' && `pii=${d.pii}`,\n    Array.isArray(d.guardrails) && d.guardrails.length && `guardrails=${d.guardrails.map(g => g.type).join('/')}`,\n  ].filter(Boolean).join(', ') || 'none fired',\n  requester: `incident-responder (${n.severity || 'unknown'} severity, triggered by webhook)`,\n  dataTouched: `${n.repo || 'repo'} working tree; verify=${n.applyVerify ? 'yes' : 'no'}`,\n  preview: String(i.result || i.message || '(no diagnosis returned)').slice(0, 4000),\n  autoApprove: false,\n} }];\n"
      },
      "id": "adadadad-0000-0000-0000-000000000004",
      "name": "Fix approval request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        860,
        300
      ]
    },
    {
      "parameters": {
        "source": "database",
        "workflowId": {
          "__rl": true,
          "value": "aicp-approval-gate",
          "mode": "id"
        },
        "options": {
          "waitForSubWorkflow": true
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "action": "={{ $json.action }}",
            "scope": "={{ $json.scope }}",
            "riskTier": "={{ $json.riskTier }}",
            "requester": "={{ $json.requester }}",
            "dataTouched": "={{ $json.dataTouched }}",
            "preview": "={{ $json.preview }}",
            "dimensions": "={{ $json.dimensions }}",
            "autoApprove": "={{ $json.autoApprove }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "action",
              "displayName": "action",
              "type": "string",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "scope",
              "displayName": "scope",
              "type": "string",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "riskTier",
              "displayName": "riskTier",
              "type": "string",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "requester",
              "displayName": "requester",
              "type": "string",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "dataTouched",
              "displayName": "dataTouched",
              "type": "string",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "preview",
              "displayName": "preview",
              "type": "string",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "dimensions",
              "displayName": "dimensions",
              "type": "string",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "autoApprove",
              "displayName": "autoApprove",
              "type": "boolean",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        }
      },
      "id": "adadadad-0000-0000-0000-000000000005",
      "name": "Request approval",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.2,
      "position": [
        1080,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true
          },
          "conditions": [
            {
              "leftValue": "={{ $json.approved }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "adadadad-0000-0000-0000-000000000006",
      "name": "Approved?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1300,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://claude-runner:8080/fanout",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-runner-token",
              "value": "change-me-to-a-long-random-secret"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { \"repo\": $('Normalize + classify').first().json.cfg.repo, \"base\": $('Normalize + classify').first().json.cfg.base, \"clean\": true, \"tasks\": [ { \"id\": $('Normalize + classify').first().json.id, \"prompt\": 'Apply the fix proposed in the incident report deliverables/' + $('Normalize + classify').first().json.id + '.md (in the workspace scope directory). Implement it in this repository, keeping the change minimal.', \"verify\": $('Normalize + classify').first().json.cfg.applyVerify } ] } }}",
        "options": {
          "timeout": 900000
        }
      },
      "id": "adadadad-0000-0000-0000-000000000007",
      "name": "Apply fix (worktree)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1520,
        200
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://router:8080/route",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-router-token",
              "value": "change-me-to-a-long-random-secret"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { \"prompt\": 'Encode what was learned from incident ' + $('Normalize + classify').first().json.id + ' as a durable skill. Read deliverables/' + $('Normalize + classify').first().json.id + '.md and write/update a short imperative skill file at skills/' + $('Normalize + classify').first().json.category + '-' + $('Normalize + classify').first().json.id + '.md covering: symptom pattern, how to detect it fast, root cause, the fix that worked, prevention. No tenant names, no PII.', \"task_type\": \"generate\", \"scope\": $('Normalize + classify').first().json.scope, \"maxTurns\": 15 } }}",
        "options": {
          "timeout": 600000
        }
      },
      "id": "adadadad-0000-0000-0000-000000000008",
      "name": "Encode learning (skill)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1740,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "const ev = $('Normalize + classify').first().json;\nconst fan = $('Apply fix (worktree)').first().json;\nconst r = (fan.results || [])[0] || {};\nreturn [{ json: {\n  severity: r.verify?.passed ? 'info' : 'warn',\n  title: `Incident ${ev.id}: fix ${r.verify?.passed ? 'ready for merge' : 'NEEDS ATTENTION'}`,\n  message: `${fan.review || ''}\\nBranch: ${r.branch || 'n/a'} | verify: ${r.verify ? (r.verify.passed ? 'PASS' : 'FAIL') : 'not run'}\\nLearning encoded to skills/. Merge remains a human act.`,\n  source: 'incident-responder'\n} }];"
      },
      "id": "adadadad-0000-0000-0000-000000000009",
      "name": "Summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1960,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "const ev = $('Normalize + classify').first().json;\nconst gate = $('Request approval').first().json;\nreturn [{ json: {\n  severity: 'warn',\n  title: `Incident ${ev.id}: diagnosis only (fix not applied)`,\n  message: `${gate.timedOut ? 'Approval timed out \u2014 default deny.' : 'Fix rejected by approver.'}\\nReport remains at deliverables/${ev.id}.md.`,\n  source: 'incident-responder'\n} }];"
      },
      "id": "adadadad-0000-0000-0000-00000000000a",
      "name": "Diagnosis-only summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1520,
        420
      ]
    }
  ],
  "connections": {
    "Incident webhook": {
      "main": [
        [
          {
            "node": "Normalize + classify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize + classify": {
      "main": [
        [
          {
            "node": "Diagnose (router)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Diagnose (router)": {
      "main": [
        [
          {
            "node": "Fix approval request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fix approval request": {
      "main": [
        [
          {
            "node": "Request approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request approval": {
      "main": [
        [
          {
            "node": "Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Approved?": {
      "main": [
        [
          {
            "node": "Apply fix (worktree)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Diagnosis-only summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Apply fix (worktree)": {
      "main": [
        [
          {
            "node": "Encode learning (skill)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Encode learning (skill)": {
      "main": [
        [
          {
            "node": "Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {},
  "active": false
}