{
  "name": "Secrets Provisioner",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "secrets-provisioner",
        "options": {
          "rawBody": false
        },
        "authentication": "headerAuth",
        "responseMode": "lastNode"
      },
      "id": "webhook-trigger",
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "position": [
        200,
        300
      ],
      "typeVersion": 2
    },
    {
      "parameters": {},
      "id": "manual-trigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        200,
        500
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "jsCode": "// Parse incoming missing secrets list (webhook body: { missing: [...] }).\nconst input = $input.first().json;\nconst body = input.body || input;\nconst missing = body.missing || body.all_missing || input.missing || input.all_missing || [];\n\n// Classify each secret as auto-provisionable or manual.\nconst AUTO_PROVISION = {\n  // Secrets that COULD be auto-provisioned with a bootstrap token.\n  // For now these are stubs \u2014 real implementation would call provider APIs.\n  'CLOUDFLARE_API_TOKEN': { provider: 'cloudflare', method: 'api_token_create', auto: false, reason: 'Requires Cloudflare dashboard or bootstrap Global API Key' },\n  'FIRECRAWL_API_KEY': { provider: 'firecrawl', method: 'manual', auto: false, reason: 'Sign up at firecrawl.dev and copy API key' }\n};\n\nconst ALWAYS_MANUAL = [\n  'N8N_API_KEY', 'N8N_ENCRYPTION_KEY', 'SUPABASE_SERVICE_ROLE_KEY',\n  'ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'MONDAY_API_TOKEN',\n  'COMMANDER_WEBHOOK_TOKEN', 'VAULT_MASTER_KEY', 'GRAFANA_PASSWORD'\n];\n\nconst provisioned = [];\nconst manual = [];\nconst errors = [];\n\nfor (const name of missing) {\n  const info = AUTO_PROVISION[name];\n  if (info && info.auto) {\n    // Stub: attempt auto-provision\n    try {\n      // In a real implementation, call the provider API here.\n      // e.g. Cloudflare: POST /client/v4/user/tokens with bootstrap token\n      provisioned.push({ name, method: info.method, provider: info.provider });\n    } catch (e) {\n      errors.push({ name, error: e.message });\n    }\n  } else {\n    const reason = info ? info.reason : 'Manual provisioning required \u2014 see docs/runbooks/missing-secrets.md';\n    manual.push({ name, reason });\n  }\n}\n\nreturn [{ json: { provisioned, manual, errors, total_missing: missing.length } }];"
      },
      "id": "classify-and-provision",
      "name": "Classify & Provision",
      "type": "n8n-nodes-base.code",
      "position": [
        480,
        400
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "jsCode": "// Store any auto-provisioned secrets into the vault adapter.\nconst report = $input.first().json;\nconst vaultBase = process.env.VAULT_BASE_URL || 'http://localhost:4000';\n\nfor (const item of report.provisioned) {\n  if (item.value) {\n    try {\n      await fetch(`${vaultBase}/secret/${item.name}`, {\n        method: 'PUT',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ value: item.value })\n      });\n      item.stored_in = 'vault';\n    } catch (e) {\n      report.errors.push({ name: item.name, error: `Vault store failed: ${e.message}` });\n    }\n    delete item.value; // never pass value downstream\n  }\n}\n\nreturn [{ json: report }];"
      },
      "id": "store-provisioned",
      "name": "Store Provisioned Secrets",
      "type": "n8n-nodes-base.code",
      "position": [
        720,
        400
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "jsCode": "// Build human-readable request packages for manual secrets.\nconst report = $input.first().json;\n\nconst packages = report.manual.map(item => ({\n  secret_name: item.name,\n  action_required: item.reason,\n  runbook: 'https://github.com/<owner>/<repo>/blob/main/docs/runbooks/missing-secrets.md',\n  instructions: [\n    `1. Obtain the value for ${item.name} from the appropriate provider.`,\n    `2. Store it: set in server ENV or run: echo <value> | vaultctl set ${item.name}`,\n    `3. Re-run secrets:heal to verify.`\n  ]\n}));\n\nreturn [{ json: { ...report, request_packages: packages } }];"
      },
      "id": "build-request-packages",
      "name": "Build Request Packages",
      "type": "n8n-nodes-base.code",
      "position": [
        960,
        400
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "jsCode": "// Final provisioner report. Gate 4: actions_taken[], remaining_missing[].\nconst r = $input.first().json;\nconst actions_taken = r.provisioned.map(p => `Auto-provisioned or attempted: ${p.name}`).concat(r.manual.map(m => `Request package generated: ${m.name}`));\nconst remaining_missing = r.manual.map(m => m.name);\nreturn [{ json: {\n  timestamp: new Date().toISOString(),\n  actions_taken,\n  remaining_missing,\n  total_missing: r.total_missing,\n  provisioned: r.provisioned.map(p => ({ name: p.name, method: p.method, stored_in: p.stored_in || 'n/a' })),\n  manual: r.manual.map(m => ({ name: m.name, reason: m.reason })),\n  errors: r.errors || [],\n  request_packages: r.request_packages || []\n} }];"
      },
      "id": "final-report",
      "name": "Final Report",
      "type": "n8n-nodes-base.code",
      "position": [
        1200,
        400
      ],
      "typeVersion": 2
    }
  ],
  "connections": {
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Classify & Provision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Classify & Provision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify & Provision": {
      "main": [
        [
          {
            "node": "Store Provisioned Secrets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Provisioned Secrets": {
      "main": [
        [
          {
            "node": "Build Request Packages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Request Packages": {
      "main": [
        [
          {
            "node": "Final Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateId": "secrets-provisioner-v1",
    "description": "Receives a list of missing secrets, attempts auto-provisioning where possible (stub), and generates human-readable request packages for manual secrets. Never outputs secret values."
  },
  "tags": [
    "secrets",
    "self-healing",
    "infrastructure"
  ]
}