Commit Graph

912 Commits

Author SHA1 Message Date
claude a32dc38d18 feat(std): meaning lines for all 27 shipped node schemas
C29 compile/unit seam, task 3 of the self-description plan: every
aura-std NodeSchema literal threads its one-line doc. Four texts were
corrected against the actual eval/finalize semantics rather than taken
from the plan table verbatim: Delay is a lag-N register (not one-step),
GatedRecorder flushes an ungated final row at finalize, Latch is a
level-sensitive set/reset register (captures no input), SeriesReducer
emits its single summary row at finalize (not per cycle).

Gate: cargo build -p aura-std --lib clean; full std test run follows at
the all-crates gate once strategy/backtest/engine thread their sites
(the earlier per-crate test gate was unsatisfiable in isolation --
std's dev-deps pull crates whose sites belong to later tasks).

refs #316
2026-07-23 15:25:16 +02:00
claude df3c2f06bd feat(core): doc_gate shape check + required NodeSchema.doc field
C29 compile/unit seam, core carrier (iteration 1 of the self-description
spec): DocGateFault { Empty, RestatesName } + doc_gate() — deterministic
string-shape check, never content judgement — and NodeSchema gains the
required pub doc: &'static str, so every construction site must declare
a meaning line (compiler-enumerated). Core's own 10 test literals thread
doc: "test-only schema"; doc_gate/DocGateFault re-exported at crate root.

Downstream crates are E0063-broken by design until their sites thread
the field (tasks 3-7 of the plan); gates here are crate-scoped:
cargo test -p aura-core (59 green) + clippy -p aura-core (clean).

Interim subset commit of the implement run after adjudicating a
cross-task discard: task 2's file checkout had destroyed task 1's
node.rs/lib.rs work; restored from the loop's recovery snapshot
53c55c3 and merged with task 2's field threading.

refs #316
2026-07-23 15:10:42 +02:00
claude 8c19260e8d fieldtest: tap-subscribers — 5 examples, 0 bugs / 2 friction / 3 spec-gap
Cycle-close fieldtest for #283 + #77 (tap subscribers), exercising the new
tap surface as a downstream consumer over the public interface only (design
ledger, glossary, cargo doc, example corpus — never the crate sources).

Five examples, one per axis, all built and run against worktree HEAD bd0c557:
- tap_1_record_spread.ops.json    — record → runs/traces/graph/spread.json
- tap_4_measure_ic.ops.json       — two-tap run → aura measure ic (clean IC)
- tap_consumer/src/tap_2_fold.rs  — Named{mean}/Named{count} via run_signal_r
- tap_consumer/src/tap_3_live.rs  — Live(closure); bit-identical to fold + record
- tap_consumer/examples/refusal_probe.rs — unknown-label refusal (exit 1, no write)

The consumer crate is a detached workspace (own empty [workspace], path-deps on
aura-core/-engine/-runner), exactly as a real World program; it is not a
workspace member and does not build under `cargo build --workspace`. Verified
here with a fresh `cargo build --bins --examples` (Finished, 0 errors).

No merge-blocking defect: record/fold/live/measure over the shared pair are
correct and deterministic. Follow-ups filed at cycle close:
- #309 (feature) — a plain `aura run` echoes no recorded trace handle (F1+SG1)
- #310 (idea)    — Named fold has no data/CLI reach; ratify Rust-only vs selector (SG2)
- #311 (bug)     — same-name re-run orphans still-chartable tap files (SG3, pre-existing)
- #297 (comment) — library refusal = process::exit, not Result (F2, already tracked)
2026-07-22 00:40:04 +02:00
claude bd0c557f16 docs(ledger, glossary): tap-subscribers cycle tidy — C8/C27/C28 lifts, tap subscription vocabulary
Cycle-close audit for the #283/#77 tap-subscribers cycle
(09994b8..6e3f394). Architect review: code contract-clean; what
holds — C1/C7/C8 (empty-output caller-built consumers, (Timestamp,
Cell) zero-heap payload, lifecycle hooks inside the sequential loop,
byte-identical twin-run and streamed-vs-legacy byte-equality pins),
C28 (assembly-crate placement of the writer-holding consumer,
c28_layering guard green), C25 (registry growth = new Rust entry,
data-expressible Named form, roster-enumerating refusals). All drift
items were doc lifts, resolved in this commit:

- C8: the lifecycle is now a symmetric pair — initialize() as
  finalize's start-of-stream mirror (infallible by signature;
  acquire-in-initialize, degrade-to-inert, surface-once-at-finalize);
  history sidecar records the refinement and the #77 resolution the
  old Deferred paragraph still carried as open.
- C27: Guarantee and Current state now describe the shipped model —
  tap plan (Named{label, params} | Live) over the layered fold
  registry, BOTH entry points (run_signal_r AND run_measurement) on
  the one shared wiring pair, record streaming at constant memory.
- C28: aura-runner now defines one graph node of its own (the
  in-graph record consumer — exactly the graph-meets-persistence
  assembly position); the #297 refusal-site count grows ~20 → ~24
  (the four tap-plan refusals, already typed before the exit).
- glossary §tap: the subscription/fold-registry sense added; Avoid
  line steers probe/monitor/scope away from the observation slot.

Bench (report-only, loadavg 5.8 > 2 on the box — read with care),
all fingerprints OK, no regression:
  engine_throughput    bars_per_s 14005262   wall_s 0.714s   Δ bars_per_s +3.0% wall_s -3.0%
  ingest_throughput    bars_per_s 12970670   wall_s 0.387s   Δ bars_per_s +0.5% wall_s -0.5%
  campaign_sweep       cpu_percent 2202.0   peak_rss_mb 92.2   wall_s 1.396s   Δ cpu_percent +1.5% peak_rss_mb -6.1% wall_s -1.5%
  campaign_heavy       cpu_percent 2155.0   peak_rss_mb 94.8   wall_s 5.585s   Δ cpu_percent +1.8% peak_rss_mb -3.5% wall_s -0.4%
  cli_fixed_cost       help_ms 1.5ms   run_ms 3.6ms   Δ help_ms -1.8% run_ms -2.6%
No baseline update (report-only surface; no ratify due).

Correction on 6e3f394's body: "no Probe symbol is introduced
anywhere" — introduced is the operative word; the pre-existing
aura-core test fixture `struct Probe` stays, as the cycle's design
record specified.

Follow-ups on the tracker: #308 (legacy Recorder surface migration),
#307 (binary trace format). Cycle spec/plan working files discarded.

refs #283 refs #77
2026-07-21 23:56:14 +02:00
claude 6e3f394b48 feat(aura-runner, aura-cli): run_measurement on the shared tap pair; integration + register coverage (closes #283, closes #77)
Completes the #283 registry cycle. run_measurement drops its
duplicated Recorder-bind + post-run try_iter drain for the shared
bind_tap_plan/BoundTaps pair — the mirror of run_signal_r is now the
same code, so the two declared-tap entry points cannot drift; the
measure CLI arm passes TapPlan::record_all() (semantics unchanged,
constant memory). Measure-side library tests pin a fold plan's
one-row trace (streamed mean bit-equal to the slice mean over the
synthetic prices) and the tap-free write-nothing guarantee through
the shared pair.

Run-side integration tests pin the #283 acceptance surface: fold and
live plans agree exactly with the recorded series of the same
blueprint (C1, three runs, three drain policies); a live-only plan
persists nothing; plan refusals (unknown tap, roster-enumerating
unknown label) fire typed before any store I/O; two identical
record-all runs produce byte-identical tap files. The previously
untested `aura: writing tap traces failed` register gains end-to-end
coverage on both arms: store root occupied by a file (pre-run
refusal) and a read-only run dir (deferred initialize-open failure —
the run completes, the failure surfaces terminally, no index.json is
written, the store's treat-as-absent crash shape).

Two forced deviations from the plan's scripted literals, both
verified equivalent: an explicit match replaces expect_err (BoundTaps
is deliberately not Debug — it carries a TraceStreamer), and the test
helper takes &Path, not &PathBuf (clippy::ptr_arg under -D warnings).

Closes #77 as specced: zero per-cycle heap on the tap path
((Timestamp, Cell) payload, committed earlier this branch) and the
Recorder→Probe rename retired (no Probe symbol anywhere; decision
recorded on #77, 2026-07-21).

Verification: cargo test --workspace — 1517 passed, 0 failed;
cargo clippy --workspace --all-targets -- -D warnings clean;
tap_recording 5/5 (byte-compat anchors + both register arms).

refs #283 refs #77
2026-07-21 23:47:22 +02:00
claude d8ef9de5c2 feat(aura-runner): TapPlan + layered fold registry; run_signal_r re-wired onto the shared pair
The #283 registry model's assembly seam: tap_plan.rs carries
TapSubscription (Named{label, params} — fully data-expressible — |
Live(closure), the single non-data variant), TapPlan (name →
subscription + default; the plan travels with the FoldRegistry it
resolves against, the Env-at-resolve pattern, so layered hosts inject
vocabulary as data), and the registry itself: eight core entries
(record, count, sum, mean, min, max, first, last), each with a doc
line (help + roster-enumerating refusals) and a scalar-typed param
schema (all v1 entries param-less; the seam ships now because it sits
in every entry's build signature). Typed TapPlanError refusals fire
pre-bootstrap, before any store I/O; the terminal Store variant keeps
the established "writing tap traces failed" register bytes.

bind_tap_plan/BoundTaps is the shared wiring pair both declared-tap
entry points call; run_signal_r is re-wired onto it here (Recorder
bind + post-run try_iter drain replaced — no O(cycles) retention, no
per-cycle heap), with all six callers threaded (both CLI run-arm and
the four example-identity test calls pass TapPlan::record_all()).
run_measurement follows in the next commit.

Registry growth stays a new Rust entry (C25); a layer-registered
entry is pinned by the injection test, param validation by a
schema-carrying test entry (missing/mismatched/unknown all refused
with named messages).

Verification: cargo build --workspace clean; aura-runner 85+ tests
green incl. both example-identity anchors; tap_recording byte-compat
anchors green (recorded traces byte-identical, tap-free writes
nothing, duplicate-tap refusal unchanged); clippy -D warnings clean.

refs #283 refs #77
2026-07-21 23:18:54 +02:00
claude 92e281e4ec feat(aura-registry, aura-runner): deferred writer opener; TapRecorder moves in-graph to the assembly crate
Two #283 groundwork moves for the fold-registry model:

- TraceStreamer::register_tap returns a TapWriterOpener — everything
  needed to open one tap's incremental writer later, from inside a
  consumer node's initialize, without holding the streamer. tap_writer
  is re-expressed as register_tap(..).open(), so there is exactly one
  open path; pinned by a byte-equality test between the two.

- TapRecorder leaves aura-std (which must not know aura-registry, C28
  layering) and is reworked in aura-runner as the record entry's
  consumer: it holds the TapTraceWriter in-graph — initialize opens
  (deferred; an open error is stored and the node degrades to inert),
  eval appends per fired warm cycle (zero heap, no thread, no
  channel), finalize finishes the writer and reports exactly one
  terminal outcome. The SyncSender-based aura-std variant and its
  planned writer-thread protocol are retired; newest_cell becomes pub
  for the assembly crate.

Verification: cargo clippy --workspace --all-targets -D warnings
clean; aura-core/engine/registry/std/runner suites green (new tests:
opener equivalence, happy-path capture + single Ok outcome,
failed-open inert + single Err outcome).

refs #283 refs #77
2026-07-21 22:51:10 +02:00
claude b8ba324a41 feat(aura-core, aura-engine): Node::initialize — the start-of-stream mirror of finalize
Adds the second lifecycle hook the #283 design revision pairs with
finalize: Node::initialize (default no-op) runs once per node, in
topological order, before the first source value — inside the
deterministic sequence, no within-sim concurrency (C1). Consumers
acquire run resources there (the record consumer opens its streaming
writer); the hook is infallible by signature, so an acquiring node
stores its error, degrades to inert, and surfaces the failure once,
terminally, at finalize.

Every existing node keeps the default. Pinned by the mirror test
run_initializes_every_node_once_before_the_stream (init0,init1
strictly before any eval, exactly once), the shape of the existing
finalize test. RED was observed by temporarily reverting the flush
(test fails with evals-before-inits), then re-applied.

Ledger note for cycle close: C8's lifecycle section gains initialize
as finalize's mirror.

refs #283
2026-07-21 22:51:10 +02:00
claude 4ae2297a35 feat(aura-registry): streaming trace write path — begin_run, TraceStreamer, TapTraceWriter
Adds the incremental write path the tap-subscriber cycle streams
record subscriptions through: TraceStore::begin_run opens a run
directory and returns a TraceStreamer; per tap, TapTraceWriter
appends (ts, cell) points into two temp column streams and finish()
assembles the canonical ColumnarTrace JSON by streaming
concatenation (io::copy), removing the temps. index.json is written
last by TraceStreamer::finish, preserving the store's crash
discipline (no index -> NotFound, treat-as-absent).

Compatibility is pinned as an equality property, not a frozen byte
assumption: a new test asserts the streamed output byte-identical to
TraceStore::write's for the same rows (including empty and bool
columns), so the two write paths cannot drift and every reader
(read, read_family, chart) stays untouched. TraceStore::write is
refactored onto shared write_tap_file/write_index helpers used by
both paths. Temp streams open read+write (OpenOptions), since
finish() seeks back and re-reads them.

Verification: cargo test -p aura-registry — 78 passed (3 new:
byte-equality incl. empty+bool, crash shape NotFound, caller-order
index).

refs #283
2026-07-21 21:05:52 +02:00
claude 09994b83ed feat(aura-std): tap subscriber sinks — TapFold, TapRecorder, TapLive
The three C8 sinks realizing the per-tap subscription model (#283): TapFold
(closed FoldKind vocabulary Count|Sum|Mean|Min|Max|First|Last, in-graph
accumulation, exactly one finalize row), TapRecorder (bounded
SyncSender<(Timestamp, Cell)>, lossless backpressure — a full channel blocks
the sim, wall time never state), and TapLive (consumer-owned closure, inline
on the sim thread). All three move (Timestamp, Cell) — 16 B, Copy — instead
of (Timestamp, Vec<Scalar>): zero per-cycle heap on the tap path (#77 part
2). The shared kind-dispatched newest-cell read lives in tap_cell.rs.

Known-good subset commit of iter tap-subscribers (tasks 1-3 of 7, each gated
implementer -> spec-compliance -> quality DONE; the loop blocked on a task-4
plan defect, repaired separately): the trace-store streaming writer, the
TapPlan wiring seam, and both entry-point rewires follow in this same cycle.
Verified directly: cargo test -p aura-std — 109 passed, 0 failed.

refs #283, refs #77
2026-07-21 19:55:30 +02:00
claude 8688a60ded docs(ledger): split the design ledger into an INDEX map, per-contract live files, and history sidecars
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.
2026-07-21 16:40:36 +02:00
claude fb28b831f3 feat: show resolves a unique content-id prefix (closes #302)
aura {process|campaign} show now accepts the 8-hex prefix every surface
prints: an exact match is tried first, then the prefix is resolved
against the store's candidate ids — a unique match prints the identical
stored bytes as the full id (#164 byte discipline), an ambiguous prefix
refuses listing every sharing candidate, and a genuinely-unknown id
keeps its established refusal. The resolution mirrors the semantics
reproduce shipped with #298, closing the asymmetry the #300 fieldtest
named (df_4). Registry gains list_process_ids/list_campaign_ids
(missing store dir reads as empty, matching the blueprint-scan
discipline); campaign_not_found_prose becomes the single source for
show_campaign's two not-found arms. The ambiguity arm is pinned by a
unit test at the resolution seam — hash-derived ids make a
shared-prefix E2E fixture impractical.
2026-07-21 15:23:55 +02:00
claude 6b994cead3 test: red for show resolving a unique content-id prefix (refs #302)
aura {process|campaign} show is exact-match-only while every surface
prints 8-hex prefixes and reproduce has resolved prefixes since #298;
the fieldtest's prefix paste (df_4) hits the same flat not-found prose
as a genuinely-unknown id. The E2E test pins the wanted headline: a
unique 8-hex prefix of a registered id prints byte-identically what the
full-id show prints (#164 byte discipline), both verbs. The ambiguity
refusal is deliberately left to a unit test at the resolution seam —
hash-derived ids make a shared-prefix E2E fixture impractical.
2026-07-21 15:03:56 +02:00
claude a5ca0e7481 feat: the select-rule refusal enumerates the accepted roster (closes #301)
An unknown select rule in a process document now refuses with the
accepted vocabulary beside the quoted offending value:
  block std::sweep: unknown select rule "plateau" (accepted: argmax |
  plateau:mean | plateau:worst)
mirroring the roster-naming metric-refusal shape. The roster is derived
from slot_kind_label(SlotKind::SelectRule) — CLI describe output and
the document refusal stay in sync by construction — and a companion
test pins the "select rule: " prefix that derivation trims, so a
label-wording drift breaks a test instead of silently degrading the
prose. No alias-hint sentence: the enumeration alone rescues the
bare-plateau paste case (fieldtest df_3), and the document schema stays
strict per #300 F7.
2026-07-21 14:59:35 +02:00
claude edb8c37916 test: red for the select-rule refusal enumerating the accepted roster (refs #301)
A process document carrying an unknown select rule refuses correctly
but names no accepted vocabulary; the fieldtest's bare-plateau paste
(df_3) hits an opaque wall. The unit test pins the wanted property at
the site that builds the prose (select_from): the refusal enumerates
argmax, plateau:mean, plateau:worst beside the quoted offending value.
No alias-hint sentence — the enumeration alone rescues the paste case
and the project's refusal prose stays sparse; the document schema stays
strict per #300 F7.
2026-07-21 14:44:40 +02:00
claude 0a70f67711 fix: register outside a project refuses instead of creating a stray store (closes #305)
register_process and register_campaign now carry the same
env.provenance() no-project guard as their show/runs siblings, refusing
with exit 1 and the missing-Aura.toml prose instead of letting
Env::runs_root()'s relative fallback materialise ./runs/ in an innocent
cwd. Five pre-existing register tests ran from a bare temp cwd and were
reaching their asserted behaviour only through this bug; they now run
inside the fresh_project() fixture, preserving their original property
(content addressing, intrinsic-validation refusals) in a real project.

Verified: the RED E2E test (register_outside_a_project_refuses_and_
leaves_no_store, both verbs, refusal + no side-effect store) observed
GREEN by the loop's independent verify alongside the full suite.
2026-07-21 14:41:47 +02:00
claude 27ace153f2 test: red for register's stray-store creation outside a project (refs #305)
register_process/register_campaign call env.registry() without the
no-project guard their show/runs siblings carry, so Env::runs_root()'s
relative fallback silently creates ./runs/ in an innocent cwd and
registers the document there (exit 0). The E2E test pins both verbs and
both halves of the wanted refusal: exit 1 naming the missing Aura.toml,
and no store directory created as a side effect.
2026-07-21 14:27:04 +02:00
claude da37f596f2 fix: show outside a project names the missing Aura.toml, not the id
show_process/show_campaign now guard env.provenance().is_none() before
touching the registry — the guard their siblings (campaign_runs,
validate_campaign_file) already carry — refusing with the honest
degradation prose ('needs a project ... no Aura.toml found up from
{cwd}') instead of blaming the id with 'not found in the project
store' against a store that does not exist. Refusal exit 1 unchanged.
The register-verb sibling gap (silently creating a stray ./runs/) is
deliberately NOT touched here — tracked as #305.

RED-first: the in-process pin landed at 803c970; independent verify
re-ran it green with the suite.

closes #304
2026-07-21 13:29:39 +02:00
claude 803c9704cc test: red for show's no-project refusal blaming the id (#300 fieldtest)
Outside any project, show_process/show_campaign print 'not found in the
project store' — asserting a store that does not exist and blaming the
id, where the sibling verbs (campaign_runs, validate) degrade honestly
naming the missing Aura.toml. The in-process RED pins: the refusal
stays Err, its prose names 'no Aura.toml found up from', and does NOT
claim 'not found in the project store'. Diagnosed cause: both show fns
lack the env.provenance().is_none() guard their siblings use, so the
no-project fallback store's Ok(None) masquerades as an unregistered id.

refs #304
2026-07-21 13:21:11 +02:00
claude 63711a1d3a fieldtest: document-first — 4 examples, 7 findings
Downstream-author fieldtest of the #300 surface, public interface only,
real GER40 archive. All four axes hold: the closed
generate -> show -> hand-extend -> re-register -> run loop (2-cell
extended campaign), the typed stop grammar's clap rejections, bare
plateau's content-id identity with plateau:mean, and the show read-back
(byte-identical, no framing) with a clean unknown-id refusal.

Findings routed: 1 bug (show outside a project blames the id instead of
the missing Aura.toml — RED-first fix follows), 2 friction items filed
to the tracker (opaque process-document select refusal; no prefix-id
hint on show), 4 working (carry-on). The fieldtest spec survives
git-ignored as the next planning cycle's reference input.

refs #300
2026-07-21 13:16:59 +02:00
claude a12617bb80 audit: document-first cycle close — doc verb rosters caught up
Architect review (cycle 757e3ac): core clean — show is pure shell
dispatch over Env::registry() (C28); both ledger amendments match the
F1-F8 decision log exactly; document schema untouched; the #164
no-framing pin is stricter than the spec snippet asked. Four peripheral
drift items, resolved:

- fix: docs/authoring-guide.md verb rosters now name show (process) and
  show|runs (campaign) — the guide teaches exactly the
  generate->retrieve->extend loop this cycle closed.
- fix: docs/glossary.md campaign/process document entries carry the
  settled verb sets (record-reality glossary write).
- fix: show_process's not-found refusal now sources its prose from
  ref_fault_prose(ProcessNotFound) instead of a hand-copied format!
  (byte-identical, test-pinned; the campaign-side prose has no RefFault
  sibling and stays the single campaign instance).
- ratify (log-only): the shipped show helpers use print! (no framing,
  #164) where the spec snippet sketched println! — the code is the
  stricter, correct form; recorded on the reference issue's decision
  log. The spec is a git-ignored working file swept at this close.

Regression: aura-bench report-only run, all fingerprints OK, deltas in
noise (host load 9.6 noted; engine/ingest/campaign/cli lanes all within
±5%). Full aura-cli gates re-run green after the tidy edits.

refs #300
2026-07-21 12:58:02 +02:00
claude 757e3ac1bd feat: the document-first surface closes — show read-back, typed stops, bare plateau
The #300 keystone cycle: the residual run-quintet flags were already
document vocabulary on the --real arm (#210/#220); this closes the loop
and ratifies the surface.

- `aura process|campaign show <content-id>` prints a registered
  document's canonical bytes (print!, no framing — #164), so
  generate -> retrieve -> hand-extend -> re-register needs no direct
  store filesystem access. Refusals reuse the store-hint prose, exit 1.
- walkforward/mc --stop-length/--stop-k become clap-typed i64/f64
  (generalize's form); stop_knob_or and its parse_csv_list helper are
  deleted (multi-value/invalid input is now clap's exit-2 rejection —
  the two dissolved-verb refusal tests retarget, the type-dead unit
  test is deleted).
- --select accepts bare `plateau` as the documented default
  plateau:mean (#227 carry); document schema untouched — the two
  spellings generate content-id-identical campaigns (test-pinned).
- family.rs's quadruplicated demo stop literal collapses onto one
  DEFAULT_STOP const (maintainer-lens drift finding).
- Ledger: C25's "which projection next" line resolved (document-first
  delivered, per-verb identity re-ratified — F8; host/MCP stay
  demand-driven); C18 records the read-back.

Design triage, fork decisions F1-F8, and the auto-signed spec live on
the reference issue. Verification: full workspace suite 1482 passed /
0 failed with the real GER40 archive exercised (the byte-identity
round-trips ran, not skipped); clippy -D warnings clean.

closes #300
2026-07-21 12:50:10 +02:00
claude 4ed6455b64 fix: load_family resolves the enumerated family name, refusing ambiguity (#298)
GREEN for 78e68e6's RED. aura_runner::reproduce::load_family resolved
only the derived C18 handle '{family}-{run}' while the registry's
enumeration exposes the bare name factor — the natural
list-then-reproduce library workflow dead-ended (found by the #295
fieldtest; predates the extraction byte-identically). Resolution now:
exact-handle match keeps first precedence; a bare name naming exactly
one stored run resolves as fallback; an ambiguous name (several runs,
or a name/handle collision) refuses without guessing, listing the
candidate handles. Two additional tests pin the ambiguity refusal and
the handle-precedence-on-collision semantics.

Ledger record-reality rider: the C18 #158 paragraph claimed reproduce
refuses an unknown id with exit 2; the code has always exited 1 —
correct per C14's partition (the id names missing recorded state, a
runtime failure, not a usage error). The paragraph now records the
actual behaviour and the new name-fallback semantics.

Verification: cargo test --workspace green (1477 passed, 0 failed);
clippy --workspace --all-targets -D warnings clean.

closes #298
2026-07-21 07:34:41 +02:00
claude 78e68e6002 test: red for the enumerated family id rejected by load_family (#298)
Registry::load_family_members() exposes the bare name factor while
aura_runner::reproduce::load_family exact-matches only the derived C18
handle '{family}-{run}' — the natural list-then-reproduce workflow
dead-ends on its first attempt (found by the #295 fieldtest; predates
the extraction byte-identically). The test pins: the enumerated
identity of an unambiguous single-run family resolves through
load_family to the same family the canonical handle names.

refs #298
2026-07-21 07:18:59 +02:00
claude 9df217d868 fieldtest: shell-boundary — 4 examples, 6 findings
Downstream-consumer corpus for the #295 library surface: a standalone
workspace (path-deps, no aura-cli in the link graph — cargo tree
verified) exercising the four cycle axes as a World program would,
public interface only: a DefaultMemberRunner member run over real
GER40, a hand-authored campaign document through
aura_campaign::execute, the IC + registry deflation dispatch as
library calls, and load_family/reproduce round-trips. Run transcripts
committed beside the sources.

Findings routed separately: the family-id mismatch between the
registry enumeration and the reproduce keying (list-then-reproduce
dead-ends) to debug; the reproduce-API friction (no report on the
simple path; DataSource/pip re-specification the manifest already
holds, wrong pip silently DIVERGED) to a follow-up. Four working
confirmations, chief among them the cycle's acceptance claim: all
four axes compiled first-try from the public docs alone.

refs #295
2026-07-21 07:10:09 +02:00
claude 5006766579 audit: shell-boundary cycle close — drift resolved in-commit
Cycle-close audit (architect: drift_found; design core clean). What
holds, architect-confirmed with the guard run and the diff read in
full: the shell boundary is real and enforced (full-workspace
c28_layering incl. completeness + shell-content checks, acceptance
grep clean — no member-run symbol left under crates/aura-cli/src);
aura-campaign keeps zero production dependency on the runner; the IC
move is verbatim against the anchor; the new library-only E2E pins C1
(two independent runners, byte-identical RunReport).

Drift items, all resolved as fixes in this commit:
- design ledger: the C28 status sentence "No crate exists yet for
  measurement" contradicted this cycle's own phase-3 done line — now
  records aura-measurement as seeded (#295), execution still unbuilt.
- the #147 registry-dispatch acceptance tests (IC deflation null,
  seeded determinism, cross-vocabulary refusal) were pure library
  properties stranded in the shell's test module — relocated to
  aura-measurement/tests/registry_dispatch.rs (engine/registry as
  dev-only, layering-exempt edges), per the cycle's own tests-move-
  with-their-module rule.
- aura-runner's direct production edge on the external data-server
  tree was structurally invisible (the direction table checks aura-*
  keys only) and unrecorded — the C28 assembly prose now names it, and
  a new c28_layering test pins the external tree's entry points to
  exactly {aura-ingest, aura-runner, aura-cli}.
- the C28 shell prose named three buckets that did not cover two real
  shell residents — it now names the op-script construction front-end
  (translation) and the `aura new` scaffolder (authoring-tooling,
  shell-resident like rendering until a second consumer wants it).

Deliberately deferred, tracker homes exist (not drift): #297
(process::exit sites in aura-runner's single-run verb paths), #294
(IC duplicate-timestamp semantics), #288-era rustdoc unresolved-link
warnings in aura-std/aura-backtest (pre-existing byte-identically at
the anchor).

No regression scripts are configured (the bench is report-only); the
architect review is the gate. Verification: cargo test --workspace
green (1474 passed, 0 failed) incl. the relocated registry-dispatch
tests and the new data-server guard; clippy --workspace --all-targets
-D warnings clean.

refs #295
2026-07-21 06:46:13 +02:00
claude 170c6c82dc feat: full-workspace C28 guard — the shell boundary is structural (#295 part 2)
Completes the shell-boundary cycle (tasks 10-13):

- c28_layering now enumerates the FULL workspace: every crate row is
  reconciled against its real production [dependencies], a completeness
  assertion pins the table to the on-disk crates/ set (a new crate can no
  longer escape the guard silently), the shell/assembly imported-by-
  nothing rule is asserted, and a shell-content check pins aura-cli to
  no-[lib] plus a closed allow-list of argv/translation/presentation
  modules. A new domain module in the shell now fails the suite.
- Ledger amendments: C28 gains the assembly position (aura-runner) and
  the corrected import-rule prose (the ratified aura-campaign ->
  aura-backtest production edge, #291/#292); phase 3 is marked done with
  the #297 process::exit residual named; a provenance note records this
  as structural-debt closure, not a demonstrated downstream blocker.
  C25 records the control-surface decision: the text artifact vocabulary
  is canonical, every control surface (CLI executor verbs, a future
  host, an MCP face, a World program) is a projection/executor over it —
  the which-projection-next ranking deliberately open on #295. C14 gets
  the executor-face amendment, C26 the binding-module relocation.
- aura_campaign::MemberRunner's doc names the shipped default
  implementor (aura-runner::DefaultMemberRunner) while the column keeps
  zero dependency on it.
- New library-only E2E fixture (aura-runner/tests/world_member_run_e2e):
  runs the canonical member recipe over a tiny synthetic archive with no
  aura-cli in the link graph, and pins C1 determinism (two independently
  constructed runners produce a byte-identical RunReport).
- campaign_run.rs module doc: intra-doc MemberRunner link re-anchored to
  aura_campaign::MemberRunner (the impl moved out with part 1).

Verification: cargo test --workspace green (1473 passed, 0 failed);
clippy --workspace --all-targets -D warnings clean; cargo doc clean of
NEW warnings (the five unresolved-link warnings in aura-std/aura-backtest
predate this cycle byte-identically at the anchor — #288-era doc drift,
left for the cycle audit).

refs #295
2026-07-21 06:35:31 +02:00
claude 5c2ac982bc refactor: extract the member-run recipe into library crates (#295 part 1)
The shell no longer defines what an aura backtest is. Tasks 1-9 of the
shell-boundary cycle — structural extraction, behaviour byte-identical:

- aura-runner (new; the C28 assembly position): input binding (the C26
  module, moved whole), the C1-load-bearing param<->config translators,
  harness assembly (wrap_r / run_signal_r / run_blueprint_member and the
  probe/reopen cluster), axis + risk-regime conventions (bind_axes et
  al.), the campaign family builders + MC guards, reproduce
  (process::exit -> RunnerError, shell remaps to identical bytes), the
  measurement-run orchestration, project loading (Env / cdylib load /
  charter / staleness, moved whole), the coverage gap-walk (deduplicated
  onto interior_gap_months), and DefaultMemberRunner — the public
  implementation of aura_campaign::MemberRunner (ex CliMemberRunner).
  The MemberRunner trait stays in aura-campaign; the column still
  imports no harness/data-binding machinery.
- aura-measurement (new; seeds C28 rung 3): the IcMetrics/IcKey
  vocabulary + information_coefficient, verbatim incl. serde derives
  (#294 duplicate-timestamp semantics move as-is, unresolved).
- aura-backtest: the pure per-run scaffold (point_from_params,
  wf_ms_sizes / fit_wf_ms_sizes, intersect_shared_window).
- shell residue: main.rs / campaign_run.rs keep argv/dispatch,
  argv->document translation, and presentation only;
  walkforward_summary_json_from_reports now calls the public
  aura_engine::param_stability instead of its inline twin.
- worked example (crates/aura-runner/examples/world_member_run.rs): a
  World program runs a member backtest through the library alone.

Held quality findings, adjudicated: the plan's literal pub-visibility
list for binding.rs kept (cosmetic); ~20 refusal sites inside
aura-runner (family/member/measure/translate) still process::exit —
behaviour-identical today, conversion to RunnerError is filed forward
(refs #297); rustfmt line-width drift on re-pathed call sites left for
a repo-wide fmt decision.

Verification: cargo test --workspace green (1471 passed, 0 failed);
cargo clippy --workspace --all-targets -D warnings clean. Byte-identity
is pinned by the untouched shell E2E suites (cli_run, measure_ic,
run_measurement, research_docs, run_refuses_unrunnable_blueprint,
tap_recording — zero assertion edits).

Remaining in this cycle: full-workspace c28_layering + shell-content
check, ledger amendments (C28 assembly position, C25/C14 control-surface
consequence, C26 realization note).

refs #295
2026-07-21 05:20:27 +02:00
claude 2cf4574e33 audit: cycle metric-vocabulary tidy — ledger records the retired #147 deferral
Cycle-close audit (architect: drift_found, design core clean). What holds,
architect-confirmed: C18 bit-identity for RunMetrics (prose derived
byte-identically, rng order preserved, registry suite unchanged); the C10
wall (monomorphic R-gates, r_based in the R vocabulary, cross-vocabulary
refusal tested both ways — no leak); C28 direction (trait in aura-analysis,
vocabulary supplied from the outer rungs, zero Cargo edge changes); C1
determinism pinned through the generic path.

Drift items, all resolved as fixes in this commit:
- design ledger: the C28 #147 disposition now records item 2 SHIPPED (the
  A1 cut) with A2 still deliberately deferred; the #136 one-implementor
  clause carries a supersession note (the IC is the second implementor).
- seven stale doc comments describing the pre-#147 or mid-cycle state
  (analysis trait + estimator docs, registry check_r_metric C9 claim,
  engine re-export note, campaign PER_MEMBER_METRICS roster note,
  member-seam guard comment, research vocabulary note) updated to the
  shipped state.
- R_BASED_METRICS is now oracle-pinned against RunMetricKey::r_based()
  in the vocabulary test (it feeds the NonRMetric refusal prose; a
  divergence would have misreported the R-gate silently).

Noted, not amended (history stays): commit 6744f67's body says 'three
syntax-only edits' where the test module actually took five.

No regression scripts are configured (the bench is report-only); the
architect review is the gate. Verification: extended vocabulary test,
campaign suites, analysis/research suites green; clippy --workspace
-D warnings clean.

refs #147
2026-07-20 20:03:02 +02:00
claude 0651e16146 feat: the IC becomes the second metric vocabulary; rosters single-sourced
The A1 cut of #147 item 2, part 2 of 2 (the consumers), completing what
part 1 (the MetricVocabulary substrate) set up:

- aura-campaign's RANKABLE_METRICS hand-copy becomes a re-export of the
  backtest vocabulary's roster; the #190 guard test extends to pin the
  roster nesting (rankable == vocabulary roster, rankable within
  per-member, per-member within the research vocabulary) and the legacy
  exposure_sign_flips alias, so any drift fails a test loudly. The
  research and per-member rosters themselves stay hand-copies by ratified
  isolation (#190 accepted residual) — now oracle-pinned instead of trusted.
- aura-cli gains IcMetrics + IcKey implementing MetricVocabulary: the
  second PRODUCTION implementor. The standalone `aura measure ic`
  reduction now computes its permutation p via the shared null_draw +
  one_sided_p_laplace building blocks (independent draws instead of
  chained shuffles — statistically equivalent, deterministic per seed;
  emitted p values may differ in the last digits from #290's).
- Acceptance proven at the registry boundary: an engineered
  SweepFamily<IcMetrics> deflates through optimize_deflated to a LOW
  overfit probability and a noise family to a HIGH one (the permutation
  null, dispatched by the vocabulary — the deflation machinery is no
  longer baked-in R-only); same seed reproduces the identical
  FamilySelection; cross-vocabulary names are refused with the family's
  own roster in the prose (the C10 guard). A new cli_run E2E exercises
  the legacy alias through the real `aura runs family … rank` boundary.

Deviation from the plan, forced by the compiler: IcMetrics additionally
derives Debug (unwrap_err on the deflation Result needs it; mirrors
RunMetrics).

Verification: full workspace suite green (no FAILED, no compile errors),
clippy -D warnings clean, C18 goldens (cli_run, measure_ic, c28_layering)
green unchanged. A2 — measurement runs as sweep-family citizens — stays
deliberately deferred until a concrete campaign demand exists.

closes #147
2026-07-20 19:55:24 +02:00
claude 6744f670b1 refactor: registry deflation dispatch generic over the MetricVocabulary trait
The A1 cut of #147 item 2, part 1 of 2 (the substrate): the registry's
ranking/deflation machinery no longer bakes in the R metric vocabulary.

- aura-analysis gains the deliberately narrow MetricVocabulary trait (name
  resolution, roster, direction, value read, one null draw) and the
  single-sourced one_sided_p_laplace estimator; re-exported through
  aura-engine's existing seam (no Cargo.toml edge changes anywhere).
- aura-backtest now OWNS the R vocabulary: RunMetricKey (was the registry's
  private Metric enum, exposure_sign_flips alias preserved), r_based (the
  C10 classification), R_BASED_METRICS/RANKABLE_METRICS rosters, the
  centred moving-block null_draw, and member_metric_from_rs.
- aura-registry: metric_cmp/rank_by/null_best_of_k/member_sd generic over
  M: MetricVocabulary; optimize/optimize_plateau/optimize_deflated over
  M: MetricVocabulary + Clone (SweepPoint<M>: Clone bounds on M: Clone);
  the private vocabulary deleted; check_r_metric/generalization stay
  monomorphic on the RunMetrics vocabulary (the C10 wall by type);
  RegistryError::UnknownMetric carries the vocabulary's roster so the
  refusal prose derives instead of being hand-strung (byte-identical for R).

Bit-identity (C18): rng consumption order, centring, Laplace arithmetic,
floors, tie rules, and refusal prose are all unchanged for RunMetrics
inputs — the full registry suite (75 tests) passes unchanged except three
syntax-only edits for the UnknownMetric variant reshape; clippy -D warnings
clean on the three touched crates. Alternatives considered: a vocabulary
table in aura-core (rejected: inverts C28's supplied-from-outer-rungs
direction and cannot host the null model); a parallel measurement-side
deflation path (rejected: duplicates the machinery and adds a fifth
roster). Part 2 lands the consumers (campaign roster single-sourcing, the
IC implementor, acceptance tests).

refs #147
2026-07-20 19:10:39 +02:00
claude a9d36ddd70 feat: measurement's first deflatable metric — the Information Coefficient
Give a measurement run a standalone post-run quality score: the Information
Coefficient (IC), corr(signal_t, forward_return_{t+h}), with a permutation
null model. Before this a measurement run persisted only tap names and series
(MeasurementReport) — inspectable but not rankable or deflatable. IC is the
first metric measurement supplies: the tap-to-scalar reduction the C28
process-column / metric-interface seam anticipates ("a named metric vocabulary
supplied by measurement and backtest instead of baked in R-only").

Surface: `aura measure ic <run> --signal <tap> --price <tap> [--horizon]
[--permutations] [--seed]`. It reads the run's two recorded tap traces, builds
the forward-return series offline over the recorded price spine, aligns
signal -> return by exact timestamp, computes the IC, and prints it with a
one-sided permutation-null overfit probability (Laplace-smoothed, mirroring the
R deflation arm's formula in optimize_deflated).

Placement follows the C28 ladder: the generic pieces — pearson_corr and a
Fisher-Yates permute over SplitMix64 — land in aura-analysis (the domain-free
statistics foundation, beside resample_block); the IC semantics (forward-return
horizon, the offline join) and the CLI verb land in aura-cli (the shell). The
run path (run_measurement), MeasurementReport, the trace store, and the registry
metric vocabulary are untouched — existing `aura run` output stays byte-identical
(the C18 golden run_prints_json_and_exits_zero stays green).

Causality (C2): the forward-return read close_{t+h} is over a completed run's
recorded trace, not an in-graph node. A look-ahead signal is structurally
impossible in a causal run, so the engineered-vs-noise acceptance property is
unit-tested over hand-built offline series (perfect signal -> IC ~= 1, p < 0.05;
a varying but exactly-uncorrelated signal -> IC = 0, not significant;
constant/degenerate -> the (0.0, 1.0) floor), while the E2E validates the CLI
wiring, report well-formedness, determinism, and the error paths over a real run.

This lands the second, structurally distinct null-model computation (permutation,
beside the moving-block bootstrap behind R). That makes the deferred registry
deflation-vocabulary abstraction (#147 item 2) demand-driven rather than
speculative — the next cycle.

Verified: cargo test --workspace green (incl. the new aura-analysis unit tests,
the aura-cli ic_* unit tests, and the measure_ic E2E); cargo clippy --workspace
--all-targets -D warnings clean; the C18 byte-identity golden unchanged.

closes #290
refs #147
2026-07-20 16:27:57 +02:00
claude a56ab7859d refactor: cut the engine's backtest-metrics edge via RunReport<M>
C28 phase 2 (Stratification); realizes item 1 of the deferred #147. The
engine's production surface no longer names a backtest-metric type:

- RunReport becomes generic over its metric payload M; sweep/mc/walkforward/
  blueprint thread the parameter (SweepPoint<M>, SweepFamily<M>, WindowRun<M>,
  WalkForwardResult<M>). RunManifest stays concrete and engine-owned (its
  selection: Option<FamilySelection> embeds the foundation-grade analysis type).
- summarize and the MC assembly (McDraw/McFamily/McAggregate/RBootstrap/
  r_bootstrap/monte_carlo) move to aura-backtest - McAggregate::from_draws reads
  RunMetrics fields by name, so generifying it is the phase-6 metric-vocabulary
  abstraction (#147 item 2), still deferred; wholesale relocation is the honest
  cut. The concrete instantiation lives in aura-backtest as
  `type RunReport = aura_engine::RunReport<RunMetrics>` + sibling aliases.
- the statistics kernel (MetricStats/quantile/resample_block/SplitMix64) moves
  to the aura-analysis foundation; the engine re-imports it (inner->foundation,
  legal) and re-exports it so existing consumers stay source-compatible.

Dependency inversion in one commit: aura-engine drops aura-backtest from
[dependencies] (back to dev-deps for its SimBroker/RunMetrics test fixtures);
aura-backtest gains aura-engine. Cycle-free for lib targets - the cycle closes
only through the engine's dev-dep edge, the pattern aura-vocabulary already
uses. aura-backtest reaches the kernel transitively through the engine
re-export, so no aura-backtest -> aura-analysis edge exists (the C28 ladder
permits backtest -> {core, engine} only). run_indexed / SplitMix64::next_f64
widened pub(crate) -> pub for cross-crate use.

Consumers (registry/campaign/cli/composites/ingest/bench) rewired by import
path only, no call-site logic changed. The c28_layering structural test extends
to the full ladder: aura-analysis (no aura-* deps), aura-engine ⊆ {core,
analysis}, aura-backtest ⊆ {core, engine}.

Behaviour-preserving: 1448/0 tests, clippy -D warnings clean, serde shapes
byte-identical (C18 - RunReport<M> keeps field order manifest,metrics; the
CLI pre-serialized-splice contract unchanged), moved code traceable via git
rename detection. Cycle-introduced broken intra-doc links fixed.

closes #292
2026-07-20 13:25:07 +02:00
claude 94aaa4cde8 refactor: split aura-analysis into statistics and backtest metrics
C28 phase 5 (Stratification): the backtest reductions - RunMetrics, RMetrics,
summarize_r, r_metrics_from_rs, and the position-event table - move verbatim
into aura-backtest::metrics, beside the position_management producer whose
record layout they read (the r_col/cost_col lockstep contract is now
intra-crate; its stale "aura-std" comment corrected). aura-analysis is reduced
to the domain-free half: the multiple-comparison hurdle math (inv_norm_cdf,
expected_max_of_normals) and the selection-provenance types
(FamilySelection/SelectionMode - kept beside the statistics so
RunManifest.selection embeds a foundation-grade type), [dependencies] = serde
only.

The engine re-export surface is name-unchanged (report.rs re-imports from both
crates), so no indirect consumer needed a source edit; aura-backtest moves from
aura-engine's dev-dependencies into [dependencies] as a commented TRANSIENT
widening of the C28 violation, removed by the phase-2 edge cut on this branch.
The campaign drift guard imports its pinned types from their new source
crates. Behaviour-preserving: 1448/0 tests, clippy -D warnings clean, serde
shapes byte-identical (C18), moved code traceable via git copy detection.

closes #291
2026-07-20 12:23:17 +02:00
claude b39fd63396 refactor: split the aura-std roster into C28 layer crates
Phase 4 of the Stratification milestone. aura-std held four C28 ladder
layers in one roster; this cuts them into layer-aligned, aura-core-only
node crates so the import direction is enforced by the crate graph:

- aura-std        — engine nodes only (arithmetic/logic/rolling + sinks)
- aura-market     — session, resample
- aura-strategy   — bias, stops, sizer, cost-model machinery
- aura-backtest   — sim_broker, position_management
- aura-vocabulary — the relocated closed std_vocabulary roster

Node modules move verbatim (byte-identical renames); consumers are
rewired by import path only. A new structural test
(aura-vocabulary/tests/c28_layering.rs) asserts each node crate's
[dependencies] stay within its C28-permitted inner set, catching the
acyclic-but-outward violation the compiler misses.

Behaviour byte-identical: full workspace suite green (1448 tests), no
golden edited, clippy -D warnings clean. C28 Status block updated.

closes #288
2026-07-19 20:28:20 +02:00
claude 34ff539143 feat: measurement run verb — shape-dispatch aura run on blueprint output
Phase 3 of the Stratification milestone (contract C28). `aura run` now
shape-dispatches on the loaded blueprint: a `bias` output takes the
existing strategy path (wrap_r + R evaluation), byte-identical; a
blueprint that declares taps but exposes no `bias` runs bare via the new
`run_measurement` (bootstrap, run, drain the declared taps) with no
SimBroker, no risk executor, and no per-cycle equity/exposure/r
recorders; a blueprint exposing neither is refused. This gives the
measurement layer its own run verb and removes the O(cycles) recording
retention (the measured ~2 GB peak RSS over 12y UK100 M1) the mandatory
R scaffold imposed on a measurement-shaped run.

A new `MeasurementReport { manifest, taps }` (aura-engine::report, beside
RunReport) is the stdout/record shape — no R metrics, since a bare run
has no broker. The manifest's `broker` carries an interim "measurement"
sentinel; the honest `Option<String>` model is deferred to the #147
metric-genericity block, keeping this phase #147-free and the
strategy-path C18 goldens byte-identical.

`run_signal_r` is untouched (the tap machinery is duplicated into
`run_measurement`, not extracted). The measurement arm's closed-blueprint
guard uses `signal.param_space()` rather than `blueprint_axis_probe`,
which welds wrap_r and would panic on a no-`bias` signal.

refs #286
2026-07-19 16:09:51 +02:00
claude 1c49d5dce2 design: ratify C28 — internal stratification (ladder, process column, shell)
Codify the layer model settled with the user: an inner-to-outer ladder
engine -> market -> {measurement | strategy} -> backtest -> execution,
the research-process column beside the ladder, the shell outside. States
the import rule (inner never imports outer; measurement/strategy
siblings; column imported by no ladder crate; shell imports all;
dev-deps exempt), the six seams (five already ratified contracts —
C8/C24/C27/C18/C10-C13 — only the metric interface new), and an honest
Status recording the one hard production violation (engine -> analysis
via report.rs re-exports + the R-typed mc.rs fields) and its coupling to
the deferred #147.

Also restore the "### C19" header, deleted as collateral of ebe8568
(2026-07-03 cycle-0106 tidy); its body survived orphaned under C18. C28
cites C19, so the header must resolve.

refs #286
2026-07-19 15:14:50 +02:00
claude 592750b122 audit: close #284/#285 cycle — GraphBuilder::tap twin restores C24 symmetry
Cycle-close tidy for the #284 (tap construction op) + #285 (docs) cycle. The
architect drift review (cb6211c..HEAD) came back drift-clean on the ledger
contracts the cycle touched — C27 machinery untouched, a faithful `expose`
twin (C25 preserved), lockstep op<->OpDoc / OpError<->format_op_error arms
paired, and the #285 docs verified accurate against aura-std/src/session.rs —
save one drift item, fixed here.

Drift item (fixed): the op-script gained `Op::Tap` but `GraphBuilder` had no
`tap()` twin, softening C24 (replay-equals-builder = one construction
semantics) and C17 (builder-primacy) — every other op verb has a builder
twin. Unlike the op-script's `doc` gap (a ledger-recorded #125 scope cut),
this asymmetry had no rationale; a tap is a first-class authoring act on both
surfaces, so denying only the fluent builder was an accident of incremental
delivery, not a design cut.

Fix: `GraphBuilder::tap` (builder.rs), the exact output-side mirror of
`expose` — a `taps: Vec<(String, OutPort)>` accumulator resolved at `build()`
into `Tap { name, from: TapWire }` and threaded via `.with_taps`. Plus a
construction.rs lockstep test `tap_replay_matches_builder` proving the
op-script `Op::Tap` replay and the `GraphBuilder::tap` twin serialize to
byte-identical blueprint JSON and compile to identical `FlatGraph.taps` — the
tap op is the by-identifier face of the same construction semantics, not a
second one.

Carry-on (architect debt-low, no action): commit 43a1cc4's body phrase
"op-built composites silently lost their taps" over-claims a prior drop (no
tap op existed pre-cycle); the "latent" qualifier keeps it honest, so the
wording is left as-is rather than rewriting an already-landed commit.

Verification: full `cargo test --workspace` green; `cargo clippy --workspace
--all-targets -- -D warnings` clean. The bench is report-only by project
convention and was not run — this cycle adds a construction-time op + docs,
no hot-path change.

Fieldtest: the #284 `tap` surface is exercised from the public interface by
two E2E consumer-path tests (op-script -> `aura graph build` -> `aura run` ->
persisted trace) plus two worked authoring examples hand-verified through the
built binary; #285 is documentation. The per-cycle fieldtest is satisfied.

Spec + plan working files (docs/specs/tap-construction-op.md,
docs/plans/tap-construction-op.md) discarded per audit Step 5 (git-ignored,
never committed; their design intent lives in the commit bodies + the ledger).
2026-07-18 16:42:39 +02:00
claude a5e02ff541 docs: SessionFrankfurt output semantics + declared-tap authoring (#285)
Surfaced by the measurement-milestone fieldtest: a downstream author
reaching for session anchoring or declared taps had no public statement of
either — the semantics lived only in node source / the design ledger.

SessionFrankfurt / Session output semantics:
- glossary `session node`: corrected — it claimed three streams
  (`bars_since_open`, `in_session`, `session_open_ts`), but the shipped node
  emits ONE `i64` field `bars_since_open`. Now states the real contract: the
  count of completed bar-periods since the local (tz-aware, DST-correct)
  session open, close-instant indexed (09:15->1, 09:45->3; pre-open <=0), with
  the Frankfurt 09:00 Europe/Berlin open + `period_minutes` knob + the
  value-ignored `trigger`.
- authoring-guide: a "Session anchoring: the SessionFrankfurt preset"
  subsection — schema (trigger / period_minutes / bars_since_open), the
  close-instant multiples, the EqConst gating pattern, DST handling, and the
  add-snippet. Verified against `aura graph introspect --node SessionFrankfurt`.

Declared-tap authoring (C27 / the #284 tap op):
- authoring-guide: the `tap` op-table row (seven ops -> eight) + a worked
  "declaring a measurement tap" example. The example is verified: piping it
  through `aura graph build` emits
  `taps:[{"name":"spread","from":{"node":2,"field":0}}]`.
- design ledger op-list (INDEX.md): a `tap` bullet (seven verbs -> eight);
  also corrected the `expose` "only verb that keeps `as`" claim — `tap` keeps
  it too.
- README op-kind enumeration: adds `tap`.

Docs only; no code change. Both worked examples were run through the built
`aura` binary; the SessionFrankfurt schema was checked against introspect.

closes #285
2026-07-18 16:31:17 +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 cb6211c888 fix(cli): aura run refuses an unrunnable blueprint cleanly, not by panic (#282)
Two user-authorable blueprint shapes crashed `aura run` with an internal
.expect instead of the CLI's clean aura: refusal register:
- a tap wire pointing out of range: run_signal_r's compile now handles
  CompileError (TapWireOutOfRange) as an aura: refusal + exit 1, naming that
  the blueprint does not compile to a runnable harness.
- an exposed output not named bias: a pre-check in dispatch_run (the single
  loaded-blueprint call site, before the free-knob axis probe that welds the
  hardcoded bias port and would itself panic) refuses with a message telling
  the user aura run expects a strategy with a bias output and listing what the
  blueprint exposes. The sweep path (run_blueprint_member) is untouched.

Surfaced by the measurement-milestone fieldtest as the sharp edge of raw-index
tap authoring (#284 tracks the name-addressed tap op that removes the miscount
class at the source).

closes #282
2026-07-18 01:14:29 +02:00
claude e768072ed0 test: red for aura-run panics on an unrunnable blueprint (#282)
Two RED tests pinning that `aura run` refuses cleanly (exit 1 + an aura:
message) rather than panicking on a user-authorable blueprint: a tap wire
pointing out of range (run_signal_r's compile .expect swallows
TapWireOutOfRange into a panic, main.rs:1753) and an exposed output not named
bias (wrap_r's g.build().expect panics on UnknownOutPort from the hardcoded
bias weld, main.rs:1667). Surfaced by the measurement-milestone fieldtest.

refs #282
2026-07-18 00:53:36 +02:00
claude 9005fd53a2 docs(glossary): record the C27 author-declared sense of 'tap' (#282)
The measurement milestone overloaded 'tap': beside the closed campaign
persist_taps vocabulary (equity | exposure | r_equity | net_r_equity), the
word now also names a declared tap — a Composite.taps entry a hand-authored
blueprint declares on an interior output wire (C27), the output-side twin of
an input_role, bound run-mode-aware. Record-reality glossary update
reconciling the two senses; a worked authoring example is owed at docwriter
(#285).
2026-07-18 00:48:43 +02:00
claude ba46350da9 fieldtest: measurement milestone — 3 studies, 8 findings
Milestone acceptance corpus for 'Measurement as a first-class citizen',
authored as a downstream consumer (public interface only): a gated
rate/mux/counter over the four new cells, a SessionFrankfurt anchor study,
and the origin first-candle-momentum study on real GER40 data — each a
hand-authored blueprint plus one recorded run, zero throwaway Rust. The
three capabilities compose end-to-end and record via declared taps.

Findings routed separately: two aura-run panics-where-refusal-expected
(bad tap wire; non-bias output) to debug; a tap-authoring friction and two
doc gaps to follow-ups.
2026-07-18 00:45:38 +02:00
claude cf670caf46 audit: cycle-close tidy for the measurement milestone (#280/#281/#282)
Architect drift review over 16ca4e1..HEAD: drift-clean but for one low
citation slip, now fixed here — snap_to_nearest_minute's doc cited the
record-then-replay boundary as C6 (firing policy); it is C11. What holds
(architect-confirmed): C27 taps match the contract (pure declaration,
compile-resolve+hoist through the shared lowering remap, caller-built
aura-core-only sink, unbound-is-inert, Tier-1 additive serde); the four #281
cells are C8/C6-clean (<=1 record/eval, warm-up None, When's quiet-None is the
Resample precedent, barrier stall correct); #280's snap respects C11 (node-local
reads only, firing/barrier timestamps untouched, stream verbatim); FlatGraph and
validate_wiring lockstep complete.

Regression (report-only bench): exit 0, all five surfaces fingerprint OK, deltas
within noise on a loaded box — no baseline update, nothing to ratify.

Scaling follow-up filed as #283: the single-run tap drain buffers O(cycles) rows
(the #138 profile) — fine for the bounded acceptance run, to be folded before a
full-history declared-tap study.
2026-07-18 00:25:39 +02:00
claude 7a4e5eb99d docs(ledger): C27 — declared taps contract (#282)
Records the tap/binding contract: taps are the output-side twin of input_roles
(C26), resolved and hoisted at compile, bound run-mode-aware via a caller-built
sink (engine stays aura-core-only), recorded on the single run and inert in a
sweep. Documents the deliberate DCE-deferral — build-time elision now,
chain-pruning when DCE (C23) lands — and the unbound-is-inert non-error
asymmetry vs a mandatory input role.

refs #282
2026-07-18 00:17:01 +02:00
claude 2e8d74903c feat(cli): record declared taps on the single-run path (#282)
The payoff: a hand-authored blueprint's declared taps become recorded output
from one `aura run`. run_signal_r binds each tap (hoisted into flat.taps after
wrap_r nests the signal) to a fresh Recorder before bootstrap, drains the
series after the run, and persists each as a ColumnarTrace via the existing
env.trace_store() — the same trace surface the campaign path feeds, so the
existing chart tooling reads them. Duplicate tap names are refused (the
caller-owned DuplicateBind dedup) before anything persists. A tap-free run
writes no trace store — byte-identical to today. Sweep/reduce leaves taps
unbound (run_blueprint_member untouched). Covered end-to-end: a persisted tap
series, the tap-free no-write guard, and the duplicate-name refusal.

refs #282
2026-07-18 00:16:17 +02:00
claude 93d0ec45f2 feat(engine): FlatGraph::bind_tap + TapBindError (#282)
The run-mode-aware bind seam. bind_tap attaches a CALLER-BUILT Box<dyn Node>
sink to a declared tap — appending the sink node + an edge from the tap's
(node, field) to its input, before bootstrap so the Kahn sort orders it. The
engine constructs no aura-std type (it takes a Box<dyn Node>), keeping its
production dependency aura-core-only. bind_tap raises UndeclaredTap; duplicate
detection across binds is the caller's (the engine keeps no cross-call state),
mirroring how bind_sources' DuplicateFeed is a supply-level fault. TapBindError
is exported for the CLI. A bound tap records exactly the producer's per-cycle
output through the full public pipeline; an undeclared name is refused without
mutating the graph.

refs #282
2026-07-17 23:37:54 +02:00
claude f3daded514 feat(engine): compile resolves and hoists declared taps (#282)
The compile core. resolve_tap_wire resolves a blueprint Tap's interior
{node, field} through the lowering table exactly as OutField re-exports and
edges resolve — a leaf to its remapped index (output-arity checked), a nested
composite through its output remap. Taps are terminal (not re-exported through
the boundary): a flat_taps accumulator threads through the lowering recursion,
so an interior composite's taps hoist to the root FlatGraph.taps with remapped
wires — the case the CLI single-run wrapper relies on. validate_wiring gains a
tap leg (threaded at all three call sites incl. GraphSession::finish) and
CompileError::TapWireOutOfRange names a bad wire. Root-resolve, out-of-arity,
and nested-hoist are pinned green.

refs #282
2026-07-17 23:18:00 +02:00
claude 17e125a90a feat(engine): FlatTap type + FlatGraph.taps field (#282)
Second slice: the resolved-tap carrier. FlatTap { name, node, field } is the
output-side twin of SourceSpec — its name survives compile, load-bearing for
by-name binding (#275). FlatGraph gains a taps field, threaded (empty) through
every construction site the compiler enumerates. Bootstrap ignores it (taps
materialize as real recorder nodes/edges before bootstrap in a later slice).
Purely additive: the inert-producer pin and full suite stay green.

refs #282
2026-07-17 22:52:21 +02:00