The single-file ledger had grown to 2968 lines / ~42k tokens, mixing current design law with accreted history: 59 cycle-stamped realization blocks, 18 [HISTORY] passages, 22 supersession markers, and the C10 / C22 / C24 reframe sagas layered several supersessions deep. A code-grounding audit (31 agents, adversarially verified) confirmed 11 defects stated as current truth: stale crate homes from the C28 #288 roster split (cost nodes, PositionManagement, PositionEvent, Session), the renamed InputSpec->PortSpec, the pre-#241 project model in C16 and the open-threads section, a stale HarnessKind retirement deferral in C24, and three C28-internal inconsistencies. New shape, per the ailang precedent: - INDEX.md stays the sole addressable entry point: foundation, external components, a C-id-keyed contract map (one line per contract), and only the genuinely open architectural threads. - contracts/cNN-<slug>.md carries each contract's current truth only: Guarantee / Forbids / Why with ratified refinements integrated, plus a code-anchored Current state. All confirmed defects are fixed here; crate anchors were re-verified against the tree. - contracts/cNN-<slug>.history.md (18 sidecars) and INDEX.history.md preserve every superseded block verbatim, stamps and issue refs intact, under a frozen-record banner. Nothing was deleted: superseded design intent remains an addressable working-tree artifact, off the per-cycle audit walk. - Ledger discipline is now stated in INDEX.md: live files are edited in place at cycle close, superseded text moves verbatim to the sidecar, and a supersession marker in a live file is itself an audit finding. Every contract file was verified against its old text by an independent zero-loss pass (statement-by-statement) plus a code-accuracy spot check; C-ids and contract titles are unchanged, so existing C-id citations in code, tests, and issues resolve as before.
4.6 KiB
C26 — Harness input binding: role names bind archive columns
Guarantee. A strategy blueprint declares WHICH data it consumes as the NAMES
of its root input roles: a role whose name is in the closed column vocabulary —
open, high, low, close, spread, volume, plus price as the
backward-compatible alias of close — binds by default to that column of the
campaign cell's (or run invocation's) instrument. A campaign document may
override the name default per role via the additive data.bindings block
(role name → column name; serde default + skip-if-empty, so binding-less
documents keep their content ids) — the 6b rebind seam: the blueprint's content
identity never changes.
Resolution (aura-runner's binding module, resolve_binding) produces ONE
ordered plan consumed by BOTH halves of the old weld: the columns are opened
(aura_ingest::open_columns, the generalization of open_ohlc/#92) and the
wrapped root roles declared (aura-runner::member's wrap_r) in the same
canonical order — M1Field declaration order (open, high, low, close, spread,
volume), filtered to the consumed set — which is the C4
merge tie-break order, so role i receives source i by construction. A close
column is always part of the plan (shared when the strategy consumes
close/price, else opened for the broker/executor pair alone).
The vocabulary is Blockly-litmus-clean (C25): role-name
slots draw from a closed enum, the bindings block is typed role → column with no
free-form holes, and validation is two-tier — values against the column vocabulary
in the intrinsic tier (validate_campaign, aura-research), keys against the
strategies' input_roles() in the resolver tier (validate_campaign_refs,
aura-registry).
Forbids. Guessing a column for an unknown role name (refuse with the vocabulary
- the override remedy, never default); opening columns in any order other than the
canonical declaration order (the C4 tie-break contract);
free-form or logic-bearing binding values (the C17/C25
line — a binding value is a typed column reference, nothing else); fabricating
synthetic OHLC when a multi-column strategy meets synthetic data (the walk
generates a close series only — refuse with the
--realremedy).
Why. The single-price weld — wrap_r's hard-wired price←close and the six
M1Field::Close-only open sites — made every strategy monocular regardless of what
its blueprint declared. Binding by role name keeps the blueprint the single source
of its own data needs (C24: topology-as-data carries its
input contract), lets a campaign re-aim a strategy without touching its content id
(C18 identity), and pins opening and declaring to one
shared, canonically-ordered plan so the two halves cannot drift
(C1/C4 determinism).
Current state
resolve_binding lives in aura-runner's binding module
(crates/aura-runner/src/binding.rs), moved out of the shell with #295; it takes
the strategy name, its input_roles(), and the campaign override map and returns
one ResolvedBinding plan. Column opening is aura_ingest::open_columns
(crates/aura-ingest; the M1Field enum fixes the declaration order
open/high/low/close/spread/volume); role declaration is wrap_r in
aura-runner::member. Both validation tiers are realized: validate_campaign
(crates/aura-research) checks binding values against the column vocabulary;
validate_campaign_refs (crates/aura-registry) checks binding keys against the
strategy's declared roles.
Extension point (deferred). When recorded non-price sources land (the #71
Source seam), the binding VALUE-space grows additively from archive columns to
recorded-stream references — a root role like sentiment becomes bindable to a
recorded stream then, and until then refuses with the vocabulary-naming message.
The role-name key-space and the campaign bindings carrier are unchanged by that
growth.
See also
- C4 — the merge tie-break order that is the canonical open/declare order
- C24 — topology-as-data carrying its own input contract
- C18 — the content-id identity a rebind must not touch
- C25 — the Blockly litmus the vocabulary satisfies; the 6b rebind seam
- C1 — the determinism the shared plan protects
- C27 — the output-side twin (
tapstoinput_roles)