Commit Graph

37 Commits

Author SHA1 Message Date
claude 4474814fa7 audit: #328 cycle close — manifests record raw on every route, walkforward intake refuses wrapped
Architect drift review (cycle a3785a6..b3b7115) found the cycle's raw
switch incomplete on two high items; both fixed forward here rather
than minuted as residue, since #319 builds on this namespace:

- The real/campaign executor still minted WRAPPED manifest.params and
  manifest.defaults (AC2 overclaim in the feat commit body). The mint
  seams now reshape to raw on every route (member.rs raw_bound_defaults,
  runner.rs manifest_space mirror of the family.rs pattern); the fix
  surfaced a latent regression in persist_campaign_traces, which keyed
  point_from_params by exact wrapped name and is now translation-aware
  (same recipe as reproduce_family_in). Old on-disk families stay
  replayable (C29; reproduce reads both shapes).
- The walkforward --axis intake silently accepted wrapped names — the
  silent alias fork 2 rejected. It now runs the same
  refuse_wrapped_synthetic_axes preflight as the plain synthetic sweep
  (new e2e: aura_walkforward_synthetic_blueprint_refuses_a_wrapped_form
  _axis_name); family builders translate raw names onto wrapped
  SweepBinder slots on all three entry points. C24's "retired from the
  surface" claim is thereby made true instead of softened.
- Low items: stale graph_construct docstring updated; new
  graph_params_and_sweep_list_axes_are_line_identical pins the two
  discovery surfaces against format drift.
- Bench infra: aura-bench's campaign_sweep surface seeded wrapped axis
  literals and was correctly refused by the new intake — seeds converted
  to raw; full bench rerun: all 5 surfaces fingerprint OK, deltas within
  load noise (report-only, no baseline update needed).

Library-unit tests calling family builders directly keep wrapped input
(valid: translation is idempotent on exact wrapped names; intake-level
refusal is the user contract). Suite + clippy verified green post-fix.

refs #328
2026-07-25 01:58:50 +02:00
claude 3e1e7e21da feat(aura-runner, aura-cli, aura-registry): one raw axis namespace end to end
Reconciles the sweep-axis namespace (closes #328): the raw form
<node>.<param> (splice paths keep their interior path) is now the only
user-facing axis name — printed by --list-axes, accepted by --axis on
BOTH sweep routes, recorded in the synthetic run manifest, discovered
by graph introspect --params (bound params included, default= lexicon),
and validated by campaign documents. The wrapped <blueprint>.<...> form
is retired with translation refusals on both seams.

Mechanics:
- classify_axis_intake (aura-runner axes.rs): explicit raw-first
  acceptance predicate shared by both --axis intake routes. Deliberately
  not built on raw_matches_wrapped, whose equality branch would have
  accepted the retired form silently (skeptic finding, minuted on #328).
  Wrapped hit -> translation refusal naming the raw candidate (shared
  prose builder wrapped_axis_refusal, one literal for both routes).
- The synthetic route resolution moves to the raw frame the real route
  (bind_axes) already uses: override_paths/wrapped_bound_overrides_of
  match via raw_matches_wrapped (also removing a latent slicing panic),
  blueprint_sweep_family translates raw names onto wrapped SweepBinder
  slots, manifest.params records raw keys (name-only reshaping, zip is
  positional). The two sweep routes now share one convention.
- reproduce translates recorded manifest names raw-or-wrapped onto the
  wrapped space: old registered artifacts stay replayable (C29), new
  raw manifests round-trip.
- introspect --params gains the bound default= lines via the same
  render_value the --list-axes bound pass uses - the two discovery
  surfaces are byte-identical by construction.
- AxisNotInParamSpace carries raw_candidate; ref_fault_prose renders
  the did-you-mean iff present. Sweep-terminal bind errors
  (MissingKnob/KindMismatch) render the raw knob name (render seam
  only; acceptance criterion: no user-facing surface prints a wrapped
  axis name).
- Downstream consequence fixes: scaffold quickstart example spoke
  wrapped; ~90 wrapped test literals converted to raw across six test
  files (deliberately-bogus refusal names and the untouched internal
  walkforward wrapped route kept, with comments).

Fork decisions and the skeptic correction are minuted on #328
(comments 2026-07-24/25). Grounding-check PASS first attempt (12
assumptions ratified). Review (opus): translation ambiguity cleared
(uniform single prefix per blueprint), reproduce both-shapes traced;
repair pass added the synthetic-route refusal e2e + the shared prose
builder. Verified: cargo test --workspace green (99 targets, 0
failures), clippy -D warnings clean.

refs #246, refs #319, refs #331
2026-07-25 01:33:18 +02:00
claude a8b1ba45c5 feat(aura-engine, aura-vocabulary, aura-cli): the args channel end to end
The data plane reaches non-scalar structural config (closes #271, the
final Data-authorability-boundary item). Op::Add gains args
(string pairs, applied via try_args after resolve and before the bind
loop — no pending builder ever enters a graph; an arg-bearing type
without args refuses as MissingArg). OpError::BadArg carries the
precise ArgOpError; the CLI renders it as prose naming the arg, its
kind, and the kind's closed hint (exit 1, #175 content-fault class).

Serde: PrimitiveData.args (skip-if-empty — args-free documents stay
byte-identical, golden-pinned, so every existing content id is stable
per C18) with a data-driven format version: the writer emits 1 for
args-free documents and 2 when any primitive recursively carries args;
the loader accepts 1..=2. The old v2-refusal pin flips deliberately to
v3 (named contract change). Identity JSON keeps args — they are
structural, and the Rust configured() path and the args op-script
bridge to the same identity id (pinned).

Roster: Session / LinComb / CostSum enter (33 -> 36, both count pins;
the LinComb-rejection assertion flips to the resolved side). introspect
--node shows arg rows (name, kind, hint) plus the pending note;
OP_REFERENCE's add row teaches the args form. Registry comment
de-staled (LinComb is now roster-reachable).

Sequencing note: this commit lands op seam and roster together so no
intermediate tree state exposes a pending builder.
2026-07-24 21:43:02 +02:00
claude 623d304b7f feat(aura-cli): use op on the data plane — register --name, resolution echo, discovery
The op-script gains its tenth verb end-to-end: OpDoc::Use carries a tagged ref
({"content_id": id-or-unique-prefix} | {"name": label}); DTO conversion resolves
it (label sidecar / #302 prefix semantics / verbatim id), fetches, deserializes
eagerly (a vocab/parse failure fails fast naming the id prefix — review
finding, no swallowed .ok()), re-walks the C29 doc gate over the fetched
composite (a doc-less store entry cannot enter a NEW composition; existing
reads stay valid — no retroactive invalidation), echoes every resolution as
the existing benign marker (aura: note: use "<instance>": <ref> -> <id>,
stderr; stdout stays clean payload), and hands the session a pure cached
lookup closure.

graph register <file> --name <label> records the label and echoes repoints
(label "x" -> <new> (was <old>)); graph introspect --registered lists
label / 12-char id prefix / root doc line — the discovery surface for what
use can reference. Refusals are by-identifier, name their rule, and enumerate
the label set (empty store reads as prose); all op-list content faults are
runtime exit 1 per the pinned #175 attribution, argv misuse stays exit 2.

Ten new/extended e2e tests cover label round-trip + repoint, echo discipline,
unknown-label enumeration, the C29 refusal shape, end-to-end sweep axes
through a spliced instance (graph.<instance>.<node>.<param>), the open-pattern
build flow, and the standalone-run bootstrap refusal (asserts the existing
Debug-form fault; an index->name rendering at the run boundary is recorded
residue, not silently claimed). OP_REFERENCE and its help pin move 9 -> 10.

Empirical note for the record: aura run's bias arm re-roots the loaded signal
via wrap_r, so the standalone-run refusal for an open pattern is exercised on
the bare-tap measurement path; the bias path surfaces openness as a role-
binding refusal instead (pre-existing shape, untouched).

closes #317
2026-07-24 19:45:10 +02:00
claude 8dbca82756 fix(aura-cli): --folds renders the registry roster; op-lists refuse unknown keys
Two fold-selector/op-script surface-honesty bugs from the 2026-07-24
fieldtests, both RED-first.

--folds (#332): graph introspect --folds rendered the aura-std FoldKind
table — capitalized ids (Mean) and no record entry — while aura run
--tap resolves labels against the fold-registry roster (lowercase,
record included). The discovery surface the --tap help points at thus
misdescribed the accepted input twice. It now renders
FoldRegistry::core().roster() (label + doc per entry); the pinned
FoldKind-rendering test was rewritten to pin the roster form (8 rows,
no capitalized id leaks, record present).

Unknown op-list keys (#326): a typo'd key in an op-list element
("params" for "bind") was silently dropped and the wrong graph built
with zero signal, contradicting the closed-vocabulary posture (C25).
OpDoc now carries serde deny_unknown_fields; the refusal names the
offending key. Exit class is 1, not the issue's suggested 2: op-lists
arrive on stdin, and the pinned #175 attribution principle classes
stdin-content faults as runtime — the new refusal fires from that same
deserialize. Blast radius verified: all 21 committed *.ops.json under
fieldtests/ still build cleanly.

closes #332
closes #326
2026-07-24 16:12:12 +02:00
claude 7126886b81 feat(engine, cli): op-script doc slot -- the vocabulary grows the C29 meaning line
Iteration 3 completion (tasks 5-6): the construction vocabulary gains
the slot its own #125 scope-cut comment anticipated. Op::Doc { text }
is the op-script twin of the builder's .doc(...) -- a closed, typed
metadata construct (C25, no logic content); at most one per script, a
second refuses via OpError::DuplicateDoc (the existing duplicate-fault
family), rendered "a doc op may appear at most once". GraphSession
threads the text through Composite::with_doc at finish, so op-built
composites pass the C29 store gate like builder-built ones.

The op-script register test runs on the described variant
(SIGNAL_DOC_DESCRIBED) for both shapes; the doc-less script now pins
the C29 refusal, and the E2E phase added the RestatesName op-script
case. Full gates: cargo test --workspace green (98 suites, 0 failed);
clippy -D warnings clean.

refs #316
2026-07-23 20:33:00 +02:00
claude 593e233e66 test(cli): C29 register-refusal e2e -- doc-less root, doc-less nested, described twin
Iteration 3 task 4: register_refuses_undescribed_composites_end_to_end
pins the store gate's verb surface -- a doc-less root refuses naming
`sma_signal` and the rule, a doc-less named nested composite refuses
naming `inner`, the described shipped example registers (exit codes and
stderr through the C14 contract). The pre-existing twin pin
graph_doc_field_moves_content_id_but_not_identity_id stays green.

Checkpoint note: the loop escalated this task spec-ambiguous because
its plan gate demanded a fully green graph_construct suite while the
op-script register test is known-open until the doc-op task lands (a
plan-sequencing defect, orchestrator's authoring error); the task's own
work was verified green and is committed here, the doc-op task follows.

refs #316
2026-07-23 19:53:49 +02:00
claude 43a1cc44d3 feat(graph): name-addressed tap construction op — declared taps without raw indices
Declared taps (C27, #282) were authorable only by hand-patching the raw
blueprint envelope's `taps` array, where `node` is a raw interior index —
an index miscount was the expected mistake and the root friction behind the
two `aura run` panics the measurement-milestone fieldtest found.

Add a `tap` op to the construction op-script vocabulary — the exact
output-side twin of `expose`. It names its source wire by the dotted
`"node.field"` identifier the op surface uses everywhere (`split_port` +
`resolve_output_field` + `node_index`), so
`{"op":"tap","from":"fast.value","as":"fast_ma"}` replaces patching
`{"node":14,"field":0}`.

Engine (construction.rs): `Op::Tap`, its `tap()` handler (a verbatim twin
of `expose` — same resolvers, same name-dedup, appending a `Tap` to a new
`taps`/`tap_names` namespace), and `OpError::DuplicateTap` (the twin of
`DuplicateOutput`, stricter than the raw envelope, which enforces no
tap-name uniqueness). `finish()` now threads `.with_taps(self.taps)` into
the built `Composite` — fixing a latent drop #282 left (it built
`.with_gangs` but never `.with_taps`, so op-built composites silently lost
their taps).

CLI (graph_construct.rs): `OpDoc::Tap` (externally tagged, `"as"`-renamed
like `expose`) + the `kind_label` / `From` / `format_op_error` arms (the
last compiler-enumerated by the exhaustive match).

No change to the `Tap` / `FlatTap` / `bind_tap` / `aura run` machinery — the
op-declared tap flows through the existing #282 compile + record path
unchanged.

Verification: 7 new tests green (4 engine: resolve+append, bad-port /
ghost-node refusal, duplicate-name refusal, finish-threads-into-FlatGraph;
1 CLI serde round-trip; 2 CLI E2E over the real `aura graph build` /
`aura run` subprocess — one pinning the exact serialized
`"taps":[{"name":"fast_ma","from":{"node":0,"field":0}}]`, one proving an
op-authored tap round-trips through build -> run -> persisted trace). Full
workspace suite green; clippy clean.

The RED-before-GREEN step was verified by a temporary stash of the
production edits (the `Tap` / `with_taps` machinery pre-existed from #282,
so a fresh RED-only test could not be authored) — the new tests fail
without the edits, pass with them.

Docs (the authoring-guide `tap` row, the ledger op-list `tap` line, and a
worked tap-authoring example) are deliberately out of this commit's scope —
they land in #285, the docs follow-up in this same cycle.

closes #284
2026-07-18 16:22:18 +02:00
claude c8dc57e2aa feat(std): CumSum and When cells; roster complete at 33
Second half of the #281 clock-enable set. CumSum is the generic
run-length accumulator with O(1) Neumaier-compensated state — the
branching Neumaier variant, not Sma's plain Kahan, so it stays exact
when a large running total absorbs a tiny sample (the standalone
accumulator has no windowed-magnitude guarantee; module doc explains
why the helper is not reused). When is the clock-enable driver: forwards
value iff gate, else a quiet cycle (eval->None), so every stateful
reducer composes gated unmodified — SMA(When(x,g),N) is the gated SMA.

Rosters CumSum and When (both count pins 31->33) and ships two engine
E2E fixtures: When forwarding/quiet-downstream through the real
GraphBuilder->compile->run seam, and a quiet When leg suppressing a
whole Resample Barrier(0) group (the deliberate stall semantics).

refs #281
2026-07-17 20:39:56 +02:00
claude 40e962db89 feat(std): Sign and Select cells — the Bool→F64 bridge pair
First half of the #281 clock-enable set: Sign promotes the private
sign0() semantics to vocabulary (strict comparisons; NaN and -0.0
fall to 0.0), Select is the stateless 2:1 mux that consumes Bool
back into the F64 plane. Both zero-arg, Firing::Any, rostered
(count pins 29→31); an engine E2E drives both through the real
GraphBuilder→compile→run seam.

refs #281
2026-07-17 19:49:13 +02:00
claude ca4a89864c feat(std,engine): SessionFrankfurt — the Session node enters the closed roster
A Frankfurt-baked zero-arg Session preset (open 09:00 Europe/Berlin as
literals) joins the std vocabulary as SessionFrankfurt, declaring
period_minutes: I64 (default 15) as its one scalar knob — the
single-knob roster pattern the cost nodes established. A data-only
blueprint can now reach a session/time-of-day stream (bars_since_open),
unblocking time-of-day conditioners for the confirm/refute research
model. Roster count pins bump 28 -> 29 in both conscious-act sites.

Fork decision on the issue: the preset variant over a param-vocabulary
extension — a timezone param would need a string kind, breaching the
four-scalar discipline; the preset keeps the vocabulary closed and is
the additive extension the roster-exclusion comment anticipates. The
4-arg Session::builder stays untouched; in_session/session_open_ts
stay deferred (#154).

closes #261
2026-07-14 00:02:50 +02:00
claude 1ccce9ad32 fix(test): anchor all test sandboxes on reclaimable pid-free names
Every hand-rolled test-sandbox helper keyed its directory on
std::process::id() under env::temp_dir(), so the pre-create wipe never
matched a prior run's path: one directory stranded per helper site per
test-binary invocation, >50k dirs / 15.5 GiB on the 16 GiB tmpfs by
2026-07-13.

The remedy extends commit 84e1075's knob-lab pattern to every site:
fixed, tag-keyed, PID-FREE names under the build-tree tmp anchor, each
site keeping its pre-create remove_dir_all — which now genuinely
reclaims the previous run. Integration/bench targets use
env!("CARGO_TARGET_TMPDIR"); src-internal unit-test helpers (cargo sets
that var only for integration/bench targets) derive the same anchor
from env!("CARGO_MANIFEST_DIR") + ../../target/tmp. Residue is bounded
to one directory per site, on disk instead of the tmpfs, per checkout.

One deliberate exception: project_new.rs's tmp() stays under
env::temp_dir() because two of its tests (new_outside_a_work_tree_*)
need a base genuinely detached from any git work tree, and target/
lives inside the checkout's work tree. Its name carries a per-checkout
hash discriminator instead, so concurrently running checkouts (which
share /tmp) cannot race on the fixed name while each run still
reclaims its predecessor.

tempfile-RAII was considered and rejected (issue #258 decision log):
it cannot cover the process-lifetime OnceLock scaffolds (statics never
drop), adds a dependency, and still strands kill-leftovers on the
tmpfs.

Verified: full workspace suite green (incl. the new RED regression
test temp_cwd_sandbox_does_not_leak_under_env_temp_dir), clippy clean,
sandboxes observed under target/tmp with stable names across runs.
Stale pre-fix PID-keyed dirs in /tmp are not swept by this change and
age out only by manual cleanup.

closes #258
2026-07-13 20:15:31 +02:00
claude 6d3da424f4 test(cli): relocate the _open example twins to tests/fixtures (refs #248)
The four _open blueprints leave the user-facing gallery
(crates/aura-cli/examples/) and become test fixtures; the closed twins
are now the only example corpus (bound params are overridable defaults
since #246, so the closed files serve both run and sweep).

Compile-time embeds follow the move: the emit_* generators and the
shipped_*_serialize byte-pins target tests/fixtures/ (the open fixtures
stay builder-generated and byte-pinned — regenerating after the move
left the tree clean), load_open_r_sma and the axis-probe read the
fixture, and the walk-forward refit test plus the `aura graph` default
sample switch to the closed r_sma.json. graph_construct's open-fixture
tests swap example() -> fixture(), their names dropping the now-false
"shipped example" claim; a new e2e test pins the relocation property
(all four fixtures load and introspect, the gallery holds exactly the
four closed examples).

Intermediate state: cli_run.rs / research_docs.rs / project_load.rs
still reference the old example paths and fail at runtime until the
follow-up migration commit (MIGRATE sites move to the closed twins,
NEEDS-OPEN sites to the fixtures).

Verification: cargo build --workspace; cargo test -p aura-cli --bin
aura; cargo test -p aura-cli --test graph_construct; the three ignored
emit generators re-run byte-stable.
2026-07-13 11:42:03 +02:00
claude 72bfdf79d1 test(cli): RED — graph build lacks the tier hint for namespaced ids (#244)
RED pin: graph_build_hints_attach_for_namespaced_ids_in_a_data_only_project
(graph_construct.rs e2e) — the op-script path reports OpError::
UnknownNodeType without consulting the tier-aware hint the blueprint
load path carries.

refs #244
2026-07-12 19:55:25 +02:00
Brummel 62f6592ef6 feat(cli): surface the composite doc in the graph viewer (#125)
The viewer threads the model's doc through normalizeModel into both
composite view states — appended to INFO.B (collapsed box) and INFO.C
(expanded cluster frame) — and shows the root composite's doc as a muted
header line (#rootdoc span in GRAPH_HEAD, empty when absent; the DOM
population sits in the browser-only block, the same boundary as the
breadcrumb). viewer_tooltip.mjs pins the doc'd cases beside the
byte-exact un-doc'd pins; sample-model.json gains the additive doc key;
e2e drives aura graph over a nested-doc blueprint into the emitted page.

closes #125
2026-07-11 20:18:32 +02:00
Brummel 953d04a774 feat(engine): composite doc field — authoring, serde, identity, model emit (refs #125)
Composite gains doc: Option<String> (the prose twin of name, a C23 debug
symbol): with_doc/doc() on Composite, a doc knob on GraphBuilder, a
Tier-1 additive-optional CompositeData field (no format-version bump;
absent-field documents keep their exact bytes), identity-stripped like
the name. The graph model emits an optional trailing doc fragment in
both scopes; json_str is hardened for free text (\n/\t/\r named,
control chars as \u00XX — the doc is the first multi-line value through
it). e2e: a hand-authored doc'd blueprint renders with the doc embedded;
the doc moves the content id but never the identity id.

refs #125
2026-07-11 19:37:45 +02:00
Brummel 99237e1d0a feat(std): fill the by-chance vocabulary gaps — Const, Div, Abs, Max, Min
Five new rostered node types (count-pin 23 -> 28, both the in-crate
shape test and the cross-boundary CLI vocabulary e2e): Const is unary
with an f64 'value' param — the clock input drives it, its value is
ignored, since a zero-input node never evaluates in the total-push
engine — mirroring EqConst's constant-as-param pattern; Div is binary
IEEE-754 (x/0 -> signed inf, 0/0 -> NaN, unit-tested, no error
channel); Abs unary mirroring Sqrt; Max/Min binary pairwise, distinct
from the windowed RollingMax/RollingMin.

Acceptance proof: the committed executable spec composes an RSI-class
gain/loss-split-and-ratio signal purely from blueprint data through
std_vocabulary and runs it to hand-computed RS values — the r_meanrev
constant-folding workaround is no longer forced.

Verified: headline test green, aura-std 163/0, full workspace suite
green (independent mini-verify), clippy -D warnings clean.

closes #236
2026-07-10 19:59:10 +02:00
Brummel 0d9fdf8f05 test(cli): RED — the graph render positional rejects op-scripts its siblings accept
aura graph <op-script.json> exits 2 mislabeling the valid JSON array as
'blueprint document is not valid JSON'; register/--params already
shape-discriminate via composite_from_any. Pins render parity: op-script
and its built envelope render byte-identically at exit 0.

refs #226
2026-07-10 18:58:45 +02:00
Brummel fc9cf23b87 feat(cli,docs): ship the ganged open examples + document the gang construct (#61 tasks 7-8)
The two open examples now expose their author-intended single knobs — the
closed builders always bound these pairs to one value by hand; the open forms
falsely offered them as independent axes:
- r_breakout_open: channel_hi.length + channel_lo.length -> channel_length
  (the Donchian channel is structurally ONE parameter)
- r_meanrev_open: mean_window.length + var_window.length -> window; the band
  factor stays an independent axis
Regenerated via the emitters (never hand-edited); the closed examples are
byte-unchanged. The carve builders gang in the open branch only.

Test migration: the two axis-namespace pins, five --real e2e invocations
(single gang axis, the 10,20 diagonal for the campaign pair — the mismatched
20,40/10,20 grid was exactly the configuration space the gang retires), and
the param_stability row counts (4 -> 3). The r-sma walkforward golden anchor
is untouched (it never swept a ganged pair).

Docs: authoring-guide gains the seventh op + the third param state
(open/bound/ganged) + the gang wrap note; README op list + Axis concept;
glossary gang entry; ledger C24 records the gang verb and the pre-ship
Tier-2 dormancy (no format-version bump while no out-of-repo reader exists).

Verified: full workspace suite 1104/0 (--real e2e included, local data
present), clippy -D warnings clean, cargo doc clean; live acceptance:
introspect --params prints channel_length:I64 alone / window:I64 +
band.factor:F64.

closes #61
2026-07-10 14:07:22 +02:00
Brummel bc88e18247 feat(engine,cli): GraphBuilder gang cadence + render carrier (#61 tasks 5-6)
Builder: NodeHandle::param -> ParamRef, GraphBuilder::gang, build() resolves
the gang table (collect-then-reject, BuildError::{UnknownParam,AmbiguousParam,
BadGang}) and terminates in Composite::with_gangs — the same predicate the
op-script and serde boundaries use. C24 lockstep extended: an op-script gang
replay serializes byte-identically to the GraphBuilder twin (FlatGraph edges +
sources + canonical JSON).

Render: model_to_json emits a per-scope gangs fragment (name, kind, [node,pos,
"param"] members) for ganged scopes only — the gang-free model stays
byte-identical (model_golden untouched). graph-viewer.js annotates a ganged
open param with its public knob name; guarded by a viewer_gang_param.{mjs,rs}
pair RED-first per the established viewer_bound_param convention (a welcome
extra beyond the plan's file list).

Ratified deviation: the model's kind string uses the serde form ("I64") — the
plan's implementation snippet (kind_str -> "i64") contradicted its own test;
resolved toward the test + the blueprint_serde form. Two quality nits held as
principled plan-holds (inline resolution block; the unreachable unwrap_or
placeholder check_gangs refuses first).

Verified: aura-engine lib 264/0 (model_golden, lockstep, builder, carrier all
green), viewer_gang_param 1/0, workspace clippy -D warnings clean. Slice
resumed across a transient API failure via workflow resume (cached prefix).

refs #61
2026-07-10 13:03:10 +02:00
Brummel 521cfc8b5b feat(engine,cli): gang serde + identity + the gang construction op (#61 tasks 3-4)
Serde: CompositeData carries an additive-optional gangs section (omitted when
empty — every existing v1 document round-trips byte-identically, NO
format-version bump per the #61 decision; BLUEPRINT_FORMAT_VERSION doc now
records the pre-ship Tier-2 dormancy). project() emits gangs canonically
(members by (node,pos), gangs by first member) so declaration order never
reaches the content id; reconstruct() re-mints through Composite::with_gangs
and a corrupted section refuses as the new typed LoadError::Gang.
strip_debug_symbols blanks gang + member names, keeps (node,pos,kind) — the
identity id stays gang-name-blind and gang-structure-sensitive.

Construction: Op::Gang { as_name, into } with the feed-style two-phase eager
arm (member resolution against the post-bind schemas, kind uniformity,
single-gang membership; OpError::{GangKindMismatch,AlreadyGanged,GangArity});
finish() routes the assembled composite through with_gangs, and gang-name
collisions surface via the existing injectivity gate on the projected space.
CLI: OpDoc::Gang ({"op":"gang","as":…,"into":[…]}), kind_label/From/
format_op_error arms, and format_load_error phrases gang faults as prose
(gang_fault_prose) instead of leaking Debug — a ratified improvement over the
plan bytes.

Held quality nit (principled plan-hold): gang()'s inline collect-then-reject
param resolution is the module's established convention (bind/try_bind); a
shared cross-crate helper is out of this task's scope.

Verified: workspace build clean, aura-engine lib 258/0, aura-cli
graph_construct 48/0 (incl. the new gang-op e2e), clippy -D warnings clean.

refs #61
2026-07-10 11:57:16 +02:00
Brummel 257ab0b9f2 feat(std,cli): add the Scale node, retire the r-meanrev demo to data (#159 cut 3)
Cut 3 of the hard-wired demo retirement. Unlike r-sma/r-breakout, r-meanrev could
not round-trip as data: its band computes band_k*sigma (a constant times a stream)
and the closed 22-node std_vocabulary had no way to express it (EqConst is i64->bool,
Bias is clamp(signal/scale,±1), Mul is binary, LinComb is excluded as a
construction-arg node; no Div/Const/constant-emitter). Standard operators are missing
by chance, not by design, so this adds the one r-meanrev needs.

Scale node (aura-std): out = input * factor — one f64 input, one f64 `factor` param,
stateless, warm-up-filtered (the EqConst/Bias shape). A zero-input `Const` emitter was
rejected: the per-cycle eval loop gates every node through an input-firing test
(harness.rs `fires()`), so a node with no input never fires without an engine-core
change; `Scale` fits the existing model and, by IEEE-754 commutativity, reproduces the
retiring LinComb's band byte-for-byte. Rostered (`"Scale" => Scale`); the two
vocabulary count-pins bump 22 -> 23.

r-meanrev migration: r_meanrev_signal(window, band_k) is carved out of the fused
r_meanrev_graph as a #[cfg(test)] price->bias Composite whose band is `Scale` in place
of `LinComb(1)`; production loads the shipped JSON (examples/r_meanrev{,_open}.json),
never a builder. Before the fused builder was deleted, an equivalence test proved the
carved Scale-band signal reproduces its grade byte-for-byte on the synthetic stream
(window=3, band_k=2). Durable survivors: the byte-identity of the examples to the
carve, the loaded-grades-identically-to-carve proof, the closed/open introspect
anchors (mean_window.length, var_window.length, band.factor — band_k is now a
first-class sweepable param), and — re-pointed onto the carve rather than dropped —
the fades-short-above/long-below behavioural test, which becomes the durable
carve-correctness gate the deleted equivalence test used to be.

Deletions + the dead-code cascade the retirement opened: r_meanrev_graph,
r_meanrev_sweep_family, Strategy::RMeanRev (+ all arms). r-meanrev was the last reader
of run_sweep's grid, so the whole vestigial built-in-sweep grid apparatus retires with
it: run_sweep's `grid` param, the sweep call-site grid build, and SweepCmd's
--fast/--slow/--stop-length/--stop-k/--window/--band-k (sma/momentum sweeps build their
own blueprints and ignored these; the fast/slow/stop-* had been vestigial since r-sma's
cut 1b). RGrid the struct SURVIVES — the dissolved `generalize` verb resolves its
candidate from its fast/slow/stop_length/stop_k via generalize_args_from — so only its
window/band_k fields go; its stale doc-comment (r-sma-sweep / persist_traces_r / CLI
flags, all now gone) is rewritten to its true generalize-only role. Also retired as
transitively dead: persist_traces_r and the metrics_object test helper (their only
callers were the deleted families/tests); Add/Gt/Latch/Mul/Sqrt imports are now
#[cfg(test)] (their last production caller was r_meanrev_graph).

Test surface: the two built-in --strategy r-meanrev sweep tests drop; a new negative
pins that --strategy r-meanrev on both sweep and walkforward now falls into the generic
usage error; the grid-flag stray-positional negative is re-pointed to a surviving flag.

Verification (own, not the workflow's report): full `cargo test --workspace` green
(0 failed across 62 result lines); `cargo clippy --workspace --all-targets -- -D
warnings` clean, no dead-code residue; the r-sma + r-breakout anchors and the
dissolved-verb real goldens (generalize still reads RGrid) stay green. Bundled as one
commit (the Scale node's count-pin and the r-meanrev anchors share graph_construct.rs,
so a clean two-commit split was not path-separable); the implement-loop ran all three
plan tasks in one pass and the tree was verified by hand.

refs #159
2026-07-07 22:02:50 +02:00
Brummel 5856cadadd refactor(cli): retire the r-breakout demo — topology now lives only as data (#159 cut 2)
Cut 2 of the hard-wired demo retirement, applying the r-sma template one level
down. The r-breakout signal leg was fused into r_breakout_graph (which also built
the whole pip/R harness inline), so this cut first carves the signal leg out as a
pure price->bias Composite, ships it as a proven blueprint example, then deletes
the fused builder and the built-in --strategy r-breakout surface. The r-breakout
topology now exists only as data (crates/aura-cli/examples/r_breakout{,_open}.json).

Carved: r_breakout_signal(channel) -> Composite, a verbatim lift of the fused
builder's signal leg (Delay/RollingMax/RollingMin/Gt/Latch/Sub) exposing the raw
exposure as "bias"; the generic wrap_r/run_signal_r path (unchanged) supplies the
pip/R harness. It is #[cfg(test)] — its only role is regenerating the examples and
pinning them to a faithful serialisation; production loads the shipped JSON, never
this builder (so no dead-code residue, no production topology constructor — #159's
invariant).

Proof: before the fused builder was deleted, an equivalence test proved the carved
signal, wrapped and run, reproduces the retiring r_breakout_graph grade byte-for-byte
on the synthetic stream at channel=3 with the shared default R-stop (metric
computation is identical on both paths). That gate is deleted with the builder it
references; the durable anchors that survive are the byte-identity of the shipped
examples to the carve, the loaded-example-grades-identically-to-the-carve proof, and
the closed-ness / open-axis introspect anchors (channel_hi.length, channel_lo.length
per-node — ganging is the separate #61).

Deleted: r_breakout_graph, r_breakout_sweep_family (aura sweep --strategy r-breakout),
Strategy::RBreakout + all four match arms, the r-breakout token from the --strategy
usage strings and docs, and the now-dead --channel flag + RGrid.channel field (its
sole reader was r_breakout_sweep_family). The data successors: aura run
examples/r_breakout.json, and aura sweep examples/r_breakout_open.json --axis
channel_hi.length --axis channel_lo.length.

Test surface: the three built-in-surface sweep tests drop; the walkforward
valid-but-unsupported-token test is re-pointed to momentum (the property is generic
and stays live until Cut 4); and a new negative pins that --strategy r-breakout on
both sweep and walkforward now falls into the generic usage error, not a
special-cased message — catching a half-finished retirement that left the token
parseable.

Verification (own, not the workflow's report): full `cargo test --workspace` green
(0 failed across every binary); `cargo clippy --workspace --all-targets -- -D warnings`
clean, no dead-code residue; the r-sma introspect + grade anchors and the
dissolved-verb real goldens stay green. The implement-loop ran both plan tasks in one
pass (task_range was not honoured); the tree was verified by hand — grep-confirmed the
retired surface is gone and the carve is #[cfg(test)]-scoped.

refs #159
2026-07-07 19:25:13 +02:00
Brummel f7c809e5ce refactor(cli): retire the r-sma demo builder — topology now lives only as data (#159 cut 1b)
Cut 1b of the hard-wired demo retirement — the removal half. With the proven
example shipped in cut 1, delete the r-sma builder and its whole transitive
closure so no production function constructs the r-sma topology any more; it
exists only as data (crates/aura-cli/examples/r_sma{,_open}.json).

Deleted: sma_signal (the builder), r_sma_graph, run_r_sma (aura run --harness
r-sma), r_sma_sweep_family (aura sweep --strategy r-sma), the synthetic inline
r-sma walkforward/mc machinery (r_sma_space/r_sma_sweep_over/run_oos_r/
run_mc_r_bootstrap/mc_r_bootstrap_report + the walkforward_family RSma arm), both
fixture emitters, the round-trip + cut-1 byte-fidelity tests, the R_SMA_FAST/
SLOW/BIAS_SCALE consts, and the Strategy::RSma / HarnessKind::RSma / McArgs::RealR
variants. The three dissolved-verb synth sites (generalize/walkforward/mc
--strategy r-sma --real) now embed the shipped open example via include_str!
instead of building it — byte-safe, guarded by the unchanged dissolved-verb
real-archive goldens. The two now-redundant fixtures move to the examples
(deleted, ~40 readers repointed). The --cost-* flags on `aura run` retire with
the harness (their sole consumer was run_r_sma; cost-model coverage survives
engine/std/composites-level).

The retired demo surfaces (--harness r-sma, sweep/walkforward/mc --strategy r-sma
inline) have working data successors: aura run examples/r_sma.json, aura sweep
examples/r_sma_open.json --axis …, and the --strategy r-sma --real campaign path.

Deviation from the plan, noted: StopArm collapsed to StopRule in wrap_r's
signature (its only other variant, Open, died with r_sma_graph) — cleaner than
the planned vestigial single-variant enum; behaviour-preserving, touching
campaign_run.rs and the r-breakout/r-meanrev call sites.

Follow-up (not this cut): wrap_r's cost-node-building branch is now unreachable
(nothing passes Some(CostConfig) after run_r_sma's deletion) yet clippy-clean —
a latent cleanup for when the inline cost surface fully retires.

The stage1-r param-suffix remapping (r_sma_friendly_name + the FAST/SLOW/
STOP_LENGTH/STOP_K_SUFFIX consts) went dead and was removed, closing #167 by
deletion.

Verification (own, not the workflow's report): full `cargo test --workspace`
green; `cargo clippy --workspace --all-targets -- -D warnings` clean, no
dead-code residue; the cut-1 grade anchor (shipped_r_sma_example_reproduces_the_
builtin_grade) and the dissolved-verb real goldens (generalize/walkforward/mc
_dissolves_through_the_campaign_path) stay byte-identical. A first workflow
attempt was discarded after a subagent's `git checkout -- main.rs` recovery
silently reverted earlier tasks' edits (filed on the plugin tracker as
Brummel/Skills#23); this landing is the hardened re-run, verified by hand.

closes #167
refs #159
2026-07-07 17:41:59 +02:00
Brummel d7fa4b4743 feat(cli): ship r-sma demo as a proven blueprint-data example (#159 cut 1)
Cut 1 of the hard-wired demo retirement — the copy+prove phase. The r-sma demo
strategy now ships as runnable blueprint data under crates/aura-cli/examples/
(r_sma.json closed 2/4, r_sma_open.json open), emitted from the current builder
so it is byte-for-byte the builder's serialization.

Two load-bearing proofs, captured while the builder is still alive:
- byte-fidelity: the shipped examples equal blueprint_to_json(&sma_signal(..)) —
  the "the copy is identical to the original" evidence the retirement method
  requires before any original is removed;
- grade anchor: `aura run examples/r_sma.json` reproduces the built-in
  `--harness r-sma` metrics byte-for-byte (expectancy_r/sqn/total_pips/n_trades).

Plus two property tests on the shipped copies (beyond the plan, kept as on-topic
proof strengthening): the closed example introspects to zero unbound params
(genuinely closed), and the open example lists exactly its two SMA-length axes
from the public gallery location.

Purely additive: the builder (sma_signal) and every inline r-sma surface are
retained. The recon showed deleting the builder has a large transitive closure
(the synthetic inline surface, the Strategy::RSma enum, ~40 integration tests);
that removal is cut 1b, a separate cycle, made safe by the byte-fidelity proof
frozen here.

Full workspace suite green; clippy -D warnings clean.

refs #159
2026-07-07 12:40:14 +02:00
Brummel 63d2657850 feat(cli): one bare content-id display form; CLI targets tolerate the content: prefix (0106 F3 / 0107 F6)
The copyable token is now the valid ref form everywhere: all three
register lines print 'registered <family> <id> (<path>)' and both doc
introspect --content-id modes print the bare 64-hex id (converging on
graph introspect's existing form). CLI FILE-or-id targets (campaign
run, graph introspect --params) strip a leading 'content:' before
resolving — a CLI arg is ephemeral input, leniency costs nothing. Doc
ref FIELDS stay bare-only (two accepted spellings would make
semantically identical docs hash apart — canonical-form byte
stability outranks input leniency); instead the referential not-found
prose appends 'refs use the bare 64-hex id — drop the content: prefix'
when the offending ref carries it.

RED-first (tdd-author handoff): eight same-seam assertions across the
three surfaces observed failing on the prefixed forms, then the five
display sites, two target ladders, and the conditional hint landed.

Gates: workspace 993/0, clippy -D warnings clean.

closes #194
2026-07-03 23:01:20 +02:00
Brummel c3d62f2ce3 feat(cli): graph register / --params accept the op-script form (fieldtest 0107 F5)
Both verbs now route through the same composite_from_any shape
discrimination file-mode --content-id shipped with (op-script array ->
one-build tail; envelope object -> loader), so the three on-ramp verbs
agree on accepted shapes and an op-script registers under the same
shape-invariant content id as its built envelope — no more raw serde
refusal ('invalid type: map, expected u32') hiding the graph build
bridge.

RED-first (tdd-author handoff): two same-seam behavioural pins
(register stores the envelope id for an op-script; --params matches
the envelope's axis lines) observed failing on the serde refusal, then
the two call sites rerouted.

Gates: workspace 990/0, clippy -D warnings clean.

closes #202
2026-07-03 22:49:12 +02:00
Brummel 367f8678f1 feat(cli): #196 blueprint on-ramp — graph register, introspect --params, blueprint-file --content-id (0107 task 10)
Closes the F5 authoring gap: a campaign's strategy ref + axes are now
authorable from the public surface without running a sweep first.
'aura graph register <blueprint.json>' puts the canonical form into the
content-addressed store (id == topology hash) and prints the stored
path via the pub blueprint_path accessor; 'aura graph introspect
--params <FILE|64-hex-ID>' lists the RAW composite param_space — the
exact namespace campaign axes are validated against (the wrapped
--list-axes namespace stays the sweep-verb view); --content-id gains an
optional FILE that shape-discriminates a blueprint envelope from an
op-list document (stdin behaviour byte-identical without FILE).
is_content_id promoted pub(crate) and shared with graph_construct so
the two FILE-or-id surfaces cannot drift on the id shape.

RED evidence was produced retroactively by the loop (stash-probe: the
5 new tests fail for the expected reasons without the src changes) —
ordering deviation noted, outcome unaffected.

Gates: graph_ 46/0, workspace 987/0, clippy -D warnings clean.

closes #196
refs #198
2026-07-03 22:00:14 +02:00
Brummel 57f401f2ab refactor(std): std_vocabulary_roster! macro — one source for the vocabulary roster
The three hand-kept copies of the 22-key zero-arg roster in
aura-std/vocabulary.rs (std_vocabulary match arms, std_vocabulary_types
list, the test's inline array) collapse into one private declarative
macro invoked once with the "TypeId" => Type pairs — the resolver and
the enumerable list now agree by construction (the #160 failure mode:
a node resolvable but silently absent from graph introspect
--vocabulary, or vice versa). Byte-preserving: same fn signatures, same
22 ids, same order; lib.rs re-export and every consumer untouched; the
vocabulary stays a closed compiled-in set (invariant 9 / C24 — a
compile-time expansion, no registry). Adding a zero-arg node is now one
roster line plus the conscious count-pin bumps.

Tests: the two unit tests reshaped (round-trip iterates the generated
list with PrimitiveBuilder::label() as the independent oracle; the shape
test keeps the count pin as deliberate friction). The E2E phase added
one cross-boundary pin beyond the plan — kept:
graph_introspect_vocabulary_lists_exactly_the_closed_roster_count checks
the same count through Env::type_ids() and the CLI print loop across the
real process boundary, which no roster-internal test can see.

Accepted residual (documented at the roster site, recorded on #160):
a new zero-arg node never rostered at all stays unguarded — no
enumeration of zero-arg builders exists — and fails safe in both
directions (clean UnknownNodeType on load; merely absent from
--vocabulary).

Verification: cargo build clean; cargo test --workspace 885 passed /
0 failed (884 baseline + 1 new e2e); clippy --all-targets -D warnings
clean; cargo doc --no-deps 0 warnings.

closes #160, refs #180
2026-07-02 21:56:07 +02:00
Brummel 45fb06dba3 feat(engine+cli): topology-identity hash — --identity-id beside --content-id
Engine (aura-engine/blueprint_serde): blueprint_to_json factored into
build_doc/serialize_doc (byte-preserving — canonical golden unchanged) and
blueprint_identity_json added: the canonical document with every
non-load-bearing debug symbol (C23) blanked (composite name, instance
names, bound-param names, role names, output re-export names) while
everything load-bearing survives (type ids, node order, edges, role
targets/order, output pairs/order, bound pos/kind/value — openness stays
identity-bearing). Re-exported from lib.rs. Six property tests: renamed
twins (equal identity, unequal canonical), open-vs-bound, bound-value,
edge-swap, nested-composite interior names, role/output renames — the
last two are additive beyond the plan and cover the recursion and
role/output arms the planned four did not.

CLI (aura-cli): graph introspect gains --identity-id, a sibling of
--content-id through the same shared content_id SHA-256 primitive;
composite_from_str factored out of build_from_str (fault strings
byte-identical). The exactly-one introspect dispatch is DELIBERATELY
relaxed: the two id flags form one group and may combine — one build,
both ids, one per line, content id first. In-crate cross-path twin test
(Rust sma_signal vs op-script twin: distinct content ids, one identity
id) beside the existing cross-surface pins; four e2e tests incl. the
previously uncovered count!=1 usage exit-2 path.

topology_hash, the blueprint store, reproduce, and every --content-id
byte stay untouched (spec acceptance 3; all existing pins green).

Verification: cargo build clean; cargo test --workspace 884 passed /
0 failed (873 baseline + 11 new); clippy -D warnings clean; doc build
0 warnings. The implement-loop's Task-4 spec-compliance block was a
plan-byte count mismatch only (plan under-counted pre-existing
blueprint_serde tests 6-vs-7 and did not anticipate the two
sibling-accepted extra tests); gates re-run by hand, all green.

closes #171, refs #180
2026-07-02 21:24:18 +02:00
Brummel 4de6d5cbad rename: retire the stage1-* family for the r-family (r-sma / r-breakout / r-meanrev)
The stage1 ordinal has been dead vocabulary since the C10 reframe dropped
the two-stage research model: a 1 structurally implies a 2 that no longer
exists. The family is renamed by its live discriminator - the R yardstick -
with members named by their signal, uniform with their signal-named
siblings (sma, macd, momentum):

- selectors: stage1-r -> r-sma, stage1-breakout -> r-breakout,
  stage1-meanrev -> r-meanrev (old tokens are usage errors, exit 2 - no
  silent alias)
- identifiers: Strategy::RSma/RBreakout/RMeanRev, HarnessKind::RSma,
  r_sma_*/r_breakout_*/r_meanrev_*, wrap_r, run_signal_r, RGrid, R_SMA_*
- persisted identity: the sma_signal composite (param prefix
  sma_signal.fast.length / .slow.length; fixtures regenerated; content-ids
  shift - no test pins a literal hash, the registry parses no record names)
- e2e test files git-mv'd to r_sma_e2e / r_breakout_e2e / r_meanrev_e2e
- dead Stage-1/Stage-2 prose reworded to post-C10 vocabulary across
  rustdoc, Cargo.tomls, ledger live lines, and the glossary (historical
  entries stay; fieldtests corpus untouched)
- CLAUDE.md invariant 7 rewritten to record the ratified C10 reframe
  faithfully (the token-swap alone would have laundered the retired
  gated-currency/realistic-broker design into unmarked live prose); the
  unbacked account-mode clause dropped

Verification: cargo build/test --workspace green (51 targets), clippy
-D warnings clean, doc build clean, acceptance grep gate leaves exactly
the one resampling-stage false positive (harness.rs), smoke: --harness
r-sma runs, --harness stage1-r exits 2 with the new usage line.

Decision log: forks and rationale recorded on the issue (reconciliation
+ implementation-phase comments).

closes #174
2026-07-02 12:03:09 +02:00
Brummel fa42bf3878 feat(0099): CLI exit-code split (iteration 2) — usage=2, runtime=1
Apply the clean exit-code partition (#175 deviation #8) on top of the clap
migration: runtime failures now exit 1, usage errors stay exit 2, with no
same-class inconsistency.

Partition (attribution principle): exit 2 = a command-line fault (a bad flag
value, an inapplicable combination, or the content of an argv-named file);
exit 1 = the command was well-formed but the environment / recorded state it
needs is missing, or piped stdin data is bad. The 6 boundary cases the spec's
"parse-time vs run-time" rule under-specified are resolved on #175
(malformed/open argv blueprint -> usage 2; stdin op-script content -> runtime 1;
applicability refusals -> usage 2; unknown --axis / --metric -> usage 2).

Flipped 2->1 (runtime): no local data, no recorded geometry, no recorded
run/family, "run has no tap named", trace-name collision, family/trace
persist-write failures (normalizing the lone generalize inconsistency -- all
persist -> 1 together), missing/corrupt content-addressed store state (the
reproduce path), chart-read failures, and graph stdin op-script content +
stdin-read I/O. reproduce-diverged stays exit 1. Usage sites (clap parse,
argv-applicability guards, the dual-grammar blueprint-file read/parse) stay
exit 2.

Tests: ~8 domain-refusal pins flipped Some(2)->Some(1) (message substrings
unchanged), the four runtime tests renamed _exit_2 -> _exit_1, and a partition
property test added. Orchestrator fix after the loop: 3 sibling no-data
skip-guards (run_real_with_trace + the two generalize cross-instrument tests)
that the plan's Task-1 list missed -- left at Some(2), they would fall through
to assert Some(0) and FAIL on a data-less machine; flipped to Some(1) for
consistency with the flipped code.

Deferred (cosmetic, filed forward): full "Usage:" / "usage:" / bare
error-message casing normalization -- no functional impact, high pin-churn.

Verified green (orchestrator, this session): cargo build --workspace, cargo
clippy --workspace --all-targets -- -D warnings, and cargo test --workspace all
clean (0 failed across every test binary).

This completes the #175 clap-adoption cycle (iteration 1 = clap migration at
366170a; iteration 2 = this exit-code split).

refs #175
2026-07-01 20:05:42 +02:00
Brummel 717a0b70af feat(0094b): content-id surface + Tier-1 stability — aura graph introspect --content-id (iter 2)
Completes #158 acceptance (acc 1 + acc 3) on top of iteration 1's reproduction:

- acc 1: `aura graph introspect --content-id` reads an op-script and prints the
  content id of its canonical blueprint — the SHA256 (hex) of the same
  blueprint_to_json bytes `graph build` emits. Extracted the single `content_id`
  primitive shared by this surface AND topology_hash, so the two command paths
  (this surface / hashing a `graph build` output) agree by construction. Verified
  live: both e2be81b2… for the SMA-cross op-script. Deterministic + distinguishes
  topologies; a malformed op-list fails cleanly (exit 2, no partial hash).
- acc 3: a Tier-1 optional field the blueprint does not use is tolerated by the
  loader (#156) and absent from the canonical omit-defaults form, so the content
  id is unchanged (content_id_is_stable_across_a_tolerated_tier1_field); plus
  content-id stability across the serialize -> reload -> re-serialize store
  round-trip (#164), the property reproduction rests on.

Finding (documented, filed forward): an op-script and the Rust `stage1_signal`
builder produce DIFFERENT canonical forms — the composite debug-name ("graph" vs
"stage1_signal"), a named vs unnamed Sub, and an unbound vs bound Bias.scale — so
they are different topologies by the byte definition and get different content
ids. Content-addressing keys on the canonical form, which currently includes the
composite debug-name (a non-load-bearing symbol, invariant 11). Whether the
content id should exclude the debug-name (a topology-canonical form distinct from
the byte-canonical form, which would also change the shipped cycle-0092
topology_hash values) is a forward design question, not this cycle.

Verified: full workspace suite green (51 suites); clippy -D warnings clean.

refs #158
2026-07-01 03:05:37 +02:00
Brummel b8b83cf1c9 feat(0091): byte-canonical blueprint emit; record the equality-surface decision
#164 — tighten the C24 "canonical, versioned" blueprint contract on two corners
the milestone fieldtest surfaced. Both forks were derivable; resolved with
rationale (recorded on #164).

Fork 1 — canonical artifact carries no trailing newline. `blueprint_to_json`
returns the JSON value with no trailing newline (647 bytes for the SMA-cross
fixture); `aura graph build` had framed it with a `println!` (648). The library
serializer is the single canonical source — the form content-addressed topology
identity (#158) hashes — so the CLI is a transport that must not mutate the
bytes: `build_cmd` now `print!`s the canonical bytes verbatim, making the CLI and
library emit paths byte-identical. RED-first: the new E2E
`graph_build_emit_is_byte_canonical_no_trailing_newline` pinned the trailing
newline (FAIL), green after the one-line change. The two cycle-0088 `.out.json`
goldens are re-recorded at 647 bytes. The loader stays lenient (a trailing
newline on input still parses, Tier-1 robustness; the milestone fieldtest's
`mt_4` tolerance check is unaffected).

Fork 2 — the canonical JSON is the blueprint equality/identity surface; no
second in-memory PartialEq/Debug is added. `Composite`/`BlueprintNode` cannot
derive them: `PrimitiveBuilder` (aura-core node.rs) holds a
`build: Box<dyn Fn(&[Cell]) -> Box<dyn Node>>` closure, neither comparable nor
printable. Equality could only be defined over the serialized data, of which
`blueprint_to_json` is already the single source; a separate in-memory equality
notion would be a redundant second source of truth and a drift hazard against
the form #158 hashes. Recorded the decision (discharges acceptance box 2's "or
JSON-string is the intended equality surface" arm) in the C24 ledger; no code
added for it.

Verified: cargo test --workspace green (51 suites, incl. the new RED->green E2E);
cargo clippy --workspace --all-targets -D warnings clean.

closes #164
2026-06-30 15:57:15 +02:00
Brummel 3c4b667955 feat: construction add names a node via name, mirroring the builder (#157)
The op-script authoring form is meant to be the Rust builder lifted into data —
every op is a 1:1 image of a GraphBuilder method. The one un-builder-ish key was
`add`'s `"as"`: the builder names a node with `.named("fast")`, so the data echo
is `"name"`, not the SQL-ish aliasing word `as`. Renamed the `add` wire key
`as -> name`.

`expose` keeps `as` — there it is a genuine alias (`expose bias.bias as bias`,
mirroring the builder's positional name arg). `source`/`input` keep their terse
verbs: those are plain shortenings of `source_role`/`input_role`, not a foreign
concept the way `as`-for-naming was.

CLI-side only: a one-attribute serde rename on the `OpDoc` Add DTO
(`#[serde(rename = "name")]`), since the engine `Op` is serde-free (the engine
field stays `as_name`, untouched). Updated the op-list test documents to the
`name` key (add ops only; expose's `as` left intact) and added an explicit pin
(`add_names_node_via_name_key`). The cycle-0088 fieldtest corpus
(fieldtests/cycle-0088-construction-op-script/*.json) is left on the old `as`
form as historical cycle-0088 evidence (it is not run by the suite).

Verified: full workspace suite + clippy green; `aura graph build` on a `name`-keyed
doc names the node and emits it in the #155 blueprint.
2026-06-30 12:22:45 +02:00
Brummel f1ab81d1b8 feat(0089): construction diagnostics read by-identifier (closes #162)
The cycle-0088 fieldtest found three presentation defects on the construction
op-script surface, all where the surface dropped out of its by-identifier register.
Presentation only — every fault still fires identically (same exit code, same
op/finalize attribution); only the rendered cause changes.

- Item 1 (finalize by-identifier). The holistic finalize fault leaked the raw
  index Debug form (`UnconnectedPort { node: 2, slot: 1 }`). `GraphSession::finish()`
  now translates the three reachable index-carrying CompileErrors into new
  by-identifier OpError variants (UnconnectedPort{node,slot} / RoleKindMismatch{role}
  / UnboundRootRole{role}) using the session's `ids`/`schemas` and the composite's
  `input_roles()` — the holistic gate calls (validate_wiring,
  check_param_namespace_injective, check_root_roles_bound) stay byte-for-byte
  unchanged (C24: no second validator; only the `.map_err` closures changed).
  `aura graph build` now prints `finalize: slot sub.rhs is unconnected`.
- Item 2 (bind mismatch prose). `format_op_error`'s BadParam arm matches the
  BindOpError variants instead of `{:?}`: `op 1 (add): param fast.length expects I64
  but got F64`, matching the connect mismatch register.
- Item 3 (discoverable bind form). `introspect --node` shows the typed-Scalar bind
  form: `param length:I64  (bind {"I64": <v>})`.

Item 1 spans both crates atomically (adding OpError variants forces the exhaustive
format_op_error match; the finish() translation flips behavioural test pins in both
crates). Five test touches: the two in-file unit tests, the two E2E twins plan-recon
found that the spec's testing strategy under-enumerated
(construction_e2e.rs, tests/graph_construct.rs), and a new unbound-root-role test;
the implementer added RoleKindMismatch / Ambiguous/UnknownParam coverage too. The
CLI finalize test was tightened (orchestrator) to pin `sub.rhs` and the absence of a
raw `node:` index.

Spec boss-signed on a grounding-check PASS (docs/specs+plans/0089). Verified: full
workspace suite + clippy green; the three messages reproduce exactly against the
built binary on the fieldtest fixtures.
2026-06-30 11:49:03 +02:00
Brummel 25e452aaf7 feat(0088): §C construction op-script CLI — aura graph build/introspect
Iteration-2 §C of the construction service (#157), the CLI shell over the
iteration-1 engine core (ea1ca32 + 27ac4dc): a declarative, replayable JSON
op-list document drives the engine's per-op-fallible construction surface.

- graph_construct.rs (new, aura-cli): an `OpDoc` serde DTO (`#[serde(tag="op",
  rename_all="lowercase")]` + field renames type/as) deserializes the document
  and maps 1:1 into the serde-free engine `Op` — the wire format is a CLI
  concern, the engine owns no second representation. Bind values use the typed
  Scalar form (`{"I64":2}`), kinds the capitalized #155 form (`"F64"`).
- `aura graph build`: read the op-list from stdin, replay through std_vocabulary,
  emit the #155 blueprint JSON on success; on the first failing op print
  `op N (kind): cause` to stderr (the op-kind recovered from the retained parsed
  list) / `finalize: cause` for a holistic fault, and exit non-zero. A CLI-side
  format_op_error phrases each OpError (the engine error types stay Display-free).
- `aura graph introspect`: `--vocabulary` (std_vocabulary_types), `--node <T>`
  (a type's ports/kinds + param paths off the pre-build schema), `--unwired`
  (a partial document's open slots via GraphSession::unwired) — all build-free.
- Two new argv arms over the flat match; the bare `["graph"]` HTML-render arm
  (sample_blueprint, #159's concern) is left intact.

#157 acceptance now demonstrated through the CLI (E2E in tests/graph_construct.rs,
8 tests): a document builds to the #155 blueprint that compiles identical to its
Rust-built twin (C1); an invalid op is rejected at the op, named; introspection
answers vocabulary / ports+kinds / unwired slots without a build. Full suite
green (51 suites); clippy --all-targets -D warnings clean.

The JSON op-list reuses #155's Scalar/ScalarKind serde shapes; full harnesses
(SimBroker/Recorder construction-arg builders) await #156. Wire-format forks
derived + recorded on #157.

refs #157
2026-06-29 21:30:46 +02:00