AutomationFlowsWeb Scraping › Promote Skill (scrub → Review → Commit)

Promote Skill (scrub → Review → Commit)

Promote skill (scrub → review → commit). Uses httpRequest. Event-driven trigger; 9 nodes.

Event trigger★★★★☆ complexity9 nodesHTTP Request
Web Scraping Trigger: Event Nodes: 9 Complexity: ★★★★☆ Added:

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "aicp-promote-skill",
  "name": "Promote skill (scrub \u2192 review \u2192 commit)",
  "meta": {
    "description": "The only sanctioned path for knowledge to move UP the scope tree. Previews the scrubbed skill, sends it through the approval gate (never auto-approved), and commits to the target scope only on explicit approval. The scrubber refuses non-ancestor targets and re-checks for raw PII at commit. Set scope/file/toScope in the Config node and the approval-gate workflow ID in the Request approval node."
  },
  "nodes": [
    {
      "parameters": {},
      "id": "acacacac-0000-0000-0000-000000000001",
      "name": "When clicking Test",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        220,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// What to promote, from where, to where (must be an ancestor scope).\nreturn [{ json: {\n  scope: 'data-team',\n  file: 'example-skill.md',\n  toScope: 'engineering'\n} }];"
      },
      "id": "acacacac-0000-0000-0000-000000000002",
      "name": "Config",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        420,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://scrubber:8080/promote/preview",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-scrubber-token",
              "value": "change-me-to-a-long-random-secret"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { \"scope\": $json.scope, \"file\": $json.file, \"toScope\": $json.toScope } }}",
        "options": {}
      },
      "id": "acacacac-0000-0000-0000-000000000003",
      "name": "Preview (scrubbed)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        620,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Typed approval contract (G3).\nconst i = $input.first().json;\nconst c = $('Config').first().json;\n\nreturn [{ json: {\n  action: `Promote skill ${c.file || '(unnamed)'} from scope ${c.scope} to ${c.toScope}`,\n  scope: `${c.scope} -> ${c.toScope}`,\n  riskTier: 'high',\n  // Promotion widens who can see a skill. That is the risk, stated as the thing\n  // being approved rather than left for the approver to infer.\n  dimensions: `access=widened (${c.scope} -> ${c.toScope}), autonomy=promoted-artifact`,\n  requester: 'promote-skill lane (manual trigger)',\n  dataTouched: `${c.file}; scrubbed preview below is what will be committed`,\n  preview: String(i.preview || i.message || '(no preview generated)').slice(0, 4000),\n  autoApprove: false,\n} }];\n"
      },
      "id": "acacacac-0000-0000-0000-000000000004",
      "name": "Build review request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        820,
        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": "acacacac-0000-0000-0000-000000000005",
      "name": "Request approval",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.2,
      "position": [
        1020,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true
          },
          "conditions": [
            {
              "leftValue": "={{ $json.approved }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "acacacac-0000-0000-0000-000000000006",
      "name": "Approved?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1220,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://scrubber:8080/promote/commit",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-scrubber-token",
              "value": "change-me-to-a-long-random-secret"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { \"scope\": $('Config').first().json.scope, \"file\": $('Config').first().json.file, \"toScope\": $('Config').first().json.toScope } }}",
        "options": {}
      },
      "id": "acacacac-0000-0000-0000-000000000007",
      "name": "Commit promotion",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1420,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "const r = $input.first().json;\nreturn [{ json: {\n  severity: 'info',\n  title: `Skill promoted: ${r.from} \u2192 ${r.to}`,\n  message: `Written: ${r.written}\\nProvenance header added.`,\n  source: 'promote-skill'\n} }];"
      },
      "id": "acacacac-0000-0000-0000-000000000008",
      "name": "Summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1620,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "const gate = $('Request approval').first().json;\nconst cfg = $('Config').first().json;\nreturn [{ json: {\n  severity: 'warn',\n  title: `Skill promotion NOT committed: ${cfg.file}`,\n  message: gate.timedOut ? 'Approval timed out \u2014 default deny.' : 'Rejected by reviewer.',\n  source: 'promote-skill'\n} }];"
      },
      "id": "acacacac-0000-0000-0000-000000000009",
      "name": "Rejected summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1420,
        420
      ]
    }
  ],
  "connections": {
    "When clicking Test": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "Preview (scrubbed)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preview (scrubbed)": {
      "main": [
        [
          {
            "node": "Build review request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build review request": {
      "main": [
        [
          {
            "node": "Request approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request approval": {
      "main": [
        [
          {
            "node": "Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Approved?": {
      "main": [
        [
          {
            "node": "Commit promotion",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Rejected summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Commit promotion": {
      "main": [
        [
          {
            "node": "Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {},
  "active": false
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Promote skill (scrub → review → commit). Uses httpRequest. Event-driven trigger; 9 nodes.

Source: https://github.com/jgobuilds/ai-control-plane-public/blob/main/n8n-workflows/promote-skill.workflow.json — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

This workflow listens for an “Approved” label on a Trello card, reads the AI draft bookkeeping JSON from card comments, and posts the corresponding transaction to Xero. It then adds a Xero deep link b

Trello Trigger, HTTP Request
Web Scraping

02_LLM_Pipeline v1.0. Uses executeWorkflowTrigger, httpRequest, seaTable. Event-driven trigger; 65 nodes.

Execute Workflow Trigger, HTTP Request, Sea Table
Web Scraping

This workflow allows you to import any workflow from a file or another n8n instance and map the credentials easily. A multi-form setup guides you through the entire process At the beginning you have t

Execute Command, Read Write File, HTTP Request +3
Web Scraping

[n8n] Advanced URL Parsing and Shortening Workflow - Switchy.io Integration. Uses splitInBatches, stickyNote, httpRequest, html. Event-driven trigger; 56 nodes.

HTTP Request, GitHub, Stop And Error +1
Web Scraping

[](https://youtu.be/c7yCZhmMjtI)

HTTP Request, GitHub, Stop And Error +1