Context and Problem Statement
The doctrine artefact model — agent profiles, mission-type/step contracts, the DRG
(DRGNode/DRGEdge/NodeKind/Relation), and the ArtifactKind vocabulary — is
documented in prose across doctrine-kinds.md, mission-type-resolution.md, and
doctrine-relationships.md. It has no schema diagrams: a reader cannot see the shape
of a bound model at a glance, and prose alone does not make a closed field set legible.
Two facts constrain any fix.
- The docsite renders Mermaid but not PlantUML. The DocFX pipeline runs an ordered
scripts/docs/HTML post-processing chain overdocs/_site(glossary_linker.pyis the established pattern). Mermaid renders client-side; a```plantuml-fenced@startyamlblock does not render at all. So the diagram genre best suited to typed schema depiction — PlantUML@startyaml— has no rendering path today. - These diagrams must not drift from the code, and they must not send doctrine content off-machine to be drawn. A schema diagram that is hand-copied, or rendered by a remote PlantUML server, fails one or the other: it drifts, or it egresses.
This ADR records the rendering decision (Scope B, mission doctrine-schema-diagrams,
FR-001/FR-002), the reasoning that positions these schema diagrams as a new genre
rather than an extension of the existing C4 model, and the reconciliation with R-04 in
docs/architecture/diagrams/README.md so the two
governance surfaces do not contradict.
The governing toolguide for the mechanism is the active plantuml-diagramming
toolguide (packs/built-in/toolguides/plantuml-diagramming.toolguide.yaml), activated in
the charter's Writing, Communication & Diagramming Doctrine (2026-08-08) alongside
USE_C4_MODEL_TECHNIQUES and the mermaid-diagramming toolguide. That activation is
charter-prose active — a governing directive read from the charter document — not a
runtime-resolved artefact loaded through the doctrine chain; this ADR cites it by name as
the source of the how-to, and does not depend on runtime resolution of it.
Decision Drivers
- Zero doctrine-content egress (C-001, NFR-002). The content being drawn is the project's own doctrine model. It must be rendered on-machine, and the renderer must be provably unable to reach the network — proven behaviourally, not by a flag.
- Generated fidelity over source-view convenience (C-002). A diagram that is generated from the frozen code model and guarded against drift is worth more than one that renders on github.com's raw source view. We accept docsite-only rendering to get fidelity.
- Reproducibility (NFR-003). The rendering binary must be pinned by version and sha256, and the diagrams must be CI-generated, not committed as SVGs that can rot.
- Slot into the existing chain, disturb nothing. The step must run inside the
established
scripts/docs/post-processing order and leave the existing Mermaid/C4 diagrams (R-04) rendering unchanged (NFR-004). - Keep the genre boundary honest. These diagrams depict code models, not architecture zoom levels. Conflating them with the C4 lane would misdirect both authors and the drift guard.
Decision
Render @startyaml doctrine schema diagrams at docs-build time, locally, with a pinned
binary inside a network-denied container, as a post-processor after glossary_linker.
- Pinned binary, sandboxed engine. A single
plantuml.jar, pinned by version and sha256, is verified before use (a sha256 mismatch fails the build before any render from an unverified binary). It is invoked with-DPLANTUML_SECURITY_PROFILE=SANDBOX, and SANDBOX is proven behaviourally: a diagram carrying an egress directive (!include/!includeurl) must fail-closed under it. - Rendering runs only inside a digest-pinned JRE container under
docker run --network=none. The jar never runs host-native. The container image is pinned by digest;--network=nonedenies the render process any network namespace, so even a hypothetical SANDBOX bypass cannot reach the wire. This is the portable, hard egress gate on CI-Linux (the runners have Docker);unshare -rnis not reliable on Ubuntu-24.04 runners (apparmor_restrict_unprivileged_userns=1) and is therefore not the primary mechanism. - Python orchestration is host-native and stdlib-only. The orchestrator
(
scripts/docs/plantuml_render.py) recovers```plantuml-fenced@start*blocks from the builtdocs/_siteHTML (HTML-unescaping the payload; asserting the emittedlanguage-plantumlclass), shells out to the pinned container to render each block to SVG, and injects the SVG with descriptive alt/aria text. The orchestrator itself does no network I/O and pulls in no third-party dependency; only the isolated container does the drawing. - Positioned after
glossary_linkerin both docs workflows. The step runs afterglossary_linkerin the PR gate (docs-build-pr.yml) and in the deploy pipeline (docs-pages.yml, including itspaths:allowlist), so glossary linking has already run over the HTML before diagrams are injected. - Fidelity is enforced by the drift guard, not by hand. Each diagram is bound to its
source model by an explicit
file:classtable and checked by the FR-004 drift guard, which introspects the live model (Pydanticmodel_fieldswithFieldInfo.alias or namenormalization and transitive nested recursion; frozen-dataclassfields(); StrEnum members vialist(...)— never a hand-copied count) and fails on any field-set mismatch. The guard is the control that lets us treat a generated diagram as authoritative.
Schema diagrams are a new genre, distinct from C4 progressive-zoom
The charter's USE_C4_MODEL_TECHNIQUES directive governs architecture diagrams via
the C4 model's progressive zoom — System Context → Container → Component → Code — where
each level serves one audience at one altitude. Those diagrams are hand-authored and
live under docs/architecture/diagrams/ as the
living C4 model (R-04).
The doctrine schema diagrams introduced here are a different genre. They depict the shape of a code model — the fields, types, and enum members of a frozen artefact schema — not an architecture zoom level. They are not a fifth C4 tier and they are not a finer-grained "Code" level of the architecture C4 set; they answer "what does this model's field set look like?", a question C4 never asks. They are generated from the frozen models (not hand-authored), drift-guarded (not narrative), and docsite-only (not source-view). Treating them as their own genre keeps the C4 lane's hand-authored, GitHub-renderable convention intact while giving schema depiction its own rules.
Accessibility carve-out (NFR-005)
The docs-accessibility styleguide requires that a diagram's facts be restated in
prose so the page is legible without seeing the image. For these schema diagrams that
duty is discharged by the surrounding doctrine-kinds prose — the kind descriptions and
model explanations already on the page — not by re-listing every field beside the
diagram. Re-listing the fields would recreate exactly the hand-maintained field inventory
that C-005 forbids: a second copy of the schema that drifts from both the code and the
generated diagram. Each injected SVG still carries descriptive, non-trivial alt/aria text
derived from the diagram's @startyaml title/caption (asserted by the render pipeline), so
the identity and purpose of each diagram is available to assistive technology; the
exhaustive field facts live once, in the code model, surfaced through the generated
diagram and narrated by the existing prose.
Consequences
Positive
- Doctrine schema shape becomes legible on the docsite, generated from the single source of truth (the frozen models) and unable to drift past the FR-004 guard.
- No doctrine content leaves the machine: the render runs in a
--network=nonecontainer with a SANDBOX engine, both proven behaviourally. - The rendering binary is reproducible (version + sha256; digest-pinned image), and SVGs are CI-generated rather than committed and left to rot.
- The existing hand-authored C4 lane (R-04) and its Mermaid/C4 diagrams are untouched (NFR-004); the genre boundary is explicit, so authors know which lane a new diagram belongs to.
Negative / accepted trade-offs
- Docsite-only rendering (C-002). Pre-rendered SVGs appear only on the built docsite,
not on github.com's source view of the Markdown. A reader browsing the raw doctrine
pages on GitHub sees the
```plantumlfence, not a picture. We accept this: the value is generated fidelity, and the fidelity control (the drift guard) only exists on the build path. This is the deliberate inverse of the C4 lane's R-04 convention, which chooses GitHub-source rendering (Mermaid, no build tooling) over generation. - Build-time and Docker dependency. The docs build now shells out to Docker and adds render time (target ≤ 60s, a monitored budget/warning, not a hard per-PR gate, per the flakiness policy). A runner without Docker cannot render these diagrams — acceptable because CI-Linux (which has Docker) is the hard gate.
- A new lane to govern. There are now two diagram lanes with different conventions (hand-authored/source-rendered C4 vs generated/docsite-only schema). The R-04 amendment and this ADR cross-link so the boundary is stated once and identically in both places.
Reconciliation with R-04 (and #1839)
R-04 in docs/architecture/diagrams/README.md
records that deterministic diagram generation (Structurizr/PlantUML) is out of scope
for the living C4 model, which stays hand-authored so it renders on GitHub. That line is
unchanged for hand-authored C4 architecture diagrams — the C4 lane keeps its
convention. This ADR opens a separate, narrowly-scoped lane: generated,
docsite-only schema diagrams of code models, governed here and by the
plantuml-diagramming toolguide. The two do not contradict because they cover different
genres: R-04's "generation out of scope" applies to the C4 architecture model; the new
lane applies only to drift-guarded schema depictions of frozen doctrine models. The R-04
entry is amended to state this carve-out and cross-link this ADR.
References
- Mission spec:
kitty-specs/doctrine-schema-diagrams-01KZTQTH/spec.md— FR-001, FR-002, NFR-002, NFR-003, NFR-005, C-001, C-002, C-004, C-005, C-006. - Research:
kitty-specs/doctrine-schema-diagrams-01KZTQTH/research.md— D1 (rendering), D2 (drift guard), D4 (ADR / R-04 reconciliation). - Toolguide (charter-prose active):
plantuml-diagramming(packs/built-in/toolguides/plantuml-diagramming.toolguide.yaml). - Charter:
.kittify/charter/charter.md§ Writing, Communication & Diagramming Doctrine —USE_C4_MODEL_TECHNIQUES,mermaid-diagramming+plantuml-diagrammingtoolguides,docs-accessibility. - Amended R-04 entry:
docs/architecture/diagrams/README.md. - Upstream generation-scope issue this carve-out refines:
#1839(deduped vs#1812).