Retire the bias-output magic string as the strategy/measurement run-dispatch discriminator #287

Open
opened 2026-07-19 19:22:47 +02:00 by claude · 1 comment
Collaborator

Situation

aura run decides whether a loaded blueprint is a strategy or a measurement by testing whether its output exposes a port literally named "bias":

// crates/aura-cli/src/main.rs, dispatch_run (the shape branch, commit 34ff539)
let has_bias = signal.output().iter().any(|o| o.name == "bias");
let has_tap  = !signal.taps().is_empty();
if has_bias { /* strategy: wrap_r + R evaluation */ }
else if has_tap { /* measurement: bare run */ }
else { /* refuse */ }

This landed with C28 phase 3 (the measurement run verb — docs/design/INDEX.md contract C28 Status; #286).

Why the magic string is only transitional (decided 2026-07-19)

Keying the dispatch on the "bias" port name is consistent with the existing contract — domain invariant 7 / C10 define a strategy's backtestable output as the directional bias stream, and wrap_r already welds the R scaffold onto the hardcoded "bias" output port. That consistency is exactly why it was the minimal, behaviour-preserving choice for the additive phase 3 (the strategy path stays byte-identical).

But it makes a C28 layer boundary — strategy vs measurement, sibling layers — ride on a magic string rather than a declared property. The decision recorded here: the magic-string discriminator is a transitional solution, to be retired once the strategy/measurement distinction is made explicit. The right replacement is deliberately left open (no design chosen).

The concrete sharp edge (verified by construction)

A measurement blueprint that happens to expose an output port named "bias" is dispatched as a strategy — it gets the wrap_r broker/executor/R scaffold that the measurement path exists to avoid. Rare in practice (a measurement surfaces its result through declared taps, not through an exposed "bias" output), but latent: the layer a run takes is determined by a string, not by anything the author declared about the blueprint's kind.

Candidate directions (recorded as options, none chosen)

  • A declared blueprint kind (strategy | measurement) — the layer as a first-class, explicit blueprint property.
  • Dispatch on a typed / semantic bias output (the bias-stream contract) instead of the port name.
  • Something else entirely.

The right alternative is an open design question.

Scope

Independent of the #147 metric-genericity block (not coupled to it). A follow-up to the stratification milestone's phase 3, not a blocker for it — the current discriminator works and is behaviour-preserving; this records that it is a stopgap, not the end state.

## Situation `aura run` decides whether a loaded blueprint is a **strategy** or a **measurement** by testing whether its output exposes a port literally named `"bias"`: ```rust // crates/aura-cli/src/main.rs, dispatch_run (the shape branch, commit 34ff539) let has_bias = signal.output().iter().any(|o| o.name == "bias"); let has_tap = !signal.taps().is_empty(); if has_bias { /* strategy: wrap_r + R evaluation */ } else if has_tap { /* measurement: bare run */ } else { /* refuse */ } ``` This landed with C28 phase 3 (the measurement run verb — `docs/design/INDEX.md` contract C28 Status; #286). ## Why the magic string is only transitional (decided 2026-07-19) Keying the dispatch on the `"bias"` port name is **consistent with the existing contract** — domain invariant 7 / C10 define a strategy's backtestable output *as* the directional bias stream, and `wrap_r` already welds the R scaffold onto the hardcoded `"bias"` output port. That consistency is exactly why it was the minimal, behaviour-preserving choice for the additive phase 3 (the strategy path stays byte-identical). But it makes a **C28 layer boundary** — strategy vs measurement, sibling layers — ride on a magic string rather than a declared property. The decision recorded here: the magic-string discriminator is a **transitional solution**, to be retired once the strategy/measurement distinction is made explicit. The right replacement is **deliberately left open** (no design chosen). ## The concrete sharp edge (verified by construction) A measurement blueprint that happens to expose an output port named `"bias"` is dispatched as a **strategy** — it gets the `wrap_r` broker/executor/R scaffold that the measurement path exists to avoid. Rare in practice (a measurement surfaces its result through declared taps, not through an exposed `"bias"` output), but latent: the layer a run takes is determined by a string, not by anything the author declared about the blueprint's kind. ## Candidate directions (recorded as options, none chosen) - A declared blueprint **kind** (`strategy` | `measurement`) — the layer as a first-class, explicit blueprint property. - Dispatch on a **typed / semantic** bias output (the bias-*stream* contract) instead of the port *name*. - Something else entirely. The right alternative is an open design question. ## Scope Independent of the #147 metric-genericity block (not coupled to it). A follow-up to the stratification milestone's phase 3, not a blocker for it — the current discriminator works and is behaviour-preserving; this records that it is a stopgap, not the end state.
claude added the idea label 2026-07-19 19:22:47 +02:00
Author
Collaborator

This issue's claims remain fully current after the #295 shell-boundary extraction. dispatch_run still lives at crates/aura-cli/src/main.rs (now lines 1753-1839) and still discriminates a strategy run from a measurement run by testing signal.output().iter().any(|o| o.name == "bias") against !signal.taps().is_empty() (lines 1785-1786), with the same in-code rationale comment this issue paraphrases. #295 relocated the two branches this dispatch calls into — run_signal_r/wrap_r to crates/aura-runner/src/member.rs, run_measurement to crates/aura-runner/src/measure.rs — but left the discriminator itself in the shell. That is not drift: per docs/design/INDEX.md C28's post-#295 status, the shell's retained mandate is exactly "argv/dispatch, argv→document translation, presentation," so shape-dispatch stays where it is. The ledger itself still records the mechanism as the open stopgap this issue names: C28 phase 3 notes the measurement rung's "run verb remains the additive shape dispatch, #286."

Context worth folding in for whoever resolves this: the owner-decided "document-first completion" direction (#300) and the #295 C25 amendment ("control surfaces are projections... the text artifact vocabulary — blueprints, process/campaign documents, registry records — is the canonical layer") both lean toward this issue's first recorded candidate — a declared blueprint kind (strategy | measurement) carried as document vocabulary — over the second (a typed/semantic bias-output contract sniffed at dispatch time). A document-first world naturally wants the strategy/measurement layer boundary declared once in the blueprint document and consumed identically by the CLI executor, a future host, or a World program, rather than re-derived per control surface from the compiled signal's port names. This does not resolve the fork — it stays explicitly open, and independent of #147 as before — it only narrows which of the two recorded options fits the direction the project has since committed to.

This issue's claims remain fully current after the #295 shell-boundary extraction. `dispatch_run` still lives at `crates/aura-cli/src/main.rs` (now lines 1753-1839) and still discriminates a strategy run from a measurement run by testing `signal.output().iter().any(|o| o.name == "bias")` against `!signal.taps().is_empty()` (lines 1785-1786), with the same in-code rationale comment this issue paraphrases. #295 relocated the two branches this dispatch calls into — `run_signal_r`/`wrap_r` to `crates/aura-runner/src/member.rs`, `run_measurement` to `crates/aura-runner/src/measure.rs` — but left the discriminator itself in the shell. That is not drift: per `docs/design/INDEX.md` C28's post-#295 status, the shell's retained mandate is exactly "argv/dispatch, argv→document translation, presentation," so shape-dispatch stays where it is. The ledger itself still records the mechanism as the open stopgap this issue names: C28 phase 3 notes the measurement rung's "run verb remains the additive shape dispatch, #286." Context worth folding in for whoever resolves this: the owner-decided "document-first completion" direction (#300) and the #295 C25 amendment ("control surfaces are projections... the text artifact vocabulary — blueprints, process/campaign documents, registry records — is the canonical layer") both lean toward this issue's first recorded candidate — a declared blueprint **kind** (strategy | measurement) carried as document vocabulary — over the second (a typed/semantic bias-output contract sniffed at dispatch time). A document-first world naturally wants the strategy/measurement layer boundary declared once in the blueprint document and consumed identically by the CLI executor, a future host, or a World program, rather than re-derived per control surface from the compiled signal's port names. This does not resolve the fork — it stays explicitly open, and independent of #147 as before — it only narrows which of the two recorded options fits the direction the project has since committed to.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#287