feat(aura-cli): render the graph as a self-contained WASM-Graphviz viewer; retire ascii-dag

Iteration 2 of cycle 0026 (graph render redesign). `aura graph` no longer emits
ASCII: it now prints one self-contained `.html` to stdout — the ported prototype
pin-graph viewer (drill / inline-expand / styled tooltips / breadcrumb) driven by
the deterministic model serializer that shipped in iteration 1, with layout and
SVG done in-browser by Graphviz compiled to WebAssembly. Closes the redesign
opened by spec 0026; unblocked by cycle 0027 (input ports are now named, so the
viewer labels every input pin from the model's real names instead of inventing
"a"/"b").

What ships:
- crates/aura-cli/assets/: the ported graph-viewer.js (prototype genDot/chrome
  verbatim + a normalizeModel adapter mapping aura's real model tuple shape —
  ins = [kind, firing, name], index node keys, `comp` refs, `src_<role>` — into
  the viewer's native shape), plus the vendored Graphviz-WASM (@viz-js/viz@3.7.0)
  and svg-pan-zoom@3.6.1 blobs and a refresh-assets.sh provenance script.
- crates/aura-cli/src/render.rs: render_html(&Composite) -> String, a read-only
  (C9) assembly — model_to_json + include_str! of the inlined assets, no eval,
  no build, no serde (C14). The `["graph"]` arm calls it.
- Retired: the ascii-dag adapter (graph.rs), the `ascii-dag` dependency, the
  `--compiled`/`--macd` graph flag plumbing, render_compiled, the color/terminal
  plumbing, and the 12 ascii-dag-asserting tests + the now-orphaned helpers.
  The cli_run integration test now pins the HTML page envelope.
- crates/aura-core/src/node.rs: the two last ascii-dag prose justifications
  re-grounded (single-line label rule kept, re-justified generically; the
  param-generic rule re-justified on C23, not on a renderer limitation).
- docs/design/INDEX.md: the cycle-0026 C9 realization note (both iterations).

Vendoring decision (spec deferred it to the plan): the WASM/JS blobs are checked
in, not fetched at build time. include_str! needs them at compile time, and a
build-time unpkg fetch would make the build non-hermetic/non-offline and let the
shipped artifact drift with the registry — against C1 (determinism) and C8
(frozen artifacts). ~1.4 MB is the price of a hermetic, frozen render asset.

Three adaptations beyond the plan, each verified: (1) render.rs imports
`aura_engine::Composite` (the path model_to_json takes), not aura_core; (2)
macd_blueprint is now test-only — removing the `--macd` arm left it used solely
by the param-space test, so it took `#[cfg(test)]` rather than removal or an
`#[allow]` suppression (the production `aura run --macd` path is intact, verified
emitting JSON); (3) the `grep ascii-dag` over crates/ matches only the new
contract test, which names the term deliberately to document the retirement — no
stale justification prose remains.

Invariants held (verified independently, not on agent report): C9 (render path
read-only), C10 (viewer is a render asset, no logic/DSL), C4 (four-colour palette
= the four scalar base types), C14 (no serde). The iteration-1 model byte golden
is unchanged and green. cargo build --workspace: 0 errors. cargo test --workspace:
157 passed, 0 failed (incl. the two new HTML tests + the unchanged model_golden).
cargo clippy --workspace --all-targets -- -D warnings: clean. ascii-dag absent
from `cargo tree -p aura-cli`.

closes #51
This commit is contained in:
2026-06-10 17:09:12 +02:00
parent c3109bdfdd
commit 66dff88528
12 changed files with 395 additions and 878 deletions
+4 -4
View File
@@ -151,8 +151,8 @@ pub trait Node {
/// tracing / graph rendering (#13), never read by the run loop and never
/// part of wiring (which is by index). Overrides SHOULD carry the node's
/// identifying params so identical node types disambiguate (`SMA(2)` vs
/// `SMA(4)`). MUST be single-line (no `\n`): ascii-dag breaks box drawing on
/// a multiline label. The default is a placeholder; every shipped node
/// `SMA(4)`). MUST be single-line (no `\n`): the graph render draws each
/// label in one cell. The default is a placeholder; every shipped node
/// overrides it. Returns an owned `String` and takes `&self`, so `Node`
/// stays object-safe and `Box<dyn Node>::label()` dispatches.
fn label(&self) -> String {
@@ -195,8 +195,8 @@ mod tests {
#[test]
fn primitive_builder_label_is_the_bare_type() {
// param-generic: the label is just the node type, no knob suffix (the
// ascii-dag blueprint view cannot render wide cluster-sibling labels).
// param-generic: the label is just the node type, no knob suffix the
// blueprint view is param-generic (C23), values appear only post-compile.
let with = PrimitiveBuilder::new(
"SMA",
NodeSchema {