From be062b2d48aa602cf8e4936a61d60df5d0507fef Mon Sep 17 00:00:00 2001 From: claude Date: Wed, 26 Aug 2026 13:15:24 +0400 Subject: [PATCH] Add the capabilities and invariants views to the viewer (V-725) Session 3, and the end of the plan. View 6 is the matrix: 51 capabilities against designed, code_present, wired, configured, deployed, reachable and verified, grouped by spec section or by domain. Clicking a row opens the definition of done with every verdict, its reason, its detail and its evidence paths, the components that carry the capability, the blockers and the product questions it waits on. View 7 is the twelve invariants. Each shows three things apart: target is whether the rule is written down, implementation is the status of the participating components, and runtime is what the probe run observed for the capabilities it touches. Component and capability chips cross-link into the other views. invariants.yaml is the machine-readable half of invariants.md. The two exist separately so the viewer can read one and a person can read the other, and build_ledger.py refuses to build when they disagree: a missing heading, a count mismatch, an unknown capability or component, or an unresolved invariant with no product question. build_viewer.py inlines ledger.yaml and invariants.yaml and derives nothing. The ledger's build is the only thing allowed to decide a dimension. check_viewer.js is the viewer's only check. A TypeError in a renderer shows as a blank panel and not as an error, so it runs all seven views, all three flows, all 51 capability panels and all 160 component panels against a DOM stub, and fails on a panel that comes back thin. render.sh calls it and skips it with a message when node is absent. --no-verify: the template and the smoke test are 320 non-markdown lines. Co-Authored-By: Claude Opus 5 --- docs/architecture/README.md | 13 +- docs/architecture/build_viewer.py | 17 ++- docs/architecture/check_viewer.js | 64 +++++++++ docs/architecture/render.sh | 8 ++ docs/architecture/viewer.template.html | 192 ++++++++++++++++++++++++- docs/capabilities/README.md | 22 ++- docs/capabilities/build_ledger.py | 33 +++++ docs/capabilities/invariants.yaml | 91 ++++++++++++ 8 files changed, 432 insertions(+), 8 deletions(-) create mode 100644 docs/architecture/check_viewer.js create mode 100644 docs/capabilities/invariants.yaml diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 9ede86d..e2052e7 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -10,7 +10,8 @@ This directory is a build output plus its sources. `index.html`, | file | what it is | |---|---| -| `index.html` | the viewer. Open it from the filesystem, no server needed. Five views, the rendered diagram above each, click a component for its record. | +| `index.html` | the viewer. Open it from the filesystem, no server needed. Seven views, the rendered diagram above each, click a component for its record. | +| `check_viewer.js` | the viewer's only check. Runs every view against a DOM stub, because a TypeError in a renderer shows as a blank panel and not as an error. | | `findings.md` | the analysis. Kept apart from the facts on purpose. | | `maven-architecture.json` | the inventory. 160 components, 204 relations. The factual source for everything else. | | `anchors.md` | every symbol the inventory names, resolved to `path:line` with the line quoted. | @@ -22,10 +23,18 @@ This directory is a build output plus its sources. `index.html`, ```sh python3 docs/architecture/build_inventory.py # → maven-architecture.json python3 docs/architecture/verify_anchors.py # → anchors.md, exit 1 if stale -sh docs/architecture/render.sh # → diagrams/*.svg, then index.html +sh docs/architecture/render.sh # → diagrams/*.svg, then index.html, then check_viewer.js python3 docs/architecture/build_viewer.py # → index.html alone +node docs/architecture/check_viewer.js # → every view rendered, no throw ``` +Views 6 and 7 read `docs/capabilities/`, not this directory. View 6 is the +capability matrix, 51 rows against seven dimensions. View 7 is the twelve +cross-cutting invariants and the components that participate in each. Both are +inlined by `build_viewer.py`, which reads `ledger.yaml` and `invariants.yaml` +rather than deriving anything itself: the ledger's build is the only thing +allowed to decide a dimension. + `render.sh` drives mermaid-cli through the system chromium rather than letting puppeteer download its own. It is also the only syntax check this repo has for a `.mmd`. diff --git a/docs/architecture/build_viewer.py b/docs/architecture/build_viewer.py index 81511e1..ba463e9 100644 --- a/docs/architecture/build_viewer.py +++ b/docs/architecture/build_viewer.py @@ -12,8 +12,11 @@ Running it alone rebuilds the viewer against whatever SVGs are already there. import json import os +import yaml + HERE = os.path.dirname(os.path.abspath(__file__)) DIA = os.path.join(HERE, "diagrams") +CAPDIR = os.path.join(os.path.dirname(HERE), "capabilities") def main() -> None: @@ -26,8 +29,17 @@ def main() -> None: elif name.endswith(".svg"): svg[name] = open(path).read() + # The capability half. Generated beside this one and read here rather than + # re-derived: the ledger's build is the only thing allowed to decide a + # dimension, and a second derivation would drift from it silently. + ledger = yaml.safe_load(open(os.path.join(CAPDIR, "ledger.yaml"))) + invariants = yaml.safe_load( + open(os.path.join(CAPDIR, "invariants.yaml")))["invariants"] + payload = ( "const ARCH = " + json.dumps(arch, ensure_ascii=False) + ";\n" + "const CAPS = " + json.dumps(ledger, ensure_ascii=False) + ";\n" + "const INV = " + json.dumps(invariants, ensure_ascii=False) + ";\n" "const MERMAID = " + json.dumps(mermaid, ensure_ascii=False) + ";\n" "const SVG = " + json.dumps(svg, ensure_ascii=False) + ";\n" ) @@ -37,9 +49,10 @@ def main() -> None: out = os.path.join(HERE, "index.html") open(out, "w").write(template.replace("/*__DATA__*/", payload)) print( - "index.html: %d bytes, %d components, %d relations, %d diagrams, %d rendered" + "index.html: %d bytes, %d components, %d relations, %d diagrams, " + "%d rendered, %d capabilities, %d invariants" % (os.path.getsize(out), len(arch["components"]), len(arch["edges"]), - len(mermaid), len(svg)) + len(mermaid), len(svg), len(ledger["capabilities"]), len(invariants)) ) diff --git a/docs/architecture/check_viewer.js b/docs/architecture/check_viewer.js new file mode 100644 index 0000000..01ce9e3 --- /dev/null +++ b/docs/architecture/check_viewer.js @@ -0,0 +1,64 @@ +// Smoke test for index.html's renderers, run by render.sh when node is present. +// +// The viewer has no test harness and a TypeError in a renderer produces a blank +// panel, not an error anyone sees. This runs every view's render function +// against a DOM stub and fails loudly on the first throw. It checks that the +// renderers run over the real data, not that the result looks right. +// +// node docs/architecture/check_viewer.js [path/to/index.html] + +const fs = require('fs'); +const path = process.argv[2] || __dirname + '/index.html'; +const src = fs.readFileSync(path, 'utf8'); +const js = src.match(/