# Node design

> A node is one bounded unit of work with one decision, a declared input, a declared output and a gate at its end; three granularities] shows this bounded form…

Page: PAG · Guide
Canonical: https://banes-lab.com/pag/guide#node-design

This section is stop 18 of 102 in the learning route. Previous: [03 - Semantic operations](https://banes-lab.com/pag/guide/tool-invocation.md). Next: [05 - Writing constraints](https://banes-lab.com/pag/guide/writing-constraints.md). It builds on [02 - Document structure](https://banes-lab.com/pag/guide/document-structure.md).

A node is one bounded [unit of work](https://banes-lab.com/records/arch/unit-of-work-pattern.md) with one decision, a declared input, a declared output and a gate at its end; [D1·a three granularities](https://banes-lab.com/pag/guide#node-design-panel-a) shows this bounded form beside the two forms that fail. Data moves between nodes by name. A value is declared before its first use, its scope reaches every later node, and no node reads an output that a later node produces, which is the flow written out in [D1·b contracts in order](https://banes-lab.com/pag/guide#node-design-panel-b) and shown in [D1·e forward flow](https://banes-lab.com/pag/guide#node-design-panel-e). Dividing work into nodes is the project stage of [the loop](https://banes-lab.com/disciplined-methodology/start/the-loop.md). It yields the edges between units, and the order of the nodes follows how the artifact comes to be, as listed in [D1·c verb to stage](https://banes-lab.com/pag/guide#node-design-panel-c), rather than a count chosen in advance. [D1·d split or combine](https://banes-lab.com/pag/guide#node-design-panel-d) shows where a boundary belongs.

### Boundaries, data flow, genesis

Directives poured into one flat block have no repair point and no place a gate can hold. A node halfway through a long document fails, neither you nor the model can say which earlier output it needed, and the repair restarts from the top because no boundary was a real checkpoint. A gate can only check what a node produced, so a node that produces several unrelated things has a gate that checks a list rather than a unit.

For this reason the data flow from one node to the next is a contract, and the order of the nodes follows the genesis of the artifact. The boundaries decide the number of nodes, rather than a number deciding the boundaries. In practice, each node has one decision and ends with the gate that shows the decision was made. A node is split at a repair point, a persistence, a decision, or a condition the next node needs, and steps that succeed or fail together are combined. The nodes are ordered by dependency and by genesis, and every contract names the one prior output it reads and the one output it yields.

To check this, read each contract's input slot and name the earlier node that yields it. A node whose input names nothing from its predecessor is in the wrong place, an input that no node produces is a forward reference, and a node that builds before its input is found is a genesis inversion. A document with one decision has one node, and a gate at the end of it is still worth writing.

Granularity can fail in two directions, and both look tidy. If the nodes are too fine, each gate only checks that one line ran. If they are too coarse, the only gate is at the end, where it can no longer say which step failed. The bounded form has [high cohesion](https://banes-lab.com/records/arch/high-cohesion.md) inside a node and [low coupling](https://banes-lab.com/records/arch/low-coupling.md) across the boundary, so the boundary is a repair point and the result line can name its owner.

Node order follows the genesis of the artifact, which is what makes it derivable rather than chosen. A node never depends on an output from a later stage than the one it realises, because a thing cannot be built before it is found, or checked before it is built. The same rule makes a document orderable as a [directed acyclic graph](https://banes-lab.com/records/arch/directed-acyclic-graph.md), in which a genesis inversion and a forward reference are one defect seen from two sides.

D1·a three granularities

```pag
# too fine · a node per directive, a gate that checks one line ran
# NODE 1 — READ
READ_RESOURCE <config> INTO <held>
# NODE 2 — PICK
SET <name> = <held>.<field>

# too coarse · one node, no recovery point, no gate until the end
# NODE 1 — EVERYTHING
READ_RESOURCE <config> INTO <held>
READ_RESOURCE <records> INTO <rows>
FOR EACH <row> IN <rows>:
COMPOSE_ARTIFACT <shaped> FROM <row> USING <held>.<rules>
PERSIST_ARTIFACT <shaped> TO <output>

# bounded · one decision per node, a gate at each boundary
# NODE 1 — CONFIGURATION   [epistemic · analysis · set-theory · yields: set]
CONTRACT:
input:   <the declaration's objective>
output:  <config>, validated
HANDOFF GATE:
[check] <config> read (evidence: the read returned content)
[check] <config> conforms (evidence: VALIDATE_ARTIFACT against <schema> passed)
[check] <config>.<rules> is non-empty (evidence: a count above zero)
result: pass → NODE 2 | nonconforming → REPAIR (owner: NODE 1) | unknown → BLOCKED

# NODE 2 — TRANSFORMATION  [epistemic · formalisation · computation · yields: procedure]
CONTRACT:
input:   <config> from NODE 1, and nothing else
output:  <shaped-records>
HANDOFF GATE:
[check] one entry per <record> (evidence: the two counts match) over: <records> measured: <shaped> / <records>
[check] every entry conforms to <config>.<rules> (evidence: VALIDATE_ARTIFACT passed on each)
[check] <records> unchanged (evidence: a witness read after the transform)
result: pass → NODE 3 | count mismatch → REPAIR (owner: NODE 2) | unknown → BLOCKED
```

D1·b contracts in order

```pag
# NODE 1 — DISCOVERY   [epistemic · analysis · set-theory · yields: set]
@genesis: existence
CONTRACT:
input:     <the objective's pattern>
transform: DISCOVER_RESOURCES "<pattern>" INTO <files>
output:    <files>
HANDOFF GATE:
[check] <files> is non-empty (evidence: a count above zero)
[check] every <file> matches <pattern> (evidence: the discovery's own filter) over: <files> measured: <matching> / <files>
[check] no <file> lies outside <root> (evidence: every path prefixed by <root>)
result: pass → NODE 2 | empty set → REPAIR (owner: NODE 1) | unknown → BLOCKED

# NODE 2 — ANALYSIS     [epistemic · reasoning · logic · yields: boolean]
@genesis: difference
CONTRACT:
input:     <files> from NODE 1
transform: FOR EACH <file> IN <files>: READ_RESOURCE <file> INTO <content>; ANALYZE_CONTENT <content> AGAINST <pattern> INTO <finding>; APPEND <finding> TO <findings>
output:    <findings>
HANDOFF GATE:
[check] every <file> read (evidence: one content per file) over: <files> measured: <read> / <files>
[check] one <finding> per <file> (evidence: the two counts match)
[check] every <finding> names its <file> (evidence: no finding with an empty source)
result: pass → NODE 3 | unread file → REPAIR (owner: NODE 2) | unknown → BLOCKED

# NODE 3 — REPORTING    [evaluative · representation · information-theory · yields: artifact]
@genesis: structure
CONTRACT:
input:     <findings> from NODE 2 · never anything a later node produces
transform: COMPOSE_ARTIFACT <report> FROM <findings> USING <shape>; PERSIST_ARTIFACT <report> TO <destination>
output:    <report>
freshness: fingerprint(<findings>) + fingerprint(this document)
HANDOFF GATE:
[check] <report> names every entry in <findings> (evidence: each finding's id present) over: <findings> measured: <named> / <findings>
[check] <report> persisted (evidence: a read of <destination> returns it)
[check] <findings> unchanged since NODE 2 (evidence: a witness read)
refuse: <destination> changed since it was read before PERSIST_ARTIFACT
result: pass → TERMINATE | missing entry → REPAIR (owner: NODE 3) | unknown → BLOCKED
```

D1·c verb to stage

```pag
# a verb realises one stage of how an artifact comes to be
# and a node never depends on a later stage than the one it realises
READ, FIND        → existence       does it exist, is it found
ANALYZE, FILTER   → difference      what distinguishes it
EXTRACT, LINK     → relation        what it connects to
CREATE, WRITE     → structure       how its parts are arranged
EXECUTE, ITERATE  → transformation  what operation it performs
VERIFY            → constraint      what bounds it

# a genesis inversion · a decomposition defect, not a tie to break
# NODE 1 — BUILD    COMPOSE_ARTIFACT <base> FROM <signatures>      structure
# NODE 2 — FIND     DISCOVER_RESOURCES <signatures> INTO <found>   existence · needed by NODE 1
```

D1·d split or combine

```mermaid
flowchart TB
split{"Split here?"}
output["The next node needs this node's output"]
retry["This part is repaired on its own"]
human["The developer decides before it continues"]
persist["The result is persisted before it continues"]
verify["A condition must hold before the next node"]
combine{"Combine here?"}
atomic["The steps succeed or fail together"]
partial["A partial result has no value"]
shared["The steps share values that must not outlive them"]
coupled["Separating adds noise, not clarity"]
split --> output
split --> retry
split --> human
split --> persist
split --> verify
combine --> atomic
combine --> partial
combine --> shared
combine --> coupled
```

D1·e forward flow

```mermaid
flowchart LR
n1["Node 1 · existence · yields files"]
n2["Node 2 · difference · reads files, yields findings"]
n3["Node 3 · structure · reads findings"]
n1 -- gate --> n2 -- gate --> n3
n3 -. never a forward reference, never an earlier genesis .-> n1
```

## Links to

- [Unit of Work Pattern](https://banes-lab.com/records/arch/unit-of-work-pattern.md)
- [The loop](https://banes-lab.com/disciplined-methodology/start/the-loop.md)
- [High Cohesion](https://banes-lab.com/records/arch/high-cohesion.md)
- [Low Coupling](https://banes-lab.com/records/arch/low-coupling.md)
- [Directed Acyclic Graph (DAG)](https://banes-lab.com/records/arch/directed-acyclic-graph.md)

## Linked from

- [Document structure](https://banes-lab.com/pag/guide/document-structure.md)
- [Genesis stages](https://banes-lab.com/pag/patterns/genesis-stages.md)
