DeLoNET n8n Architecture
How to build n8n workflows that are worth building on: atomic nodes with typed
contracts, lifecycle events on the bloodbank bus, safety invariants you can see
on the canvas, and reusable units that earn their place. n8n is meant to be a
visual hub for the internals — not a place to hide a shell script inside one node.
Operating Principles
- One node, one reversible responsibility. A single node that transcribes
AND archives to S3 AND writes to the vault is a god-node. Split it. The tell:
an
executeCommand wrapping a script that quietly does three jobs, giving the
canvas zero visibility into any of them.
- The bus is the completion signal. Pipeline lifecycle events publish to
bloodbank (
bloodbank.evt.<domain>.<entity>.<action>). Never wire ntfy /
Slack / email directly for a pipeline event — the bloodbank-event-toaster
already fans bloodbank.evt.> out to ntfy.delo.sh/bloodbank, so emitting
correctly gives you the notification plus every other consumer for free.
A direct ntfy node bypasses the bus and throws all of that away.
- Schema-first events. Only emit events already defined under
bloodbank/schemas/. If the one you need is missing, author the schema first
(→ bloodbank-integration), then emit. Never invent an ad-hoc payload.
- Safety invariants are topology, not buried logic. "Back up before X",
"never delete the source", "stash on failure" become visible error-branch /
merge structure on the canvas so the guarantee is auditable — not 40 lines
inside a bash function nobody opens.
- Reusable + parameterized beats bespoke. Every atomic action that recurs
across pipelines earns a reusable unit. Promote it up the escalation ladder
as reuse grows.
- Ingress authenticates before it transforms. An external webhook's exact
bytes are provenance. Preserve the raw body, select the narrow credential by
stable sender identity, verify HMAC, then normalize and publish. Never put a
Set/Code transform or workspace guess ahead of signature verification.
Quick Navigation
The Escalation Ladder
Pick the lowest rung that still gives a clean, typed boundary. Promote when a unit
reaches 2+ pipelines or needs credentials / schema validation / a real UI.
How reusable is this atomic action?
├─ One-off glue, this workflow only → inline Code / Set / Execute Command
├─ Reused across 2+ workflows, no creds → subworkflow (Execute Sub-workflow),
│ with an explicit typed input contract
└─ Reused broadly, OR needs credentials /
schema validation / a first-class node UI → custom node: n8n-nodes-<thing> micro-package
Never inline a multi-step responsibility just because executeCommand is fast to
write — that is exactly how god-nodes are born. Splitting first, promoting later,
is cheaper than untangling a monolith under fire.
Cross-Cutting Rules
- Custom nodes are per-node micro-packages, and self-contained. One node = one
npm package
n8n-nodes-<thing>, cloned from the n8n-nodes-hermes scaffold,
installed into ~/.n8n/nodes. Its logic runs in-process in execute() — never
shelling to a host script; build it inside the repo that owns its contract,
generated on deploy. An Execute Command calling a host CLI is the transitional
rung only. → node-catalog.
- n8n has no Dapr sidecar. It runs as a PM2 host process on
:5678; NATS is on
the host at 127.0.0.1:4222. Emit NATS-direct (via the bb-emit CLI) to
bloodbank.evt.<…> — the bloodbank HTTP ingress is v2/RabbitMQ and bypasses
the v3 toaster, and Dapr /publish is unavailable. → bloodbank-emit.
- Plane is the authenticated ingress exception. Both Plane workspace
webhooks enter the same HTTPS workflow, which verifies the raw-body HMAC and
publishes through the custom Bloodbank node. This is not Bloodbank HTTP
/event, a second n8n instance, or the retired port-8477 bridge. →
plane-webhook-ingress.
- Never archive into a subdir of a watched folder.
localFileTrigger will
re-fire forever. Archive off-filesystem (S3) or to a sibling outside the watch
root. → gotchas.
- MCP access is gated. Set
settings.availableInMCP: true or the n8n MCP
tools refuse the workflow. Tags and >255-char descriptions are not settable via
MCP — use direct API PUT /api/v1/workflows/{id} with the
{name, nodes, connections, settings} envelope. → gotchas.
- The source recording is irreplaceable; the transcript is derivable. Back up
the source FIRST, verify it landed, and never delete it. This invariant survives
every refactor. → transcribe-rebuild.
Out of Scope
- Whether a job should be n8n at all (vs a bloodbank consumer, a cron, an
agent hook) →
delonet-workflow-router.
- The event envelope / schema / subject contract itself (CloudEvents fields,
naming convention, versioning) →
bloodbank-integration.
- Infra paths, container/service names, credentials,
s3.delo.sh /
drive.delo.sh endpoints → delonet-conventions.
- n8n node/workflow code syntax (SDK parameter names, expression language) →
the
n8n-* MCP skills (n8n-workflow-patterns, n8n-node-configuration,
n8n-validation-expert, …).
1---2name: delonet-n8n-architecture3description: Architecture principles and the custom-node catalog for building n8n workflows on DeLoNET/33GOD. Use when creating, reviewing, or refactoring ANY n8n workflow for the pipeline — including the canonical signed Plane webhook ingress — deciding node boundaries (atomic single-responsibility nodes vs a god-node executeCommand/localFileTrigger shell script), emitting pipeline lifecycle events to Bloodbank instead of ad-hoc ntfy, choosing community vs custom nodes, packaging custom nodes as n8n-nodes-* micro-packages, rebuilding the inbox/transcribe pipeline, or applying n8n instance conventions (PM2 :5678, raw-body HMAC, per-webhook secret selection, availableInMCP, archive-outside-watch-root, direct API PUT for tags/description). Do NOT use for whether-to-use-n8n-vs-bloodbank routing (delonet-workflow-router), the event schema/envelope contract itself (bloodbank-integration), or infra paths/containers/creds (delonet-conventions).4---56# DeLoNET n8n Architecture78How to build n8n workflows that are worth building on: atomic nodes with typed9contracts, lifecycle events on the bloodbank bus, safety invariants you can see10on the canvas, and reusable units that earn their place. n8n is meant to be a11visual hub for the internals — not a place to hide a shell script inside one node.1213## Operating Principles14151. **One node, one reversible responsibility.** A single node that transcribes16 AND archives to S3 AND writes to the vault is a god-node. Split it. The tell:17 an `executeCommand` wrapping a script that quietly does three jobs, giving the18 canvas zero visibility into any of them.192. **The bus is the completion signal.** Pipeline lifecycle events publish to20 bloodbank (`bloodbank.evt.<domain>.<entity>.<action>`). Never wire ntfy /21 Slack / email directly for a pipeline event — the `bloodbank-event-toaster`22 already fans `bloodbank.evt.>` out to `ntfy.delo.sh/bloodbank`, so emitting23 correctly gives you the notification **plus every other consumer** for free.24 A direct ntfy node bypasses the bus and throws all of that away.253. **Schema-first events.** Only emit events already defined under26 `bloodbank/schemas/`. If the one you need is missing, author the schema first27 (→ `bloodbank-integration`), then emit. Never invent an ad-hoc payload.284. **Safety invariants are topology, not buried logic.** "Back up before X",29 "never delete the source", "stash on failure" become visible error-branch /30 merge structure on the canvas so the guarantee is auditable — not 40 lines31 inside a bash function nobody opens.325. **Reusable + parameterized beats bespoke.** Every atomic action that recurs33 across pipelines earns a reusable unit. Promote it up the escalation ladder34 as reuse grows.356. **Ingress authenticates before it transforms.** An external webhook's exact36 bytes are provenance. Preserve the raw body, select the narrow credential by37 stable sender identity, verify HMAC, then normalize and publish. Never put a38 Set/Code transform or workspace guess ahead of signature verification.3940## Quick Navigation4142| Task | Read |43|---|---|44| Justify/apply the principles + the escalation ladder in depth | [references/principles.md](./references/principles.md) |45| Build, choose, or package a custom/community node | [references/node-catalog.md](./references/node-catalog.md) |46| Emit a bloodbank event from inside an n8n workflow | [references/bloodbank-emit.md](./references/bloodbank-emit.md) |47| Operate or debug the signed Plane → n8n → Bloodbank ingress | [references/plane-webhook-ingress.md](./references/plane-webhook-ingress.md) |48| n8n instance facts + hard-won failure modes | [references/gotchas.md](./references/gotchas.md) |49| Rebuild the transcribe/inbox pipeline (reference implementation) | [references/transcribe-rebuild.md](./references/transcribe-rebuild.md) |5051## The Escalation Ladder5253Pick the lowest rung that still gives a clean, typed boundary. Promote when a unit54reaches 2+ pipelines or needs credentials / schema validation / a real UI.5556```57How reusable is this atomic action?58├─ One-off glue, this workflow only → inline Code / Set / Execute Command59├─ Reused across 2+ workflows, no creds → subworkflow (Execute Sub-workflow),60│ with an explicit typed input contract61└─ Reused broadly, OR needs credentials /62 schema validation / a first-class node UI → custom node: n8n-nodes-<thing> micro-package63```6465Never inline a multi-step responsibility just because `executeCommand` is fast to66write — that is exactly how god-nodes are born. Splitting first, promoting later,67is cheaper than untangling a monolith under fire.6869## Cross-Cutting Rules7071- **Custom nodes are per-node micro-packages, and self-contained.** One node = one72 npm package `n8n-nodes-<thing>`, cloned from the `n8n-nodes-hermes` scaffold,73 installed into `~/.n8n/nodes`. Its logic runs **in-process in `execute()`** — never74 shelling to a host script; build it **inside the repo that owns its contract**,75 generated on deploy. An `Execute Command` calling a host CLI is the transitional76 rung only. → node-catalog.77- **n8n has no Dapr sidecar.** It runs as a PM2 host process on `:5678`; NATS is on78 the host at `127.0.0.1:4222`. Emit **NATS-direct** (via the `bb-emit` CLI) to79 `bloodbank.evt.<…>` — the bloodbank **HTTP ingress is v2/RabbitMQ and bypasses80 the v3 toaster**, and Dapr `/publish` is unavailable. → bloodbank-emit.81- **Plane is the authenticated ingress exception.** Both Plane workspace82 webhooks enter the same HTTPS workflow, which verifies the raw-body HMAC and83 publishes through the custom Bloodbank node. This is not Bloodbank HTTP84 `/event`, a second n8n instance, or the retired port-8477 bridge. →85 plane-webhook-ingress.86- **Never archive into a subdir of a watched folder.** `localFileTrigger` will87 re-fire forever. Archive off-filesystem (S3) or to a sibling outside the watch88 root. → gotchas.89- **MCP access is gated.** Set `settings.availableInMCP: true` or the n8n MCP90 tools refuse the workflow. Tags and >255-char descriptions are not settable via91 MCP — use direct API `PUT /api/v1/workflows/{id}` with the92 `{name, nodes, connections, settings}` envelope. → gotchas.93- **The source recording is irreplaceable; the transcript is derivable.** Back up94 the source FIRST, verify it landed, and never delete it. This invariant survives95 every refactor. → transcribe-rebuild.9697## Out of Scope9899- **Whether a job should be n8n at all** (vs a bloodbank consumer, a cron, an100 agent hook) → `delonet-workflow-router`.101- **The event envelope / schema / subject contract itself** (CloudEvents fields,102 naming convention, versioning) → `bloodbank-integration`.103- **Infra paths, container/service names, credentials, `s3.delo.sh` /104 `drive.delo.sh` endpoints** → `delonet-conventions`.105- **n8n node/workflow code syntax** (SDK parameter names, expression language) →106 the `n8n-*` MCP skills (`n8n-workflow-patterns`, `n8n-node-configuration`,107 `n8n-validation-expert`, …).