Views of the same system, from the trust boundary outwards, from two sources.
architecture.png — the canonical view, the image at the top of the README — is
laid out by hand in grapharc-architecture.drawio,
whose other two pages cover the trust boundary and the import graph. Edit it at
app.diagrams.net (File → Open From → Device) and
re-export page 0 as architecture.png.
Everything numbered is generated instead:
uv pip install diagrams # not a project dependency; `uv sync` will drop it
sudo apt install graphviz # provides `dot`
.venv/bin/python docs/diagrams/architecture.py| view | what it answers | |
|---|---|---|
| 0 | architecture.png |
the whole system in one frame — start here |
00-architecture.png |
the same view, rendered by architecture.py through Graphviz |
|
| 1 | 01-lifecycle.png |
trigger → outcome, and the loop back through the gate (§1, §2) |
| 2 | 02-planes.png |
what every node sits on, and what constrains it (§3) |
| 3 | 03-agent-node.png |
inside an agent node, gate by gate (§4) |
| 4 | 04-subsystems.png |
the twelve packages, and which ones actually import which |
| 5 | 05-trust-boundary.png |
who supplies what — the operator declares, the model proposes, the checker decides |
View 0 is the canonical one — the whole runtime on one spine, and the image at the top of the README. The five that follow each answer a single question in more depth. The two renderings of view 0 are the same diagram: architecture.png is the hand-laid one the README embeds, 00-architecture.png what architecture.py produces.
View 5 is the one to read after it. The other four show structure; this one
shows the boundary, which is the only question that decides whether the rest is
a safety argument or decoration. An operator authors the registry, the policy, the
schema and the budgets — all before a model runs. The model contributes exactly
one thing: JSON naming kinds and edges. It cannot supply a node body (extra= "forbid"), cannot smuggle a callable (not JSON-serialisable, so it cannot be
fingerprinted and never reaches the gate), cannot grant itself budget, and cannot
add a kind (the registry is frozen).
View 1 is the same claim as a lifecycle: the two coloured curves back to ③ PLAN are what make this a governed loop rather than a pipeline. Rejections return as traced reason codes, and work discovered mid-run re-enters admission — there is no already-approved path.
View 4 is drawn from the import graph, not from intent. It used to show
planner/ in its own box with arrows leaving and none arriving —
the largest gap this project had, rendered rather than described. grapharc plan
closed it, so the box is now reachable and the arrow into it is the point.
server/ carries the one remaining gap marker. Re-derive before trusting the
picture — these are a snapshot and rot the same way prose does:
grep -rn "grapharc.planner" grapharc/ --include=*.py | grep -v "^grapharc/planner/"Line counts in view 4 come from find grapharc/<pkg> -name '*.py' | xargs cat | wc -l.