Build an introspectable, fail-fast construction service that emits blueprints #157

Closed
opened 2026-06-29 12:58:41 +02:00 by Brummel · 3 comments
Owner

C24/C17 (docs/design/INDEX.md): node logic stays Rust; topology is data the World or an LLM constructs. The Rust builders — GraphBuilder, PrimitiveBuilder (crates/aura-engine/src/blueprint.rs, crates/aura-core/src/node.rs) — deliver a correct graph at compile time via per-op kind matching, check_ports_connected (wiring totality), and check_param_namespace_injective (blueprint.rs). A data-level author (a small LLM especially) should not hand-write the format and satisfy those invariants blind — the failure mode that sank the RustAst DSL was unaided authoring of a many-invariant artifact, not the syntax.

Goal: a construction service that delivers the builder's compile-time guarantees on the data level, mirroring them:

  • per-op validation reusing the existing engine gates (PortSpec.kind on connect, check_ports_connected at finalize, check_param_namespace_injective) — each op returns a handle or a typed error at the call, not at the end;
  • introspection of the closed, compiled-in node vocabulary (the NodeSchema is already declared pre-build on PrimitiveBuilder, C8) — enumerate node types, their ports + kinds, the still-unwired slots, and the param paths;
  • a terminal build() = compile_with_params, whose output is the serialized blueprint (the format from #155).

It is a thin service over the already-correct Rust machinery, so it introduces no new language and no second validator (C24 forbids a Turing-complete or applied-DSL format).

Open fork (unresolved — its own design step, does not block #155): the surface — an MCP / tool API an LLM drives vs. stateful aura graph ... subcommands over a session file. The format and the gates are surface-agnostic.

Acceptance (validated property):

  • A graph built purely through the service's ops, with no hand-written format, compiles and runs identical to its Rust-built twin (C1).
  • An invalid op (kind mismatch, double-wire, unknown node) is rejected at the op, naming the cause.
  • Introspection answers the node vocabulary, a node's ports/kinds, and the unwired slots without a build.

Depends on the serialization + loader issue #155.

C24/C17 (docs/design/INDEX.md): node logic stays Rust; topology is data the World or an LLM constructs. The Rust builders — GraphBuilder, PrimitiveBuilder (crates/aura-engine/src/blueprint.rs, crates/aura-core/src/node.rs) — deliver a correct graph at compile time via per-op kind matching, check_ports_connected (wiring totality), and check_param_namespace_injective (blueprint.rs). A data-level author (a small LLM especially) should not hand-write the format and satisfy those invariants blind — the failure mode that sank the RustAst DSL was unaided authoring of a many-invariant artifact, not the syntax. Goal: a construction service that delivers the builder's compile-time guarantees on the data level, mirroring them: - per-op validation reusing the existing engine gates (PortSpec.kind on connect, check_ports_connected at finalize, check_param_namespace_injective) — each op returns a handle or a typed error at the call, not at the end; - introspection of the closed, compiled-in node vocabulary (the NodeSchema is already declared pre-build on PrimitiveBuilder, C8) — enumerate node types, their ports + kinds, the still-unwired slots, and the param paths; - a terminal build() = compile_with_params, whose output is the serialized blueprint (the format from #155). It is a thin service over the already-correct Rust machinery, so it introduces no new language and no second validator (C24 forbids a Turing-complete or applied-DSL format). Open fork (unresolved — its own design step, does not block #155): the surface — an MCP / tool API an LLM drives vs. stateful `aura graph ...` subcommands over a session file. The format and the gates are surface-agnostic. Acceptance (validated property): - [ ] A graph built purely through the service's ops, with no hand-written format, compiles and runs identical to its Rust-built twin (C1). - [ ] An invalid op (kind mismatch, double-wire, unknown node) is rejected at the op, naming the cause. - [ ] Introspection answers the node vocabulary, a node's ports/kinds, and the unwired slots without a build. Depends on the serialization + loader issue #155.
Brummel added this to the Topology-as-data: blueprint serialization & loader (C24) milestone 2026-06-29 12:58:41 +02:00
Brummel added the feature label 2026-06-29 12:58:41 +02:00
Author
Owner

Construction-service surface — fork resolved toward a declarative, replayable op-script over the CLI

The open fork in the issue body ("an MCP / tool API an LLM drives vs. stateful aura graph … subcommands over a session file") resolves toward a declarative, replayable by-identifier op-script run through the CLI — not an MCP server, and not hand-authoring the serialized format from #155.

What the surface is

The authoring surface is a list of by-identifier GraphBuilder acts (add / connect / feed / expose, with named ports) as a document. Illustrative shape (syntax is a specify/planner concern, not fixed here):

add  SMA(length=2) as fast
add  SMA(length=4) as slow
add  Sub
connect fast.out -> sub.a
connect slow.out -> sub.b
add  Bias
connect sub.out -> bias.in
expose bias.out as bias

aura graph build < script rebuilds the GraphBuilder state inside one process, replays op by op, and stops at the first that does not resolve, naming it. The document is the artifact; each run replays it from scratch. This is the same compile path the Rust builder already takes (GraphBuilder::buildcompile_with_paramsFlatGraph), so a graph built through the ops runs identically to its Rust-built twin (acceptance criterion 1 below; C1 determinism — docs/design/INDEX.md).

Why this shape, not the alternatives

  • Not hand-authoring the #155 format. The serialized blueprint (crates/aura-engine/src/blueprint_serde.rs) wires by raw index (Edge { from, to, slot, from_field }) because it is the machine side of the two-level split (named blueprint source lowered to an index-wired FlatGraph — C23, docs/design/INDEX.md). It is the compiled output, not an authoring surface; hand-editing it is a category error. The issue body's "should not hand-write the format and satisfy those invariants blind" means precisely this. Authoring is by-identifier (fast.out, never {from:0}); the service resolves names → indices exactly as GraphBuilder::build does today.

  • Not a held session / MCP server (for now). A document replayed from scratch keeps the surface deterministic and serializable: the script is a World-owned topology value (C24, docs/design/INDEX.md), reproducible (C1), with no held process state that can diverge from the history that produced it. A RAM-session / MCP server is the one construction shape that can let result drift from history unless disciplined back to document-replay — it builds a process to enforce a model the file gives for free. MCP is deferred as consumer-strength-dependent: a held interactive dialog (op → "which slots are open?" → adaptively pick the next) only helps a weak author that must be probed step by step; a strong author (the real near-term consumer) plans the sequence and corrects against stop-at-first-error over the document.

The actual work: split the gates eager + holistic

Claim (from builder recon, to be ratified by the specify grounding-check): the three structural gates run today at finalize (compile_with_params), not per op — check_param_namespace_injective, validate_wiring (index-range + edge kind-match), and check_ports_connected (wiring totality), in crates/aura-engine/src/blueprint.rs. The op-script model requires splitting them:

  • Per-op local (fires during replay, at the offending op): add → the type is in the injected vocabulary; connect → producer-field kind == consumer-port kind, and the consumer slot is not already wired.
  • Holistic finalize (fires at end of replay): wiring totality (every interior slot wired exactly once) and param-namespace injectivity — "the list is incomplete" is only decidable once the document ends.

Plus build-free introspection (acceptance criterion 3 below): a read-only query over the vocabulary (node types, their ports + kinds, param paths — already declared pre-build on PrimitiveBuilder's schema, C8) and over a partial script (the still-unwired slots). The vocabulary is the injected closed set (aura_std::std_vocabulary); no node registry (invariant 9, CLAUDE.md).

This core (eager-split gates + introspection) is surface-agnostic — the CLI op-script is a thin shell over it, and an MCP adapter, if ever wanted, is additive over the same core.

Acceptance mapping (issue body)

  • A graph built purely through the ops compiles and runs identical to its Rust-built twin (C1) — the ops drive GraphBuildercompile_with_params, the existing path.
  • An invalid op (kind mismatch, double-wire, unknown node) is rejected at the op, naming the cause — the per-op-local gates firing during replay.
  • Introspection answers the node vocabulary, a node's ports/kinds, and the unwired slots without a build — the read-only introspection above.

Scope & sequencing

  • Topology-as-data holds for every author, including Claude Code. The axis is computation vs. topology, not author strength: node logic stays native Rust / cdylib (C17), but topology — including topology Claude Code authors — is data, not hard-coded Rust builder source. The hard-wired aura-cli harnesses (sample_blueprint, run_stage1_r, the HarnessKind set) are exactly the topology-as-source that C24 forbids and #159 retires. So this issue is valuable today against the existing aura-std vocabulary — it is not gated on the project-as-crate / cdylib layer — and pairs with #159 (this issue supplies the data-authoring surface; #159 removes the source-topology). The GraphBuilder survives as an emitter of #155 data inside a topology generator (a Rust meta-program is fine — C20 — its output is data), never as the home of a concrete harness.
  • Still depends on the serialization + loader (#155, landed d5602ec). The format extensions (#156) — construction-arg builders (LinComb / CostSum / SimBroker / Session) and recording sinks — are out of the round-trippable set today and additive later.

Next step: enter specify from this comment — the design is settled here, with no open load-bearing fork remaining.

## Construction-service surface — fork resolved toward a declarative, replayable op-script over the CLI The open fork in the issue body ("an MCP / tool API an LLM drives vs. stateful `aura graph …` subcommands over a session file") resolves toward a **declarative, replayable by-identifier op-script run through the CLI** — not an MCP server, and not hand-authoring the serialized format from #155. ### What the surface is The authoring surface is a list of by-identifier `GraphBuilder` acts (`add` / `connect` / `feed` / `expose`, with named ports) as a **document**. Illustrative shape (syntax is a `specify`/`planner` concern, not fixed here): ``` add SMA(length=2) as fast add SMA(length=4) as slow add Sub connect fast.out -> sub.a connect slow.out -> sub.b add Bias connect sub.out -> bias.in expose bias.out as bias ``` `aura graph build < script` rebuilds the `GraphBuilder` state inside one process, replays op by op, and stops at the first that does not resolve, naming it. The document is the artifact; each run replays it from scratch. This is the same compile path the Rust builder already takes (`GraphBuilder::build` → `compile_with_params` → `FlatGraph`), so a graph built through the ops runs identically to its Rust-built twin (acceptance criterion 1 below; C1 determinism — docs/design/INDEX.md). ### Why this shape, not the alternatives - **Not hand-authoring the #155 format.** The serialized blueprint (`crates/aura-engine/src/blueprint_serde.rs`) wires by **raw index** (`Edge { from, to, slot, from_field }`) because it is the *machine* side of the two-level split (named blueprint source lowered to an index-wired `FlatGraph` — C23, docs/design/INDEX.md). It is the **compiled output**, not an authoring surface; hand-editing it is a category error. The issue body's "should not hand-write the format and satisfy those invariants blind" means precisely this. Authoring is by-identifier (`fast.out`, never `{from:0}`); the service resolves names → indices exactly as `GraphBuilder::build` does today. - **Not a held session / MCP server (for now).** A document replayed from scratch keeps the surface deterministic and serializable: the script *is* a World-owned topology value (C24, docs/design/INDEX.md), reproducible (C1), with no held process state that can diverge from the history that produced it. A RAM-session / MCP server is the one construction shape that can let result drift from history unless disciplined back to document-replay — it builds a process to enforce a model the file gives for free. MCP is **deferred** as consumer-strength-dependent: a held interactive dialog (op → "which slots are open?" → adaptively pick the next) only helps a *weak* author that must be probed step by step; a strong author (the real near-term consumer) plans the sequence and corrects against stop-at-first-error over the document. ### The actual work: split the gates eager + holistic Claim (from builder recon, to be ratified by the `specify` grounding-check): the three structural gates run today at finalize (`compile_with_params`), not per op — `check_param_namespace_injective`, `validate_wiring` (index-range + edge kind-match), and `check_ports_connected` (wiring totality), in `crates/aura-engine/src/blueprint.rs`. The op-script model requires splitting them: - **Per-op local** (fires during replay, at the offending op): `add` → the type is in the injected vocabulary; `connect` → producer-field kind == consumer-port kind, and the consumer slot is not already wired. - **Holistic finalize** (fires at end of replay): wiring totality (every interior slot wired exactly once) and param-namespace injectivity — "the list is incomplete" is only decidable once the document ends. Plus **build-free introspection** (acceptance criterion 3 below): a read-only query over the vocabulary (node types, their ports + kinds, param paths — already declared pre-build on `PrimitiveBuilder`'s schema, C8) and over a partial script (the still-unwired slots). The vocabulary is the injected closed set (`aura_std::std_vocabulary`); no node registry (invariant 9, CLAUDE.md). This core (eager-split gates + introspection) is **surface-agnostic** — the CLI op-script is a thin shell over it, and an MCP adapter, if ever wanted, is additive over the same core. ### Acceptance mapping (issue body) - [ ] A graph built purely through the ops compiles and runs identical to its Rust-built twin (C1) — the ops drive `GraphBuilder` → `compile_with_params`, the existing path. - [ ] An invalid op (kind mismatch, double-wire, unknown node) is rejected at the op, naming the cause — the per-op-local gates firing during replay. - [ ] Introspection answers the node vocabulary, a node's ports/kinds, and the unwired slots without a build — the read-only introspection above. ### Scope & sequencing - **Topology-as-data holds for every author, including Claude Code.** The axis is computation vs. topology, not author strength: node *logic* stays native Rust / cdylib (C17), but *topology* — including topology Claude Code authors — is data, not hard-coded Rust builder source. The hard-wired `aura-cli` harnesses (`sample_blueprint`, `run_stage1_r`, the `HarnessKind` set) are exactly the topology-as-source that C24 forbids and #159 retires. So this issue is **valuable today** against the existing `aura-std` vocabulary — it is not gated on the project-as-crate / cdylib layer — and **pairs with #159** (this issue supplies the data-authoring surface; #159 removes the source-topology). The `GraphBuilder` survives as an **emitter** of #155 data inside a topology *generator* (a Rust meta-program is fine — C20 — its *output* is data), never as the home of a concrete harness. - Still depends on the serialization + loader (#155, landed `d5602ec`). The format extensions (#156) — construction-arg builders (`LinComb` / `CostSum` / `SimBroker` / `Session`) and recording sinks — are out of the round-trippable set today and additive later. Next step: enter `specify` from this comment — the design is settled here, with no open load-bearing fork remaining.
Author
Owner

Spec 0088 — derived design decisions (specify)

The construction-service surface fork was resolved earlier in this thread
(the "op-script over the CLI" comment). Writing the spec (docs/specs/0088)
surfaces four lower-level forks beneath it. All four are derived
orchestrator decisions (rationale given), not user decisions — recorded here
for after-the-fact audit and veto.

  • Fork: one validator or two → reuse the engine's existing gates; introduce
    no second validator. The per-op (eager) checks and the end-of-document
    (holistic) checks call the same extracted predicates at two cadences.
    Basis: derived — the issue body forbids "a second validator" (C24);
    duplicating the kind / coverage logic would be exactly that.

  • Fork: per-op fallibility on GraphBuilder vs. a new surface → a new
    per-op-fallible construction surface in aura-engine that reuses
    GraphBuilder's name-resolution (crates/aura-engine/src/builder.rs) and the
    shared predicates, leaving GraphBuilder's infallible-accumulate-then-
    build() contract (builder.rs:1-9, :67-68) intact. Basis: derived — the
    op-script's stop-at-first-failing-op is a per-op-fallible discipline, whereas
    GraphBuilder's contract is infallible ops + one fallible build().
    Overloading one type with both contracts is worse structural fit than a
    sibling surface over shared internals.

  • Fork: the eager / holistic boundary → the "every interior input slot
    covered exactly once" invariant (check_ports_connected,
    crates/aura-engine/src/blueprint.rs:688) splits along its own two failure
    arms: the ≤1 half (a second cover = DoubleWiredPort) is eagerly
    decidable at the connect / feed op; the ≥1 half (zero cover =
    UnconnectedPort) is only decidable once the document ends, so it stays
    holistic. Edge kind-match (validate_wiring, blueprint.rs:638-649) is
    eager; param-namespace injectivity (check_param_namespace_injective,
    blueprint.rs:566) is holistic. Basis: derived — a mid-document graph is
    legitimately incomplete, so totality cannot fire per-op; the split is forced
    by what is decidable when.

  • Fork: document encoding → the canonical core encoding is a JSON
    op-list
    (serde-native, mirroring the #155 blueprint format), demonstrated as
    the worked example; a line-oriented human text front-end (the
    add SMA(length=2) as fast shape illustrated earlier in this thread) is a
    deferred ergonomic sugar, not core. Basis: derived — the three acceptance
    criteria are encoding-agnostic; JSON-through-serde adds no hand-written
    parser / lexer (the smallest "no new language" surface) and syntax was
    explicitly delegated to specify/planner ("syntax is a specify/planner concern,
    not fixed here"). The replay / < script document semantics are identical
    either way.

Consequence (not a fork): build-free vocabulary introspection (acceptance 3)
needs the closed std_vocabulary match (crates/aura-std/src/vocabulary.rs:30)
paired with an enumerable companion list of its type identities — a
fn(&str) -> Option<…> resolver alone cannot be enumerated. The same iterable
surface would also give #160's guard test something to iterate, though #160
stays a separate item.

The spec auto-signs under /boss on a grounding-check PASS; a reply vetoes
any of the above.

## Spec 0088 — derived design decisions (specify) The construction-service *surface* fork was resolved earlier in this thread (the "op-script over the CLI" comment). Writing the spec (`docs/specs/0088`) surfaces four lower-level forks beneath it. All four are **derived** orchestrator decisions (rationale given), not user decisions — recorded here for after-the-fact audit and veto. - **Fork: one validator or two** → reuse the engine's existing gates; introduce no second validator. The per-op (eager) checks and the end-of-document (holistic) checks call the *same* extracted predicates at two cadences. Basis: derived — the issue body forbids "a second validator" (C24); duplicating the kind / coverage logic would be exactly that. - **Fork: per-op fallibility on `GraphBuilder` vs. a new surface** → a new per-op-fallible construction surface in `aura-engine` that reuses `GraphBuilder`'s name-resolution (`crates/aura-engine/src/builder.rs`) and the shared predicates, leaving `GraphBuilder`'s infallible-accumulate-then- `build()` contract (`builder.rs:1-9`, `:67-68`) intact. Basis: derived — the op-script's stop-at-first-failing-op is a per-op-fallible discipline, whereas `GraphBuilder`'s contract is infallible ops + one fallible `build()`. Overloading one type with both contracts is worse structural fit than a sibling surface over shared internals. - **Fork: the eager / holistic boundary** → the "every interior input slot covered exactly once" invariant (`check_ports_connected`, `crates/aura-engine/src/blueprint.rs:688`) splits along its own two failure arms: the **≤1** half (a second cover = `DoubleWiredPort`) is eagerly decidable at the `connect` / `feed` op; the **≥1** half (zero cover = `UnconnectedPort`) is only decidable once the document ends, so it stays holistic. Edge kind-match (`validate_wiring`, `blueprint.rs:638-649`) is eager; param-namespace injectivity (`check_param_namespace_injective`, `blueprint.rs:566`) is holistic. Basis: derived — a mid-document graph is legitimately incomplete, so totality cannot fire per-op; the split is forced by what is decidable when. - **Fork: document encoding** → the canonical core encoding is a **JSON op-list** (serde-native, mirroring the #155 blueprint format), demonstrated as the worked example; a line-oriented human text front-end (the `add SMA(length=2) as fast` shape illustrated earlier in this thread) is a deferred ergonomic sugar, not core. Basis: derived — the three acceptance criteria are encoding-agnostic; JSON-through-serde adds no hand-written parser / lexer (the smallest "no new language" surface) and syntax was explicitly delegated to specify/planner ("syntax is a specify/planner concern, not fixed here"). The replay / `< script` document semantics are identical either way. Consequence (not a fork): build-free vocabulary introspection (acceptance 3) needs the closed `std_vocabulary` match (`crates/aura-std/src/vocabulary.rs:30`) paired with an **enumerable** companion list of its type identities — a `fn(&str) -> Option<…>` resolver alone cannot be enumerated. The same iterable surface would also give #160's guard test something to iterate, though #160 stays a separate item. The spec auto-signs under `/boss` on a `grounding-check` PASS; a reply vetoes any of the above.
Author
Owner

Iteration 2 (§C CLI) — wire-format decision (derived)

Iteration 1 (the §A+§B engine core) shipped: aura_engine::{replay, GraphSession, Op, OpError} + the std_vocabulary_types introspection companion (commits
ea1ca32, 27ac4dc; all green). Iteration 2 is the CLI shell — aura graph build / aura graph introspect over a JSON op-list. Writing its plan
(docs/plans, cycle 0088) surfaces one load-bearing fork; derived (rationale
below), recorded for audit/veto.

  • Fork: how the JSON op-list deserializes into the engine Op → a CLI-side
    serde DTO
    (#[serde(tag = "op", rename_all = "lowercase")] + field renames
    typetype_id, asas_name) that maps into the engine Op, which stays
    serde-free
    . Basis: derived — keeping the wire shape out of the engine
    preserves the engine as a pure data type (the wire format is a CLI concern, the
    engine owns no second representation), and the document's hand-friendly keys
    (op/type/as) differ from the engine field names anyway.

    Two sub-decisions inside it:

    • Bind values use the typed Scalar form"bind": {"length": {"I64": 2}},
      not a bare 2. Basis: derived — it deserializes directly via Scalar's
      existing #155 externally-tagged serde shape (no schema-aware kind-inference
      decoder, no second vocabulary lookup), and a kind that disagrees with the
      node's declared param is caught for free by the engine's try_bind as a named
      per-op fault.
    • ScalarKind uses its native capitalized form"kind": "F64". Basis:
      derived — it is the single representation Scalar/#155/the blueprint_to_json
      build output already use, so input and output stay consistent and no bespoke
      case-mapping is introduced. (The spec's lowercase "f64" illustration was
      explicitly non-binding: "syntax is a specify/planner concern.")

Two smaller derived calls the plan records but not worth their own fork here: the
op N (kind): cause label is recovered by the CLI retaining the parsed op
list
and indexing it by replay's returned error index (no engine change, no
Op: Clone); the per-variant error phrasing is a CLI format helper (the
engine error types stay Display-free, matching CompileError /
BootstrapError / BindOpError today).

aura graph build emits the #155 blueprint JSON for a valid document; an invalid
op prints op N (kind): cause to stderr and exits non-zero; introspect
(--vocabulary / --node <T> / --unwired) answers build-free. The bare
["graph"] HTML-render arm (sample_blueprint, #159) is left intact. A reply
vetoes.

## Iteration 2 (§C CLI) — wire-format decision (derived) Iteration 1 (the §A+§B engine core) shipped: `aura_engine::{replay, GraphSession, Op, OpError}` + the `std_vocabulary_types` introspection companion (commits `ea1ca32`, `27ac4dc`; all green). Iteration 2 is the CLI shell — `aura graph build` / `aura graph introspect` over a JSON op-list. Writing its plan (`docs/plans`, cycle 0088) surfaces one load-bearing fork; **derived** (rationale below), recorded for audit/veto. - **Fork: how the JSON op-list deserializes into the engine `Op`** → a **CLI-side serde DTO** (`#[serde(tag = "op", rename_all = "lowercase")]` + field renames `type`→`type_id`, `as`→`as_name`) that maps into the engine `Op`, which **stays serde-free**. Basis: derived — keeping the wire shape out of the engine preserves the engine as a pure data type (the wire format is a CLI concern, the engine owns no second representation), and the document's hand-friendly keys (`op`/`type`/`as`) differ from the engine field names anyway. Two sub-decisions inside it: - **Bind values use the typed `Scalar` form** — `"bind": {"length": {"I64": 2}}`, not a bare `2`. Basis: derived — it deserializes directly via `Scalar`'s existing #155 externally-tagged serde shape (no schema-aware kind-inference decoder, no second vocabulary lookup), and a kind that disagrees with the node's declared param is caught for free by the engine's `try_bind` as a named per-op fault. - **`ScalarKind` uses its native capitalized form** — `"kind": "F64"`. Basis: derived — it is the single representation `Scalar`/#155/the `blueprint_to_json` build output already use, so input and output stay consistent and no bespoke case-mapping is introduced. (The spec's lowercase `"f64"` illustration was explicitly non-binding: "syntax is a specify/planner concern.") Two smaller derived calls the plan records but not worth their own fork here: the `op N (kind): cause` label is recovered by the CLI **retaining the parsed op list** and indexing it by `replay`'s returned error index (no engine change, no `Op: Clone`); the per-variant error phrasing is a **CLI format helper** (the engine error types stay `Display`-free, matching `CompileError` / `BootstrapError` / `BindOpError` today). `aura graph build` emits the #155 blueprint JSON for a valid document; an invalid op prints `op N (kind): cause` to stderr and exits non-zero; `introspect` (`--vocabulary` / `--node <T>` / `--unwired`) answers build-free. The bare `["graph"]` HTML-render arm (`sample_blueprint`, #159) is left intact. A reply vetoes.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#157