audit: cycle 0018 (#40) — drift-clean; C9 multi-output realization note

Architect drift review over a4cfe5c..HEAD (covers the previously-unaudited
EMA node and MACD PoC plus the #40 composite multi-output cycle): status
clean. C8/C7/C4 preserved (one port, one row per eval, K co-fresh base
columns — structurally identical to OHLCV); C23 honoured (ItemLowering::
Composite.output is Vec<(usize,usize)>, no name in the compilat; the
compiled-view render stayed bit-identical). EMA and MACD PoC carry no
ledger drift.

Resolution of the three minors:
- Ledger lagged the new behaviour: added a "Realization (cycle 0018)" note
  under C9 recording that a composite's output is now a named K-field record
  (Vec<OutField>), the same arity C8 already grants a leaf, names dropped at
  lowering (C23). Amended here (ledger amendment is the audit's job).
- blueprint.rs module-doc / Composite struct-doc / new-doc still said "one
  output port" singular — updated to "output record".
- Non-workspace fieldtest From<Sma> debt is pre-existing (cycle-0016) and
  already tracked as #42; not re-filed.

No regression scripts configured (profile regression: []), so the architect
review is the sole gate. Carry-on: the next natural iteration is the #41
legibility sibling (input-role + param naming at the composite boundary).
This commit is contained in:
2026-06-08 01:05:41 +02:00
parent 784e6c917a
commit 8bc429f367
2 changed files with 23 additions and 4 deletions
+6 -4
View File
@@ -1,8 +1,9 @@
//! The construction layer (C9/C19/C23): a named, param-generic graph-as-data
//! (`Blueprint`) that **compiles** to the flat, type-erased instance the run loop
//! already runs (the *compilat*). The unit of reuse is the [`Composite`]: a
//! nestable sub-graph fragment exposing one output port (C8) and named input
//! roles, which `compile` **inlines** into the flat `(nodes, sources, edges)` the
//! nestable sub-graph fragment exposing an output record (one port, K re-exported
//! fields; C8) and named input roles, which `compile` **inlines** into the flat
//! `(nodes, sources, edges)` the
//! unchanged [`crate::Harness::bootstrap`] consumes.
//!
//! The compilat is wired by raw index, **not by name** (C23): a composite's
@@ -43,7 +44,8 @@ impl From<LeafFactory> for BlueprintNode {
/// A reusable sub-graph fragment compiled away by inlining (C9/C23). It is **not**
/// a [`Node`]: it is never `eval`'d. It holds interior items (local indices),
/// interior edges (local indices), input roles (role `r` fans into the interior
/// targets `input_roles[r]`), and the one exposed output port.
/// targets `input_roles[r]`), and the exposed output record (each entry
/// re-exports one interior `(node, field)` under a boundary name).
pub struct Composite {
name: String,
nodes: Vec<BlueprintNode>,
@@ -54,7 +56,7 @@ pub struct Composite {
impl Composite {
/// Build a composite from its authored name, interior items, interior edges
/// (local indices), input roles, and output port. The `name` is a
/// (local indices), input roles, and output record. The `name` is a
/// non-load-bearing render symbol (the cluster title for #13); it does not
/// reach the compilat (the boundary dissolves at inline, C23).
pub fn new(
+17
View File
@@ -292,6 +292,23 @@ is). So "nestable arbitrarily" and "graph-as-data" hold at the **blueprint
driving a nested sub-engine* — is **explicitly rejected**: it would keep the
interior opaque to the cross-graph optimiser (C23) and add a runtime sub-loop the
flat model does not need. Inlining is what makes the composite boundary free.
**Realization (cycle 0018 — composite multi-output record, #40).** "Exposes one
output" is one output **port** carrying a **record of 1..K re-exported fields**, not
one field: `Composite.output` is a `Vec<OutField { node, field, name }>` (was a
single `OutPort`). Each entry is a **named projection** of one interior
`(node, output-field)`; a consumer selects which re-exported field it reads via the
same `Edge::from_field` that already binds leaf record columns (C8 realization,
cycle 0005). This is the **same arity** C8 already grants a leaf (OHLCV = one port,
5 columns): a multi-line indicator (MACD = macd/signal/histogram) is **one** record
of K fields, **one** port, **one** row per `eval` — C8/C7/C4 untouched, a boundary
completion, not a contract change. A strategy composite is simply the K=1 case
(one exposure field, C10). The re-export **names** are **non-load-bearing** (C23):
they live at the blueprint boundary and in render (`[out:<name>]` markers, #13)
but are dropped at lowering — `ItemLowering::Composite.output` is `Vec<(usize,
usize)>`, raw index pairs only, so the compilat is name-free (verified: the
compiled-view render stayed bit-identical across this change). Input-role and
param **names** at the same boundary remain index-only pending #41 (the legibility
sibling that depends on this cycle).
### C10 — Strategy output is an intent/exposure stream; position management is a decoupled derived layer; brokers are downstream nodes
**Guarantee.** A strategy's primary, backtestable output is **not** an equity