overview / architecture-diagram-generation

Architecture Diagram Generation

Last updated at 2026-07-02 00:00 AEST.

A walkthrough of how the platform turns the typed diagram specifications in a generated solution model into rendered, durable architecture-diagram images β€” the cloud-architecture pictures embedded in the solution overview and carried into the final proposal document. The pipeline is fully in-house: the solution-model agent authors each diagram as a structured JSON object of nodes and edges; a validation step enforces its integrity; and a self-hosted render engine lays it out with elkjs, draws a hand-authored SVG, rasterises a PNG, and uploads both to durable storage. No external diagramming service is involved.

At a glance

A handful of numbers capture the shape of the pipeline β€” what it accepts, how far it fans out, and the limits it runs under:

0
external diagram SaaS dependencies
6
max diagrams rendered per run
2
image formats per diagram (SVG + PNG)
60 s
render round-trip budget
3
node status classes (scope provenance)
52
curated icon-catalog names

Overview

Architecture-diagram generation is a sub-pipeline of solution generation. The solution-generation workflow first produces a structured solution model whose diagrams array describes each diagram abstractly β€” a typed diagramSpec of nodes, edges, and an optional title. This pipeline validates those specifications and produces finished, embeddable pictures. The characteristics below describe what the code actually does.

Specification-driven

The input is the solution model's diagrams array β€” each a typed diagramSpec (nodes + edges + title), not free-text diagram source. The agent decides what to draw as structured JSON; the engine deterministically decides how it looks.

In-house render engine

A self-hosted graph-diagram-renderer service lays the spec out with elkjs, draws a hand-authored SVG, and rasterises a PNG with resvg β€” a deterministic function of the input, with no third-party SaaS in the path.

Two formats, one render

A single render call returns both an SVG (the canonical web object, embedded in the solution overview) and a PNG (the downloadable-proposal variant), so the two never drift out of sync.

Strict validation boundary

Every spec is checked against diagramSpecSchema β€” unique node ids and every edge endpoint resolving to a declared node. There is no repair loop: a spec either satisfies the invariants or the diagram deterministically fails.

Deterministic failure classification

An unrenderable spec, a render timeout, or an engine defect is a deterministic failure (the subscriber acks β€” a replay would reproduce it); only a genuine transport fault is transient (the subscriber nacks and Pub/Sub redelivers).

Durable storage

Both rendered formats are streamed straight into the organization's Google Cloud Storage. The SVG object is the canonical reference; the PNG sibling shares its key, differing only by extension.

Generated once, reused

Diagrams are rendered during solution generation and persisted with the solution. The proposal workflow loads the pre-rendered diagrams rather than regenerating them.

Idempotent and resumable

Each step short-circuits when it already completed, rehydrating its diagram set from the persisted step result, so a retry never re-pays for work that already succeeded.

Status-aware theming

Every node carries a scope status β€” Committed, CurrentState, or RequiresCommercialValidation β€” that the engine colours, so provenance is a typed field the renderer honours, never a label suffix.

Self-describing icon catalog

A node's icon is a bare catalog name resolved to a bundled SVG at render time. An unknown name falls back to a generic icon β€” a missing icon never fails a render.

Network-gated service

The render service is a non-public Cloud Run service reachable only from the API over the VPC (internal load balancer, IAM-invoker-disabled) β€” it never accepts public traffic.

Multi-tenant and audited

Every render is scoped to the caller's organization, stored under an org/deal-prefixed path, and persisted as an org-scoped SolutionModelDiagram row linked to a backing Asset.

The remainder of this document walks the flow end to end, from the diagram specifications the solution model emits through to where the finished image is stored and embedded.

1. Where It Runs

The two diagram steps physically live under proposal-generation/workflows/proposal-generation-workflow/steps/ (so they can be shared), but only one workflow wires them in, and another deliberately leaves them out:

  • Solution generation (solutionGenerationWorkflow) β€” the canonical path. It validates the diagram specs (architectureDiagramGenerationStep), then renders and uploads the images (architectureDiagramRenderStep), persisting them with the solution model.
  • Proposal generation (proposalGenerationWorkflow) β€” does not generate diagrams at all. It loads the pre-rendered diagrams from the persisted solution via loadPersistedSolutionStep; a missing solution fails the run fast rather than regenerating inline.

Render and upload are a single step: architectureDiagramRenderStep renders each spec to both formats and streams them to storage in the same pass — there is no separate upload step. A standalone text→diagram capability (diagram-generation) reuses the same render engine from a free-text prompt; the rest of this document describes the canonical, persistence-backed solution-generation flow.

2. The Input: Diagram Specifications

Diagram generation never starts from scratch. Its input is the diagrams array on the structured solution model, produced earlier in the same workflow by the solution-model-generation agent. Each entry is a solutionDiagramSchema object:

FieldTypePurpose
idstringStable per-diagram identifier β€” keys the render, the storage object, and the persisted row.
typeenumOne of Component, Dataflow, Deployment, Sequence β€” the kind of diagram.
specification.diagramSpecobjectThe typed diagram itself β€” nodes, edges, and an optional title. This is the substance that gets rendered.
titlestring?Optional human title, threaded into the persisted row and the overview.
descriptionstring?Optional prose context, carried as a document-model text run.
mappedRequirementsstring[]?Verbatim requirement ids this diagram addresses (traceability; not used by the render).

The diagramSpec is a typed JSON object, not diagram source in any DSL. Its shape is the single source of truth in diagram-spec.schemas.ts:

ElementShapeNotes
node{ id, label, status, shape, icon?, group? }shape is box (default) or cylinder (a datastore); status is the node's scope provenance; group lays related nodes inside one titled container.
edge{ from, to, kind, label? }from/to are node ids; kind is flow (active directional stream, drawn as a marching animation) or dependency (a logical link, drawn static dashed).
titlestring?Optional diagram title drawn in a band above the graph.

How the spec is authored

The diagramSpec is written by the solution-model-generation agent β€” the "Solution Architect for 1KE." It is not a dedicated diagram model: diagrams are one facet of a single structured Solution Model JSON object the agent emits in one call, alongside the solution's components, the integrations between them (typed fromComponentId β†’ toComponentId edges), architecture decision records, NFR mappings, and security controls. The agent is the platform's "design brain," so the diagram spec and the component graph it depicts are authored together and stay consistent by construction.

Where the diagram spec is authored deal context requirements proposal context committed solution solution-model agent frontier Β· Solution Architect solution model diagrams[]: typed spec
The diagram spec is one output of the frontier-tier design agent, grounded in the same inputs as the rest of the solution. Grey = input data, purple = LLM agent work, neutral = the emitted model.
  • Model tier. This design step runs on the frontier fallback chain (FRONTIER_MODEL_FALLBACKS) β€” deciding what to draw is the reasoning-heavy work, and the agent emits the finished typed spec directly, so there is no separate diagram-code model or rewrite pass.
  • Structured, not free text. The agent authors well-formed JSON conforming to the spec schema β€” no DSL, no syntax to get right. Shared authoring guidance (DIAGRAM_SPEC_AUTHORING_GUIDANCE and DIAGRAM_ICON_AUTHORING_GUIDANCE) is embedded verbatim by every diagram-authoring prompt, so the well-formedness and icon-vocabulary rules live in exactly one place and cannot drift.
  • Authored from the committed design. Node labels are required to resolve to the same committed elements the model is built from β€” a selected vendor / product family, or a backing service entry β€” so the picture depicts the solution being proposed, not an invented one.

Grounding rules the spec must obey

Because the spec is LLM-authored, the prompt constrains it heavily so a diagram can never overstate scope or blur current state with target architecture:

  • Scope provenance is a typed field. Every node carries a status β€” Committed, CurrentState, or RequiresCommercialValidation β€” and the engine colours the node by it. Scope is never encoded as a "(current-state)" / "(dependency)" suffix on the label; the typed field carries it.
  • Commercial grounding. A node whose capability has no backing committed service is not priced, in-scope work β€” it is modelled as RequiresCommercialValidation, never drawn as committed.
  • Out-of-scope boundaries. Uncoverable themes must never appear as committed nodes; at most they are a single ungrouped current-state / dependency boundary node.
  • Referential integrity. Every edge's from and to must be the id of a declared node. Unlike a free-text DSL, a dangling endpoint is not silently drawn as a phantom node β€” it is a fatal validation error that discards the whole diagram, so the prompt calls it out as the single most important rule.
  • Restrained motion and labels. flow is reserved for genuine directional data/traffic streams (so the marching animation keeps meaning); structural links default to dependency. Edge labels are optional and kept to one or two words so parallel edges never overlap into unreadable text.

3. Stage 1 β€” Spec Generation & Validation

The first step, architectureDiagramGenerationStep, reads the specs the solution-model agent authored and turns them into validated, render-ready diagram carriers. There is no LLM call here and no image work β€” it is the deterministic validation boundary between authoring and rendering.

Per-diagram spec validation solutionModel.diagrams capped at 6 (soft slice) diagram 1 strict schema check diagram 2 strict schema check diagram N strict schema check unique node ids Β· every edge endpoint resolves Β· no repair loop carried diagram specs, ready to render
One strict validation per diagram, joined into a single carried array. Blue = deterministic code, grey = the source data, neutral = the merged result.

The fan-out and cap

The step reads solutionModel.diagrams and caps the set at MAX_DIAGRAMS_PER_PROPOSAL (6) with a soft slice β€” an over-generating upstream model is truncated rather than hard-failing, because each diagram becomes its own in-process render and GCS upload downstream. Each surviving diagram is validated in turn and its parsed spec carried forward in workflow state.

The validation

Each diagram's specification.diagramSpec is parsed against the strict diagramSpecSchema. The base object schema (carried in the solution model) enforces only shape, so a single dangling edge does not discard the entire model; the strict schema enforces the per-diagram integrity invariants at this boundary:

  1. Present. A diagram with no diagramSpec is a deterministic failure β€” there is nothing to render.
  2. Unique node ids. No two nodes may share an id; a duplicate is a validation error.
  3. Edge referential integrity. Every edge from and to must reference a declared node id. A dangling endpoint fails the diagram.

No repair loop. The step makes no LLM call and never attempts to fix a bad spec. A spec either satisfies the invariants or the step raises a structured validation error. Because such a failure reproduces on replay, it is classified deterministic: the Pub/Sub subscriber acks rather than nack-looping and re-paying for a failure that can never succeed.

The parsed spec is carried forward per diagram (with its diagramId, diagramType, and optional title / description) and the set is written into the step result so a resumed run can rehydrate it without re-validating.

4. Stage 2 β€” In-House Render & Durable Upload

The second step, architectureDiagramRenderStep, walks the carried diagram set sequentially and, for each spec, renders both image formats and streams them into durable storage in a single pass.

Render-and-upload call path render step loop over specs render adapter re-validate Β· POST spec render service POST /v1/render { svg, png } Cloud Storage upload SVG + PNG
Blue = deterministic code, purple = the self-hosted render service, grey = durable storage. The step calls the adapter, which POSTs the typed spec and gets both formats back, then uploads each.

The call chain and behaviour, per diagram:

  • Adapter. renderDiagramSpecToImages re-validates the carried spec against the same strict diagramSpecSchema (zero drift with the service) before serialising it β€” a client-side pre-check that saves a round trip β€” then calls renderDiagramViaService.
  • Request. The transport POSTs the spec to /v1/render with the resolved theme (flat by default) and a light colour scheme. Both formats come from a single render call.
  • Response. The service returns { width, height, svg, png } β€” the raw SVG string plus the PNG decoded from base64 to bytes. The adapter maps the requested formats onto rendered-image records with the right content type (image/svg+xml, image/png).
  • Budget. The round trip is bounded by GRAPH_DIAGRAM_RENDERER_REQUEST_TIMEOUT_SECONDS (60 s), with a single deadline-bounded retry on a momentary transport blip; the service itself soft-bounds the async layout phase at 30 s.
  • Upload. Each format is streamed to GCS via buildDiagramFilePath, which keys the object on the run discriminator + diagramId + format extension. The canonical SVG is uploaded last: any sibling upload that fails throws before the .svg object exists, so the PNG sibling is always present whenever the SVG is.

The durable SVG object key seeds each diagram's imageUrl in workflow state, and a per-diagram upload record (assetFilePath, fileSize, sortOrder, diagramId, and optional diagramType / title / description) is collected for persistence.

Inside the render engine typed spec nodes Β· edges elkjs layout layered Β· orthogonal hand-authored SVG icons Β· theme resvg raster SVG β†’ PNG returns { width, height, svg, png }
The deterministic render engine: layout is a pure function of input order, the SVG is the regression-gated artifact, and the PNG is rasterised from it. Blue = deterministic render stages, grey = the input spec.

Inside the render service

The renderer is a self-hosted Fastify service (@1ke/graph-diagram-renderer) that turns one typed spec into both formats deterministically:

  • Layout β€” elkjs. Each node is sized by measuring its wrapped label against the bundled Arimo font; nodes sharing a group become titled containers. Layout runs the ELK layered algorithm with orthogonal edge routing, tuned (considerModelOrder / semiInteractive) so the result is a deterministic function of input order β€” the committed SVG snapshots depend on it.
  • SVG β€” hand-authored. The SVG is emitted as a string: group panels, then edges (rounded orthogonal paths, dashed for dependency, animated for flow), then nodes (a box or cylinder, a centred inline icon, a status accent, and the wrapped label), then edge labels.
  • Icons β€” a curated catalog. A node's bare icon name is validated against the catalog allowlist and its bundled SVG inlined; an unknown name falls back to a generic icon and never fails. Generic infra icons are tinted to the theme; vendor brand marks keep their own colours. The name is never interpolated into a filesystem path.
  • PNG β€” resvg. The SVG is rasterised with resvg using only the bundled font (no system fonts) so raster metrics match the layout, fit shrink-only to the requested width.
  • Theme and scheme. flat (the production default, borderless with a status accent dot) or card (bordered, elevated cards), in a light or dark colour scheme resolved at render time β€” resvg bakes literal token values because it cannot honour prefers-color-scheme.

Failure classification. The service returns 422 for every deterministic rejection β€” an invalid spec (DIAGRAM_SPEC_INVALID), a render timeout (RENDER_TIMEOUT), or a post-validation engine failure (RENDER_FAILED). The adapter maps a 422 to a deterministic workflow error (the subscriber acks). Any transport reject, timeout, or 5xx becomes a transient unreachable error that is rethrown as-is, so the subscriber nacks and Pub/Sub redelivers. The untrusted spec payload is never echoed into an error message.

5. Persistence

The render step uploads the image bytes and produces the per-diagram upload records, but the database rows are written after the workflow completes. persistSolutionStep updates the SolutionModel and threads the upload records through its output; the solution-generation subscriber then writes the rows in a single interactive transaction.

Persistence transaction upload record assetFilePath Β· sortOrder one $transaction Asset filePath β†’ SVG object SolutionModelDiagram assetId Β· diagramId Β· sortOrder solution model org + deal scoped
Grey = durable storage / rows, blue = the finalize step, neutral = the owning model. The transaction is idempotent: it clears the solution's prior diagram rows before recreating them.

For each upload record, in one $transaction:

  1. Idempotent finalize. The subscriber first deletes any existing SolutionModelDiagram rows for the org + solution model, so a re-run recreates the set cleanly rather than duplicating it.
  2. Create the Asset. An Asset row is created with filePath = the durable SVG object key, fileSize, a system-generated flag, and org scope β€” the row that backs the GCS object.
  3. Create the SolutionModelDiagram. A SolutionModelDiagram row links the Asset to the SolutionModel and the organization, carrying diagramId, diagramType, title, description, and sortOrder (unique per solutionModelId + diagramId).

The SolutionModelDiagram row stores no image path of its own β€” the durable SVG key lives on the linked Asset.filePath, and the PNG sibling shares that key by extension swap. If the transaction fails, the subscriber makes a best-effort cleanup of both the SVG and its PNG sibling in GCS before rethrowing.

6. Where the Diagrams Are Surfaced

The persisted diagrams reach the user through two paths, both minting fresh access at read time and never persisting a signed URL:

  • Web app (SVG). The SolutionModelDiagram.imageUrl @ResolveField resolves the backing Asset.filePath to a signed URL at read time (with a signed-URL complexity override). The solution overview stores the durable GCS object key inside its content and signs each embedded diagram reference on read, without mutating the stored row.
  • Proposal document (PNG). When the DOCX is rendered, resolveDiagramImageSources walks the document, derives each diagram's .png sibling from the embedded SVG key, downloads it (bounded by a max-bytes cap and a fetch timeout, and gated to keys under the deal's own diagram prefix), and inlines it as a data:image/png;base64,… URI.
  • Reuse in proposals. The proposal-generation workflow loads the persisted solution β€” model, overview, and pre-rendered diagrams β€” via loadPersistedSolutionStep, which rebuilds the in-workflow diagram carriers from the persisted rows using the durable Asset.filePath. It never re-renders.

7. Failure Handling, Idempotency & Security

  • Step short-circuit. Both steps check isStepAlreadyCompleted and, on a resumed run, rehydrate their diagram set from the persisted WorkflowStep.result instead of re-doing the work β€” unless a force-regeneration flag is set.
  • Deterministic vs transient. An invalid or unrenderable spec, a render timeout, or an engine defect is a deterministic failure the subscriber acks (a replay reproduces it); only a genuine transport fault is transient and nacked for redelivery. Classification survives all the way to the subscriber.
  • Client-side pre-check. The render adapter re-validates each carried spec against the same strict schema the service uses before serialising it, so an invalid spec fails fast without a wasted round trip and with zero schema drift between client and service.
  • SVG-last upload ordering. The canonical SVG is uploaded after its PNG sibling, so a failed sibling throws before any .svg exists β€” the document's PNG is guaranteed present whenever the web SVG row does.
  • Idempotent persistence. The finalize transaction clears prior diagram rows before recreating them, and object keys are run-stable, so a retry or re-render overwrites the same objects and rows rather than orphaning new ones.
  • Network-gated render service. The renderer accepts no public traffic β€” it is an internal Cloud Run service reachable only from the API over the VPC, with a fail-closed auth seam and logs that never echo the spec.
  • Bounded document fetch. The DOCX render fetches each PNG under a max-bytes cap and a timeout, and only for keys under the deal's own diagram prefix β€” a scheme-less, relative, non-traversing object key.
  • Multi-tenant scope. Every GCS path, Asset, and SolutionModelDiagram row carries the resolved organizationId; storage paths are nested under the org and deal.

Summary

End to end, a typed diagram specification becomes a durable, embeddable picture entirely in-house β€” spec authoring, strict validation, deterministic render, and durable upload β€” bookended by the solution model that authors the spec and the solution overview and proposal that display the result.

End-to-end diagram generation flow solution model typed spec validate strict schema render engine elkjs Β· SVG Β· PNG Cloud Storage SVG + PNG overview + proposal embed + reuse
The full in-house relay. Blue = deterministic validation, purple = the self-hosted render engine, grey = durable storage, neutral = the originating spec and the surfaces that embed it.