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:
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.
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.
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.
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.
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.
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).
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.
Diagrams are rendered during solution generation and persisted with the solution. The proposal workflow loads the pre-rendered diagrams rather than regenerating them.
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.
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.
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.
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.
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 vialoadPersistedSolutionStep; 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:
| Field | Type | Purpose |
|---|---|---|
id | string | Stable per-diagram identifier β keys the render, the storage object, and the persisted row. |
type | enum | One of Component, Dataflow, Deployment, Sequence β the kind of diagram. |
specification.diagramSpec | object | The typed diagram itself β nodes, edges, and an optional title. This is the substance that gets rendered. |
title | string? | Optional human title, threaded into the persisted row and the overview. |
description | string? | Optional prose context, carried as a document-model text run. |
mappedRequirements | string[]? | 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:
| Element | Shape | Notes |
|---|---|---|
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). |
title | string? | 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.
- 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_GUIDANCEandDIAGRAM_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, orRequiresCommercialValidationβ 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
fromandtomust be theidof 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.
flowis reserved for genuine directional data/traffic streams (so the marching animation keeps meaning); structural links default todependency. 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.
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:
- Present. A diagram with no
diagramSpecis a deterministic failure β there is nothing to render. - Unique node ids. No two nodes may share an
id; a duplicate is a validation error. - Edge referential integrity. Every edge
fromandtomust 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.
The call chain and behaviour, per diagram:
- Adapter.
renderDiagramSpecToImagesre-validates the carried spec against the same strictdiagramSpecSchema(zero drift with the service) before serialising it β a client-side pre-check that saves a round trip β then callsrenderDiagramViaService. - Request. The transport POSTs the spec to
/v1/renderwith the resolved theme (flatby default) and alightcolour 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.svgobject 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 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
groupbecome 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 forflow), 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
iconname 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) orcard(bordered, elevated cards), in alightordarkcolour scheme resolved at render time β resvg bakes literal token values because it cannot honourprefers-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.
For each upload record, in one $transaction:
- Idempotent finalize. The subscriber first deletes any existing
SolutionModelDiagramrows for the org + solution model, so a re-run recreates the set cleanly rather than duplicating it. - Create the Asset. An
Assetrow is created withfilePath= the durable SVG object key,fileSize, a system-generated flag, and org scope β the row that backs the GCS object. - Create the SolutionModelDiagram. A
SolutionModelDiagramrow links theAssetto theSolutionModeland the organization, carryingdiagramId,diagramType,title,description, andsortOrder(unique persolutionModelId+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@ResolveFieldresolves the backingAsset.filePathto 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,
resolveDiagramImageSourceswalks the document, derives each diagram's.pngsibling 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 adata: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 durableAsset.filePath. It never re-renders.
7. Failure Handling, Idempotency & Security
- Step short-circuit. Both steps check
isStepAlreadyCompletedand, on a resumed run, rehydrate their diagram set from the persistedWorkflowStep.resultinstead 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
.svgexists β 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, andSolutionModelDiagramrow carries the resolvedorganizationId; 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.