{
  "name": "Secrets Registry Verify",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "secrets-registry-verify",
        "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": "// Load manifest (inline; sync with n8n/secrets.manifest.yml). Validate schema.\nconst manifest = {\n  version: '1.0',\n  secrets: [\n    { name: 'N8N_API_KEY', source: 'ENV', required: true },\n    { name: 'N8N_ENCRYPTION_KEY', source: 'ENV', required: true },\n    { name: 'SUPABASE_SERVICE_ROLE_KEY', source: 'ENV', required: true },\n    { name: 'ANTHROPIC_API_KEY', source: 'ENV', required: true },\n    { name: 'OPENAI_API_KEY', source: 'ENV', required: true },\n    { name: 'MONDAY_API_TOKEN', source: 'ENV', required: false },\n    { name: 'CLOUDFLARE_API_TOKEN', source: 'ENV', required: false },\n    { name: 'COMMANDER_WEBHOOK_TOKEN', source: 'ENV', required: true },\n    { name: 'VAULT_MASTER_KEY', source: 'ENV', required: false },\n    { name: 'FIRECRAWL_API_KEY', source: 'ENV', required: false },\n    { name: 'GRAFANA_PASSWORD', source: 'ENV', required: false }\n  ],\n  n8n_credentials: [\n    { type: 'supabaseApi', name: 'Prod - Supabase Service Role' },\n    { type: 'anthropicApi', name: 'Prod - Anthropic Claude' },\n    { type: 'anthropicApi', name: 'AnthropicApi' },\n    { type: 'openAiApi', name: 'Prod - OpenAI' },\n    { type: 'gmailOAuth2', name: 'Prod - Gmail' },\n    { type: 'googleSheetsOAuth2Api', name: 'Prod - Google Sheets' },\n    { type: 'mondayComApi', name: 'Prod - Monday.com' }\n  ]\n};\nconst schemaOk = manifest.version && Array.isArray(manifest.secrets) && manifest.secrets.every(s => s.name && s.source && s.required !== undefined);\nif (!schemaOk) throw new Error('Manifest schema invalid');\nreturn [{ json: manifest }];"
      },
      "id": "load-manifest",
      "name": "Load Manifest",
      "type": "n8n-nodes-base.code",
      "position": [
        480,
        400
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "jsCode": "// Check ENV secrets by inspecting process.env.\n// NEVER output actual values \u2014 only presence.\nconst manifest = $input.first().json;\nconst results = { ok: true, missing: [], present: [], warnings: [] };\n\nfor (const secret of manifest.secrets) {\n  if (secret.source === 'ENV') {\n    const val = process.env[secret.name];\n    if (!val || val.startsWith('paste-your-')) {\n      if (secret.required) {\n        results.missing.push(secret.name);\n        results.ok = false;\n      } else {\n        results.warnings.push(`${secret.name} (optional) not set`);\n      }\n    } else {\n      results.present.push(secret.name);\n    }\n  }\n}\n\nresults.env_checked = manifest.secrets.filter(s => s.source === 'ENV').length;\nreturn [{ json: results }];"
      },
      "id": "check-env-secrets",
      "name": "Check ENV Secrets",
      "type": "n8n-nodes-base.code",
      "position": [
        720,
        300
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "jsCode": "// Check VAULT_REF secrets via vault adapter (existence only).\nconst vaultBase = process.env.VAULT_BASE_URL || 'http://localhost:4000';\nconst envResults = $input.first().json;\nconst manifest = $('Load Manifest').first().json;\nconst vaultNames = (manifest.secrets || []).filter(s => s.source === 'VAULT_REF').map(s => s.name);\nif (vaultNames.length === 0) vaultNames.push('VAULT_MASTER_KEY');\nconst vaultResults = { vault_checked: 0, vault_present: [], vault_missing: [] };\nfor (const name of vaultNames) {\n  if (envResults.present && envResults.present.includes(name)) continue;\n  try {\n    const resp = await fetch(`${vaultBase}/secret/${name}`);\n    vaultResults.vault_checked++;\n    if (resp.ok) vaultResults.vault_present.push(name);\n    else vaultResults.vault_missing.push(name);\n  } catch (e) {\n    vaultResults.vault_missing.push(name);\n    envResults.warnings = envResults.warnings || [];\n    envResults.warnings.push(`Vault unreachable for ${name}: ${e.message}`);\n  }\n}\nreturn [{ json: { ...envResults, ...vaultResults } }];"
      },
      "id": "check-vault-secrets",
      "name": "Check Vault Secrets",
      "type": "n8n-nodes-base.code",
      "position": [
        960,
        300
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "jsCode": "// Check n8n credentials via the n8n API (existence only, names only).\nconst report = $input.first().json;\nconst manifest = $('Load Manifest').first().json;\nconst n8nBase = (process.env.N8N_BASE_URL || process.env.N8N_URL || '').replace(/\\/$/, '');\nconst n8nApiKey = process.env.N8N_API_KEY;\nconst credResults = { creds_checked: 0, creds_present: [], creds_missing: [] };\nconst credList = manifest.n8n_credentials || [];\n\nif (n8nBase && n8nApiKey) {\n  try {\n    const resp = await fetch(`${n8nBase}/api/v1/credentials`, {\n      headers: { 'X-N8N-API-KEY': n8nApiKey, 'Accept': 'application/json' }\n    });\n    if (resp.ok) {\n      const data = await resp.json();\n      const existingNames = (data.data || []).map(c => c.name);\n      for (const cred of credList) {\n        credResults.creds_checked++;\n        if (existingNames.includes(cred.name)) {\n          credResults.creds_present.push(cred.name);\n        } else {\n          credResults.creds_missing.push(cred.name);\n        }\n      }\n    } else {\n      report.warnings.push(`n8n API returned ${resp.status}`);\n    }\n  } catch (e) {\n    report.warnings.push(`n8n API unreachable: ${e.message}`);\n  }\n} else {\n  report.warnings.push('N8N_BASE_URL or N8N_API_KEY not set \u2014 skipping credential check');\n}\n\nif (credResults.creds_missing.length > 0) report.ok = false;\n\nreturn [{ json: { ...report, ...credResults } }];"
      },
      "id": "check-n8n-creds",
      "name": "Check n8n Credentials",
      "type": "n8n-nodes-base.code",
      "position": [
        1200,
        300
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "jsCode": "// Build final report. Never include secret values. Gate 4: ok, missing[], warnings[], checked_at, version_sha.\nconst r = $input.first().json;\nconst manifest = $('Load Manifest').first().json;\nconst allMissing = [...(r.missing || []), ...(r.vault_missing || []), ...(r.creds_missing || [])];\nconst report = {\n  ok: r.ok,\n  missing: allMissing,\n  warnings: r.warnings || [],\n  checked_at: new Date().toISOString(),\n  version_sha: manifest.version || '1.0',\n  summary: r.ok ? 'All required secrets and credentials are present.' : 'Missing secrets or credentials detected.',\n  env: { checked: r.env_checked || 0, present: r.present || [], missing: r.missing || [] },\n  vault: { checked: r.vault_checked || 0, present: r.vault_present || [], missing: r.vault_missing || [] },\n  n8n_credentials: { checked: r.creds_checked || 0, present: r.creds_present || [], missing: r.creds_missing || [] },\n  all_missing: allMissing\n};\nreturn [{ json: report }];"
      },
      "id": "build-report",
      "name": "Build Report",
      "type": "n8n-nodes-base.code",
      "position": [
        1440,
        300
      ],
      "typeVersion": 2
    }
  ],
  "connections": {
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Load Manifest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Load Manifest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Manifest": {
      "main": [
        [
          {
            "node": "Check ENV Secrets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check ENV Secrets": {
      "main": [
        [
          {
            "node": "Check Vault Secrets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Vault Secrets": {
      "main": [
        [
          {
            "node": "Check n8n Credentials",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check n8n Credentials": {
      "main": [
        [
          {
            "node": "Build Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateId": "secrets-registry-verify-v1",
    "description": "Validates all secrets from the manifest against ENV, Vault adapter, and n8n Credentials API. Returns a report with missing/present status. Never outputs secret values."
  },
  "tags": [
    "secrets",
    "self-healing",
    "infrastructure"
  ]
}