Typed construction args for vocabulary nodes — reach non-scalar structural config (Session timezone) from data-only blueprints #271
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?
Motivation
Roster factories are zero-arg by construction: the
std_vocabulary_roster!macro expands every entry to<$ty>::builder()(crates/aura-std/src/vocabulary.rs), and the only post-construction channel is.bind()over the four scalar param kinds. Any node carrying non-scalar structural config is therefore unreachable from a data-only blueprint and must be baked in Rust.The first real-world case is
Session: its timezone and open time are structural (chrono_tz::Tzis not a scalar), so the closed vocabulary can reach it only through per-city baked presets (SessionFrankfurt, #261). A research project wanting the New York session for a US index today has to author a node crate — a pure data variation (same logic, different timezone) is forced through the logic channel (role 2). That inverts the C25 principle: closed, typed vocabularies as serialized data are the proven pattern; new logic escalates to Rust, new data must not.Provenance: the user ruled the status quo unacceptable for real-world projects and requested this issue — 2026-07-14.
Sketch (shape only — the concrete form is deliberately undecided)
A second, separate channel on the construction op: typed construction args, declared per roster entry, consumed once at bootstrap.
Guardrails any implementation must keep
tzis a different harness); they are, for the same reason, legitimate experiment-matrix axes.Performance note
PrimitiveBuildercreation and roster resolution happen at bootstrap ("construction is a bootstrap phase"), once per harness member — never inside the per-tick eval loop. Arg parsing and validation are therefore not performance-critical; the hot path is unaffected by design.Open (to be decided before implementation)
PrimitiveBuilder/NodeSchema; which closed arg kinds exist beyondTzId/TimeOfDay).SessionFrankfurtremains as sugar or is retired onceSessionitself is reachable.Re-anchoring note: the file paths cited in this issue and in its closed predecessor #261 have moved and no longer resolve.
std_vocabulary_roster!) and its scope-limitation doc comment, previously atcrates/aura-std/src/vocabulary.rs, now live atcrates/aura-vocabulary/src/lib.rs(moved in commitb39fd63, "refactor: split the aura-std roster into C28 layer crates", 2026-07-19, part of the unrelated C28 internal-stratification work, not the #295 shell-boundary cycle).aura-std, is atcrates/aura-market/src/session.rs; the SessionFrankfurt baked preset is exported fromcrates/aura-market/src/lib.rs.The substance of the issue is unchanged: the roster's doc comment at the new location (
crates/aura-vocabulary/src/lib.rs, lines 10-17) still namesSession(..)among the builders excluded from the zero-arg vocabulary because their construction config is structural, not scalar, and still forward-references the args extension as a later addition. No work since this issue was filed has touched typed construction args; SessionFrankfurt remains the sole workaround.This issue's construction-time concern (non-scalar node config surfaced as blueprint data) is a distinct layer from the run-time CLI flag migration decided in #300 (--axis, --from/--to, --seeds, etc. moving into document vocabulary), but shares its underlying direction — text artifacts as the canonical layer, control surfaces as projections over them (C25). No relevance change to the open questions in this issue; only the path citations need updating for anyone picking it up.
Design reconciliation (specify)
Spec: construction-args. The issue's four open questions (plus one recon-surfaced) are still listed open; this records their resolution — all derived orchestrator decisions.
Op::Add's own payload and serialize as a newPrimitiveDatafield mirroringbound; they are consumed via a new args seam onPrimitiveBuilder. The vocabulary resolver keeps itsFn(&str) -> Option<PrimitiveBuilder>shape, including the C-ABIProjectDescriptorfn pointer.Basis: derived — the resolver is a per-type lookup while args are per-instance data (exactly like
bound); this keepsdescriptor_versionstable, leaves the C29 load gate untouched, and makes reconstruction symmetric (resolve → apply args → apply bound).ArgKindtable besideScalarKind(initially Tz, TimeOfDay, Count); values serialize as plain strings in documents and are load-time-validated (IANA table via chrono-tz, strict HH:MM, positive count).Basis: derived —
ScalarKindis domain invariant 4's streamed set; construction args are never streamed, so extending it would conflate two different closedness axes.SimBroker::builder(pip_size: f64)is a different, scalar-typed gap (should be an ordinary ParamSpec) and stays out of scope.Basis: derived — proving the mechanism on two arg families (Tz/TimeOfDay and Count with an arity-dependent schema) shows it is generic, not a Session special case; the roster doc already names exactly these three.
Basis: derived — registered blueprints resolve nodes by
type_idat load; retiring the entry would orphan loadable registered artifacts, and C29's discipline is that registered artifacts are never retroactively invalidated.Basis: derived — the campaign axis vocabulary (
aura-researchAxis/StrategyEntry) is a different serialization surface with its own closed-vocabulary discipline; coupling the two would widen this cycle past its boundary.Basis: derived — a global bump would change every canonical JSON and drift every existing content id (C18 breach), while args are genuinely must-understand for old loaders (C24 tier-2); the data-driven version is the only form satisfying both.
Mid-cycle minute (implement batch 2)
The spec's worked op-script example was unbuildable as written: it wired
Session.bars_since_open(I64) intoLinComb.term[0](F64) — a kind mismatch the existing connect gate correctly refuses (a spec prose/example defect, no behaviour question). The implementation's e2e testgraph_build_accepts_the_session_args_examplepins the corrected form (LinComb blends two same-kind SMAs;bars_since_openis exposed as a second output field). The spec example was aligned to the tested form after the fact.Derived decision: no grounding-check re-run for this post-PASS spec edit. Basis: the edit removes a false assumption (that an I64→F64 wire would build) rather than introducing a new one, changes no design decision, and the corrected example's behaviour is pinned by a green test written this cycle. Recorded here for audit/veto.