Retire the bias-output magic string as the strategy/measurement run-dispatch discriminator
#287
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Situation
aura rundecides whether a loaded blueprint is a strategy or a measurement by testing whether its output exposes a port literally named"bias":This landed with C28 phase 3 (the measurement run verb —
docs/design/INDEX.mdcontract 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, andwrap_ralready 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 thewrap_rbroker/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)
strategy|measurement) — the layer as a first-class, explicit blueprint property.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.
This issue's claims remain fully current after the #295 shell-boundary extraction.
dispatch_runstill lives atcrates/aura-cli/src/main.rs(now lines 1753-1839) and still discriminates a strategy run from a measurement run by testingsignal.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_rtocrates/aura-runner/src/member.rs,run_measurementtocrates/aura-runner/src/measure.rs— but left the discriminator itself in the shell. That is not drift: perdocs/design/INDEX.mdC28'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.