From 7a4e5eb99dab4c26a555ab77e2fad06250ba54c1 Mon Sep 17 00:00:00 2001 From: claude Date: Sat, 18 Jul 2026 00:17:01 +0200 Subject: [PATCH] =?UTF-8?q?docs(ledger):=20C27=20=E2=80=94=20declared=20ta?= =?UTF-8?q?ps=20contract=20(#282)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records the tap/binding contract: taps are the output-side twin of input_roles (C26), resolved and hoisted at compile, bound run-mode-aware via a caller-built sink (engine stays aura-core-only), recorded on the single run and inert in a sweep. Documents the deliberate DCE-deferral — build-time elision now, chain-pruning when DCE (C23) lands — and the unbound-is-inert non-error asymmetry vs a mandatory input role. refs #282 --- docs/design/INDEX.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/design/INDEX.md b/docs/design/INDEX.md index cf4ba37..6db0835 100644 --- a/docs/design/INDEX.md +++ b/docs/design/INDEX.md @@ -2542,6 +2542,49 @@ plan so the two halves cannot drift (C1/C4 determinism). --- +### C27 — Declared taps: named measurement points bind sinks run-mode-aware +**Guarantee.** A blueprint may declare **taps** — named, pure output-side +declarations `{ name, from: {node, field} }`, the output-side twin of +`input_roles` (C26). A tap names an interior producer's output field without +naming a sink, exactly as a `Role` names an abstract input without naming a +source. At compile the tap resolves — and, for an interior composite, **hoists** +to the root — through the same lowering remap edges and `OutField` re-exports +use (`resolve_tap_wire`, a `flat_taps` accumulator threaded through the lowering +recursion), landing in `FlatGraph.taps` as a `FlatTap { name, node, field }` +whose name survives compile and is load-bearing for by-name binding (like +`SourceSpec.role`, #275). Binding is run-mode-aware: the run-mode-owning layer +constructs a sink (a `Recorder`) at a bound tap via `FlatGraph::bind_tap`, which +takes a **caller-built** `Box` sink (so the engine keeps its +`aura-core`-only production dependency — it never constructs a domain sink type) +and appends it plus an edge before bootstrap. The single-run path (`run_signal_r`) +binds and records each declared tap, persisting the series through the existing +trace store (`env.trace_store()`), so the tap columns surface through the same +tooling the campaign path feeds; a sweep/reduce run leaves taps unbound. +**Forbids.** A tap carrying a channel endpoint or effect in the serialized +artefact — recording policy is run-mode authority, not fragment-embedded (a +fragment must not drag its measurement decisions into every harness that embeds +it; the tier ontology, C20/C21). The engine constructing a domain sink type +(the `aura-core`-only wall — the sink is caller-built). Order statistics +(median, etc.) inside the graph — they stay sink/analysis-side (C18); +multi-instrument study inputs stay harness/World tier. +**Non-error.** An **unbound** tap is inert, not a fault — unlike an unbound root +input role, which `check_root_roles_bound` rejects (C26): observation is optional, +a fed input is mandatory. A declared-but-unbound tap compiles and runs, its +producer evaluating and its output discarded (a no-out-edge producer is a valid +runnable sink — the Kahn sort emits it, `check_ports_connected` gates only inputs). +**Why.** Observability must be expressible in a hand-authored blueprint — the +measurement-shaped study computes in the graph and surfaces via taps, no +throwaway Rust harness — while recording stays a run-mode decision, not a +fragment-embedded effect. Taps are designed **DCE-compatible** (a bound tap is a +natural DCE root, an unbound tap a dead declaration) but this contract does +**not** depend on DCE: an unbound tap's sink is simply never constructed +(build-time elision, which the engine already tolerates). The chain-pruning +benefit — a sweep paying zero for the study wires behind an unbound tap — is +**deferred to the future DCE cycle** (C23); the mechanism ships now, verified +sound. (#282, 2026-07-18.) + +--- + ## Open architectural threads not yet resolved - **Playground & World UI surface** — the playground is core (C22); the surface