Skip to content

Production Hardening

This document defines the production guardrails for TopoViewer as a general diagramming DSL and declarative graph renderer.

Validation Gates

Use the repository-root CI lanes for production validation. They are the same commands used by GitHub Actions, so a local failure maps to the same remote gate.

npm run ci:generated
npm run ci:quality
npm run ci:schemas
npm run ci:build
npm run ci:docs
npm run ci:render-parity
npm run ci:test:topoviewer
npm run ci:test:harness
npm run ci:perf:smoke
npm run ci:package
npm run ci:public-readiness

npm run ci runs the same lanes in order. npm run ci:remote-parity runs the full lane set with CI=true and NODE_ENV=test, which is useful when a local machine has a server already running or a cached browser state that could hide a GitHub Actions failure.

Lane Purpose
ci:generated Regenerates docs/examples and fails if committed generated files are stale.
ci:quality Runs code-health checks, Oxlint, TypeScript checks, dependency boundaries, and copy-paste detection.
ci:schemas Validates YAML schemas and semantic graph linting.
ci:build Builds packages and verifies vendored MkDocs embed assets are committed.
ci:docs Builds MkDocs, Zensical, and the browser harness, then opens the built site/ artifact in Chromium.
ci:render-parity Opens the same canonical fixtures through harness, MkDocs embed assets, and Zensical embed assets, then compares viewer-only DOM geometry and screenshots.
ci:test:topoviewer Runs unit and Playwright tests for the renderer package.
ci:test:harness Runs Playwright tests for the VS Code browser harness.
ci:perf:smoke Enforces attention-engine and CLOS layout smoke benchmarks.
ci:package Runs npm pack inspection and MkDocs wheel inspection.
ci:public-readiness:core Runs remote readiness guardrails that are not already covered by the package and security workflow steps: docs lint, render parity, hostile-content tests, security health report, and public leak/readiness guardrails.
ci:public-readiness Runs the full local/release public-adoption gate: core readiness plus package dry-run, install-command checks, Grafana artifact autopsy, dependency triage, and Go vulnerability check.

GitHub CI uses a hybrid split for wall-clock feedback. Generated-content preflight runs first; quality, schemas, docs, tests, performance, and package checks then run as separate jobs. The final public-readiness job waits for docs and package checks, downloads the built site/ artifact, and runs renderer parity without rebuilding the docs site. Local npm run ci remains sequential and conservative.

JSON Schema catches malformed document shape. Semantic lint catches broken meaning:

  • Duplicate IDs.
  • Missing names on graph objects.
  • Links with missing source/target nodes.
  • Paths with missing sequence nodes.
  • Stitched child paths with missing or invalid parent paths.
  • Regions with missing members or parents.
  • Unknown layers.
  • Unused stylesheet selectors.
  • Unsafe image references.
  • Renderer limit violations.

The docs smoke gate catches deployment-specific behavior that static tests miss:

  • MkDocs embeds render graph nodes and visible links without .topoviewer-error.
  • Zensical embeds hydrate without requiring a manual browser refresh.
  • The browser harness loads under the GitHub Pages /topoviewer/harness/ base path.

The renderer parity gate is narrower than docs smoke. It treats the browser harness as the golden authoring surface, then renders the same canonical YAML fixtures through MkDocs and Zensical using a fixed viewport and parity theme. Allowed differences are page chrome, surrounding documentation layout, and non-parity wrapper themes. Not allowed: different graph geometry, missing edges, changed label placement, changed icon fit, style default drift, or different topology/stylesheet composition. Failure screenshots are stored in the ignored local artifact directory for review.

Performance Budgets

Performance gates are intentionally split by responsibility:

  • benchmark:attention:smoke covers dense attention indexing, focus updates, graph reduction, and first compile behavior.
  • benchmark:clos:smoke covers layout-only CLOS placement without React, browser APIs, or the harness.

The CLOS smoke benchmark uses a deterministic synthetic graph:

Fixture Value
Nodes 1000
Links 2520
Stages 5
Stage counts 158, 211, 264, 210, 157
Layout mode clos

The CI smoke budget is deliberately looser than a developer laptop baseline so GitHub runner variance does not hide real regressions behind flaky thresholds:

Measurement CI smoke threshold
Fixture generation 250 ms
Median CLOS layout 2000 ms
Max CLOS layout round 4000 ms
Generation + median layout 2200 ms

Current local Node 24 baseline recorded with npm run benchmark:clos -- --nodes 1000 --rounds 7:

Measurement Local result
Fixture generation 17.507 ms
Median CLOS layout 37.271 ms
Max CLOS layout round 39.687 ms
Generation + median layout 54.778 ms

Interaction budgets for production authoring and embedded docs:

Surface Budget
Default interactive diagrams Stay within the default renderer limits unless the host explicitly raises them.
1k-node CLOS apply/layout Complete layout in the benchmark:clos:smoke median budget before handing positioned nodes to React Flow.
Dense operational views Prefer layers, regions, aggregates, and attention state over rendering every repeated service or endpoint.
Browser harness and docs smoke Assert durable rendered graph state instead of transient status text or fixed sleeps.

React authoring surfaces have an additional bundle budget because the browser harness and VS Code webview both contain rich YAML editing. Monaco must stay behind the lazy editor boundary; do not import monacoSetup or @monaco-editor/react from the harness or webview entry modules. Use MonacoYamlEditor for the webview rail and React.lazy for package workbench editor use.

Use these commands when changing Harness, VS Code webview, Monaco/YAML authoring, or MUI imports:

npm run react:perf:report
npm run react:perf:check
npm run test:vscode-unit

react:perf:report reads the current built artifacts and reports initial and lazy JavaScript chunks for the browser harness and VS Code webview. react:perf:check rebuilds those surfaces, enforces the checked-in budgets in scripts/react-performance-budgets.json, and fails on forbidden @mui/material barrel imports in the budgeted paths. The budget includes a small byte tolerance for normal build variance; increase it only with a new before/after report saved under an ignored local artifact directory.

Browser authoring preferences must use the safe storage helpers in packages/vscode-topoviewer/src/webview/browserStorage.ts. Direct localStorage.setItem or sessionStorage.setItem calls are not allowed in the React authoring surfaces because blocked storage, quota errors, and malformed JSON must degrade to defaults instead of breaking the harness.

Stress fixtures are intentionally outside the required CI gate. The policy is:

Class Size Command home Required gate
Default Up to the renderer defaults Normal examples, docs, and harness fixtures npm run ci
Dense smoke 1000 CLOS nodes and 2520 links npm run benchmark:clos:smoke npm run ci:perf:smoke
Local stress 10000-node synthetic graphs npm run benchmark:clos -- --nodes 10000 --rounds 3 Manual before changing layout complexity

10k-node runs are useful for profiling algorithmic changes, but they are not published as browser harness templates and are not part of every pull-request gate. If a change improves or regresses stress behavior, record the timing in the change discussion and keep the required CI smoke threshold focused on the 1k-node production interaction budget.

Grafana Containerlab validation is also intentionally outside the default pull request gate. Run npm run grafana:clab:up, npm run grafana:clab:smoke, and npm run grafana:clab:down when changing the real telemetry lab, Containerlab topology, generated Prometheus rules, or mapper behavior that depends on live gNMIc/Prometheus data.

Generated Artifact Contract

TopoViewer has generated files because one canonical content tree feeds npm examples, MkDocs pages, Zensical pages, and package docs. The mutation rule is:

Prefix Contract
sync:* May write generated sources or projection files. Review and commit the diff.
check:* Must report drift without leaving source changes.
validate:* Must validate inputs and should not leave generated source changes.
test:* May write test artifacts under ignored report directories only.
ci:* May run sync/build steps, but must fail if generated files required by the repository are left dirty.

If ci:generated, ci:build, or ci:docs fails with a stale generated-file message, read the reported canonical source area and generated projection path, run the matching sync/build command locally, review the exact diff, and commit it with the source change.

Failure Triage

Classify CI failures before changing code:

Classification Signal Response
Product regression The same command fails locally and the output is genuinely wrong. Fix product code or fixtures and keep/add regression coverage.
Generated artifact drift A sync/build step changes tracked projections or vendored assets. Run the matching sync:* or build step, review the diff, and commit it.
Environment drift GitHub differs from local Node, npm, Python, browser, OS, or env vars. Compare ci:env output and reproduce with npm run ci:remote-parity.
Server orchestration drift Playwright attaches to a stale server or wrong port. Run under CI=true; CI-mode Playwright must start its own server.
Browser timing or hydration drift The page needs a manual refresh or a selector is asserted before hydration. Wait for durable rendered DOM state, not arbitrary timeouts.
Brittle assertion The test checks SVG string spacing, transient status text, or overly tight pixel values. Assert durable YAML, object state, graph semantics, or documented tolerances.
Resource/performance flake The runner is constrained but the behavior is correct. Preserve traces and metrics before adjusting budgets.
Command contract drift A command name hides mutation or differs from GitHub workflow behavior. Move behavior behind the correct command prefix and update the docs.

Remote-only failure workflow:

gh run view <run-id> --log-failed
npm run ci:env
npm run ci:remote-parity

Use the failing named lane first when the GitHub step identifies one, for example npm run ci:test:harness or npm run ci:docs. Do not retry a required CI test to hide a regression. A temporary retry is acceptable only when there is a tracked flaky-browser issue and the first failure preserves trace, screenshot, console output, environment report, and generated artifacts.

Renderer Limits

Default limits are intentionally conservative:

Limit Default
Nodes 1200
Edges 2400
Path segments 1600
Labels 2000
Callouts 250
Shapes 500
Embedded image bytes 750000

Override only when the target environment is known:

limits:
  maxNodes: 2000
  maxEdges: 4000
  maxPathSegments: 2400

If a use case has thousands of repeated services, model them as aggregate objects with data.serviceCount; do not render thousands of individual lanes by default.

Schema Versioning And Migration

Use version on authored documents:

version: "0.1"

Missing versions are migrated to the current compatible version at runtime. Breaking model changes must add a migration step instead of silently changing semantics.

Reference Resolution Rules

For Markdown and image references:

  • Relative paths are resolved by the host: MkDocs, browser app, or portal.
  • https: is allowed for remote references.
  • http: should only be used in trusted lab environments.
  • Data images are allowed for PNG, JPEG, GIF, and WebP.
  • SVG data URLs are blocked in Markdown image references.
  • Inline icon SVG is sanitized before being converted to a data image.

MkDocs embeds resolve topology and stylesheet relative to the Markdown page.

Security Baseline

TopoViewer treats authored diagrams as content, not code:

  • Markdown rendering escapes raw HTML.
  • Markdown links are URL-filtered.
  • Markdown image URLs are URL-filtered.
  • Inline SVG icons strip script, foreignObject, event-handler attributes, and JavaScript hrefs.
  • Public documentation should not enable untrusted third-party YAML without server-side review.

Accessibility Baseline

TopoViewer provides:

  • A top-level role="img" with diagram label.
  • Node role="group" labels from object names.
  • Callout role="note".
  • Theme-aware CSS variables for light/dark mode.

Authoring guidance:

  • Do not rely on color alone. Use labels, line style, shape, or icons.
  • Keep sufficient contrast in both light and dark themes.
  • Use name on graph objects for readable labels, tooltips, and export.

Static Export

The package exports:

  • topoviewerToSvg
  • topoviewerToPng
  • topoviewerToPdf
  • downloadTopoViewerSvg
  • downloadTopoViewerPng
  • downloadTopoViewerPdf

For deterministic exports:

  • Use explicit layout.mode: manual, or fixed force-layout parameters.
  • Set deterministic viewport size in the host.
  • Avoid remote images unless the host can load them consistently.
  • Prefer packaged fonts or system-safe fonts.

Visual Regression

DOM tests are not enough for diagrams. Use Playwright screenshots for baseline views and interaction-critical views. Update snapshots only after reviewing the visual change.