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 →
{
"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
}
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 →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
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
02_LLM_Pipeline v1.0. Uses executeWorkflowTrigger, httpRequest, seaTable. Event-driven trigger; 65 nodes.
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
[n8n] Advanced URL Parsing and Shortening Workflow - Switchy.io Integration. Uses splitInBatches, stickyNote, httpRequest, html. Event-driven trigger; 56 nodes.
[](https://youtu.be/c7yCZhmMjtI)