{
  "name": "REVOPS-L1-001 | 04 | Errors and stalled jobs",
  "nodes": [
    {
      "parameters": {},
      "id": "2db16631-2f2d-4ac0-939d-8894bcff4173",
      "name": "Worker Error",
      "type": "n8n-nodes-base.errorTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const e = $input.first().json;\nconst message = String(e.execution?.error?.message || e.trigger?.error?.message || 'Workflow failure').replace(/(Bearer\\s+)\\S+/gi,'$1[REDACTED]').replace(/(api_key=)[^&\\s]+/gi,'$1[REDACTED]').slice(0,1000);\nreturn [{json:{execution_id:String(e.execution?.id || ''),error:{node:String(e.execution?.lastNodeExecuted || 'unknown'),message}}}];"
      },
      "id": "322d7489-0a50-45db-8852-cc02fadcef61",
      "name": "Sanitize Failure",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        260,
        0
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT * FROM revops.record_failure($1::text,$2::jsonb);",
        "options": {
          "queryReplacement": "={{ [$json.execution_id,JSON.stringify($json.error)] }}"
        }
      },
      "id": "c3c08d68-ff09-4a5b-bcae-1db0a327f6a6",
      "name": "Record Failure",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        520,
        0
      ],
      "credentials": {
        "postgres": {
          "id": "REPLACE_POSTGRES_CREDENTIAL",
          "name": "REVOPS Postgres"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 5
            }
          ]
        }
      },
      "id": "a47678ad-92c4-46b4-8c77-6b17dc742085",
      "name": "Stalled Job Tick",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        780,
        0
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT * FROM revops.new_incidents($1::text);",
        "options": {
          "queryReplacement": "={{ ['REVOPS-L1-001'] }}"
        }
      },
      "id": "292cfa33-58c8-4c2f-a71b-412e1bf246c3",
      "name": "Find Newly Actionable Jobs",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        1040,
        0
      ],
      "credentials": {
        "postgres": {
          "id": "REPLACE_POSTGRES_CREDENTIAL",
          "name": "REVOPS Postgres"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Optional separate channel for operational incidents. Independent of Hot lead notifications.\nconst config = {enabled:false,channel:'REPLACE_OPS_SLACK_CHANNEL_ID'};\nreturn $input.all().map((item,i)=>({json:{...item.json,ops:config},pairedItem:{item:i}}));"
      },
      "id": "57c9c2d3-10f3-452b-9166-3b2e3127be98",
      "name": "Ops Alert Configuration",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1300,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "29508047-a974-427a-958b-fa056b6c1b71",
              "leftValue": "={{ $json.ops.enabled === true }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "0acb8160-b443-4c4b-b4eb-659bb108a41f",
      "name": "Ops Slack Enabled?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1560,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://slack.com/api/chat.postMessage",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {channel:$json.ops.channel,text:'REVOPS automation needs attention | job ' + ($json.job_id || 'unclaimed') + ' | state ' + $json.status + ' | execution ' + ($json.execution_id || '') + '. Inspect n8n execution and revops.jobs; reconcile provider before retrying.',unfurl_links:false} }}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "4dc9ee89-3a40-4613-a0ca-a0b35b5f19df",
      "name": "Notify Operations",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1820,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_SLACK_CREDENTIAL",
          "name": "REVOPS Slack"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "for (const item of $input.all()) if (item.json.ok !== true) throw new Error('Operations Slack notification failed; inspect execution and database incidents');\nreturn $input.all();"
      },
      "id": "707cee1f-45c0-4906-8f79-b19f9f79babd",
      "name": "Check Ops Notification",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2080,
        0
      ]
    },
    {
      "parameters": {
        "content": "## 04 — Operations\nActivate this workflow for the five-minute stalled-job scan.\nSelect it in the worker Settings → Error Workflow (and intake if desired).\nError Trigger runs for automatic executions, not manual tests.\nEnable a separate ops Slack channel in Ops Alert Configuration. No lead PII is sent there.",
        "width": 650,
        "height": 220
      },
      "id": "fafa507a-82e8-4cb0-bbc0-9b51c969c892",
      "name": "Guide 9",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -260
      ]
    }
  ],
  "connections": {
    "Worker Error": {
      "main": [
        [
          {
            "node": "Sanitize Failure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sanitize Failure": {
      "main": [
        [
          {
            "node": "Record Failure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Record Failure": {
      "main": [
        [
          {
            "node": "Ops Alert Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ops Alert Configuration": {
      "main": [
        [
          {
            "node": "Ops Slack Enabled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Stalled Job Tick": {
      "main": [
        [
          {
            "node": "Find Newly Actionable Jobs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Newly Actionable Jobs": {
      "main": [
        [
          {
            "node": "Ops Alert Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ops Slack Enabled?": {
      "main": [
        [
          {
            "node": "Notify Operations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify Operations": {
      "main": [
        [
          {
            "node": "Check Ops Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "UTC",
    "saveManualExecutions": true,
    "saveExecutionProgress": true
  },
  "active": false,
  "pinData": {},
  "tags": []
}

