diff --git a/docs/specs/fieldtest-milestone-walking-skeleton.md b/docs/specs/fieldtest-milestone-walking-skeleton.md new file mode 100644 index 0000000..ef09546 --- /dev/null +++ b/docs/specs/fieldtest-milestone-walking-skeleton.md @@ -0,0 +1,183 @@ +# Fieldtest — milestone "Walking skeleton" — 2026-06-04 + +**Status:** Draft — awaiting orchestrator triage +**Author:** fieldtester (dispatched by fieldtest skill, milestone-scope variant) +**Scope mode:** MILESTONE (closing functional gate), not per-cycle. + +## Scope + +The "Walking skeleton" milestone (Gitea Brummel/Aura issues #4–#8) promises a +runnable end-to-end thread (design ledger INDEX.md ~line 20): **ingest → one +signal (SMA-cross) → exposure/intent stream → sim-optimal broker → synthetic +pip-equity signal-quality metric**, foldable into a structured RunReport +(manifest + metrics, C18/C14), deterministic (C1), reachable via the `aura run` +CLI (#8), and — per C22 — populated enough that "a newcomer sees a populated +trace immediately." + +This fieldtest derives three end-to-end scenarios top-down from that promise and +drives them from the **public interface only** (the `aura` binary's observable +behaviour, rustdoc, INDEX.md, public re-exports, data-server's public rustdoc). +No `crates/*/src` was read. The artefact under test was built from HEAD +(`cargo build -p aura-cli`; the consumer crate compiled against the current +path-dep'd engine crates). + +## Examples + +### fieldtests/milestone-walking-skeleton/ms_1_newcomer_cli.sh — the newcomer CLI path +- Invokes the real `target/debug/aura` binary (built from HEAD) as a downstream + user with nothing but the rustdoc module blurb. Checks: single-line JSON + RunReport with the documented `{manifest, metrics}` shape; exit 0; + byte-identical across two runs (C1); a NON-degenerate trace (≥1 exposure sign + flip, C22); and the bad-args / usage contract. +- Fits the promise: this is the one-command surface the milestone says makes the + whole thread runnable, and the C22 "populated trace" demonstrator. +- Outcome: built? yes. ran? yes. matched expected? **yes** — single-line + `{manifest,metrics}`, exit 0, byte-identical, metrics + `total_pips=-0.13, max_drawdown=0.17, exposure_sign_flips=1` (populated). + Bad/empty subcommand → exit 2, `aura: usage: aura run` on stderr, empty stdout. + +### fieldtests/milestone-walking-skeleton/ms_2_real_data_thread.rs — the real-data thread +- A standalone consumer crate (own `[workspace]`, path-deps on + aura-core/std/engine/ingest + git dep on data-server — the shape a C16 project + takes) wires the WHOLE thread over real recorded M1 bars: + `load_m1_window(AAPL.US, 2006-08) → close_stream → SMA(2)/SMA(4) → Sub → + Exposure(0.5) → SimBroker(1e-4) → 2×Recorder → f64_field → summarize → + RunReport`, then folds the report twice and asserts determinism. +- Fits the promise: this is the literal ingest→…→metric thread over REAL bars, + the substance #7 + the milestone claim. +- Outcome: built? **yes, first try.** ran? yes (21 M1 bars ingested). matched + expected? **yes, and better than the carrier predicted** — the run is + deterministic (cross-process byte-identical RunReport, C1) AND **non-degenerate**: + `total_pips=-1.5, max_drawdown=65.5, exposure_sign_flips=6`. Window timestamps + are canonical epoch-ns. See finding [spec_gap] on the #19 premise. + +### fieldtests/milestone-walking-skeleton/ms_3_epoch_ns_coherence.rs — epoch-ns coherence +- Hand-builds 4 `M1Parsed` bars (data-server's public AoS record) with known + `time_ms`, runs `transpose_m1 → close_stream → SMA(2) → Recorder`, and asserts + the recorded sink-trace timestamps equal `unix_ms_to_epoch_ns(time_ms)` + (= ms·1e6) — the C3 normalization carried intact from ingest through the run + loop into the recorded metric stream. No external data needed. +- Fits the promise: closes the loop on C3 ("normalize at the one boundary") — + proves the metric stream a newcomer reads is in aura's canonical time unit. +- Outcome: built? yes. ran? yes. matched expected? **yes** — ingest stamps + `[…400e9, …460e9, …520e9, …580e9]`; sink trace is the warmed suffix in the same + epoch-ns stamps. + +## Findings + +### [working] The full real-data thread is authorable from the public interface and is deterministic + populated +- Example: ms_2_real_data_thread.rs. +- What happened: the entire ingest→signal→exposure→broker→metrics→RunReport thread + compiled on the first attempt from rustdoc signatures alone (Harness::bootstrap, + Edge/Target/SourceSpec field layout, summarize/f64_field, the aura-std node + ctors, load_m1_window/close_stream). Two in-process folds and two separate + processes produced byte-identical RunReports. +- Why working: the milestone's headline promise — a real ingest→metric thread, + deterministic (C1) — is delivered, and the public surface is self-sufficient + for a downstream author to wire it. This is the milestone's central win. +- Recommended action: carry-on. + +### [working] `aura run` delivers the C22 populated-trace newcomer path +- Example: ms_1_newcomer_cli.sh. +- What happened: one command, single-line `{manifest,metrics}` JSON, exit 0, + byte-identical twice, non-degenerate (1 sign flip, real drawdown). Bad/missing + subcommand → exit 2 + usage on stderr + empty stdout. +- Why working: C22 ("a newcomer sees a populated trace immediately") and C14 + (structured face from a binary) are both satisfied by the shipped binary. +- Recommended action: carry-on. + +### [working] C3 epoch-ns normalization is coherent end to end +- Example: ms_3_epoch_ns_coherence.rs. +- What happened: `close_stream` timestamps equal `unix_ms_to_epoch_ns(time_ms)`, + and the recorded sink trace carries exactly those epoch-ns stamps (warmed + suffix). ms·1e6 holds. +- Why working: C3's "one boundary normalizes, nowhere else" is observably true + through ingest → run → sink; the metric stream is in canonical time. +- Recommended action: carry-on. + +### [spec_gap] The #19 "AAPL.US too sparse → degenerate metrics" premise does NOT hold for the SMA(2)/SMA(4) demo config +- Example: ms_2_real_data_thread.rs. +- What happened: the carrier (and issue #19, from the cycle-0011 per-cycle + fieldtest) asserts AAPL.US 2006 is too sparse (~21 M1 bars/month) to warm an + SMA-cross, so the real-data run yields all-zero degenerate metrics. **Empirically + this is false for the SMA(2)/SMA(4) cross** (the same fast/slow pair the shipped + `aura run` sample uses): 21 bars warm SMA(4) after 4 bars, the cross produces + **6 exposure sign flips**, `total_pips=-1.5`, `max_drawdown=65.5` — a populated, + non-degenerate trace over REAL bars. The degeneracy #19 observed is + **window/param-dependent** (a deeper SMA, e.g. the longer lengths a realistic + daily cross would use, would not warm on 21 bars), not intrinsic to the symbol. +- Why spec_gap: the milestone never pins the "real-data demo" SMA lengths or + window, so two equally-plausible readings diverge on whether the real-data path + is degenerate. I picked the shipped `aura run` config (SMA 2/4); under it the + real-data thread is **populated**, not degenerate. The honest milestone roll-up + therefore differs from the carrier's pre-stated assumption: the real-data thread + is *also* a populated-trace demonstrator at the shipped param config, not merely + "correct but degenerate." #19's concern remains valid for *deeper* SMAs / the + C22 "name a denser demo symbol" recommendation, but it is not a blocker and the + milestone promise is met on real bars at the demo config. +- Recommended action: ratify (record that the walking-skeleton real-data demo is + pinned to SMA 2/4 over 2006-08 and is populated there) AND re-scope #19 to + "denser demo data for *deep/daily* crosses" rather than "real path is + degenerate" — the latter is no longer accurate. + +### [spec_gap] `aura run ` → exit 0, trailing args silently ignored +- Example: ms_1_newcomer_cli.sh (`aura run extra junk` → exit 0, full report). +- What happened: the arg parser keys only on the first token being `run` and + ignores everything after. `aura run garbage` runs the sample harness and exits 0. +- Why spec_gap: re-confirmation at the milestone gate of the cycle-0010 finding + (already filed). The milestone's CLI contract does not unambiguously cover + `run `; the binary chose the lenient reading. Not a regression — same + behaviour as cycle-0010. +- Recommended action: ratify-or-tighten (decide whether trailing args reject with + exit 2 or are deliberately ignored). Low priority; carried from #?/cycle-0010. + +### [friction] `aura --help` lands on the error path (exit 2, bare usage line) +- Example: ms_1_newcomer_cli.sh (`aura --help` → exit 2, `aura: usage: aura run` + on stderr, empty stdout). +- What happened: a newcomer's reflex first command (`--help`) is treated as an + unknown subcommand: exit 2, no help text, just the one-line usage on stderr. +- Why friction: the task (discover how to run) completes — the usage line *does* + name `aura run` — but the conventional discovery affordance returns a nonzero + error path rather than help on stdout. For a "newcomer sees a populated trace" + milestone (C22), the first-contact ergonomics are slightly off. +- Recommended action: plan (tidy) — make `--help`/`-h` print the usage to stdout + with exit 0; keep unknown subcommands at exit 2. Trivial, optional. + +### [friction] SimBroker slot order is load-bearing, both f64, unchecked at bootstrap +- Example: ms_2_real_data_thread.rs (had to consult rustdoc to wire slot 0 = + exposure, slot 1 = price correctly). +- What happened: SimBroker's two inputs are both `f64`, so a swapped wiring is not + caught at bootstrap (the rustdoc explicitly warns it "would silently produce a + wrong equity curve"). The downstream author must read prose to get it right; + there is no typed guard. +- Why friction: the thread is wireable correctly *only* by reading the rustdoc + carefully — a known, documented footgun, but a footgun. Not a bug (it is + documented and the wiring API offers no kind to distinguish the two f64 slots). +- Recommended action: plan (consider a named-role or distinct-newtype wiring for + broker inputs in a later cycle); or carry-on if deemed acceptable given the + rustdoc warning. Documented, so low urgency. + +## Recommendation summary + +| Finding | Class | Action | +|---|---|---| +| Real-data thread authorable, deterministic, populated | working | carry-on | +| `aura run` C22 populated-trace newcomer path | working | carry-on | +| C3 epoch-ns coherence ingest→run→sink | working | carry-on | +| #19 "real path degenerate" false at SMA 2/4 demo config | spec_gap | ratify + re-scope #19 | +| `aura run ` → exit 0 (lenient) | spec_gap | ratify-or-tighten | +| `aura --help` → exit 2, no help text | friction | plan (tidy) | +| SimBroker slot order load-bearing, unchecked | friction | plan / carry-on | + +## Milestone roll-up + +**The "Walking skeleton" milestone DELIVERS its promise end to end.** The full +thread — ingest → SMA-cross → exposure → sim-optimal broker → pip-equity +metric → structured RunReport — runs deterministically (C1, cross-process +byte-identical), in canonical epoch-ns time (C3), reachable via `aura run` +(#8/C14), and produces a **populated** trace (C22) on **both** the synthetic +sample harness AND the real AAPL.US 2006-08 bars at the shipped SMA(2)/SMA(4) +config. The carrier's pre-stated worry — that the real-data path is correct-but- +degenerate — did not reproduce at the demo configuration; #19's degeneracy is a +deep-SMA / dense-data concern, not a milestone failure. No bugs found. Two +spec_gaps and two friction items, none blocking. diff --git a/fieldtests/milestone-walking-skeleton/Cargo.lock b/fieldtests/milestone-walking-skeleton/Cargo.lock new file mode 100644 index 0000000..e2c148f --- /dev/null +++ b/fieldtests/milestone-walking-skeleton/Cargo.lock @@ -0,0 +1,938 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "aura-core" +version = "0.1.0" + +[[package]] +name = "aura-engine" +version = "0.1.0" +dependencies = [ + "aura-core", +] + +[[package]] +name = "aura-ingest" +version = "0.1.0" +dependencies = [ + "aura-core", + "data-server", +] + +[[package]] +name = "aura-std" +version = "0.1.0" +dependencies = [ + "aura-core", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bzip2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +dependencies = [ + "bzip2-sys", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.2.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "data-server" +version = "0.1.0" +source = "git+http://192.168.178.103:3000/Brummel/data-server.git?branch=main#0f5e6655b90eeab0f43a8b0f330b8d1632e80ed4" +dependencies = [ + "chrono", + "regex", + "zip", +] + +[[package]] +name = "deflate64" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "log" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "ms-walking-skeleton-fieldtest" +version = "0.0.0" +dependencies = [ + "aura-core", + "aura-engine", + "aura-ingest", + "aura-std", + "data-server", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "getrandom", + "hmac", + "indexmap", + "lzma-rs", + "memchr", + "pbkdf2", + "sha1", + "thiserror", + "time", + "xz2", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/fieldtests/milestone-walking-skeleton/Cargo.toml b/fieldtests/milestone-walking-skeleton/Cargo.toml new file mode 100644 index 0000000..053d644 --- /dev/null +++ b/fieldtests/milestone-walking-skeleton/Cargo.toml @@ -0,0 +1,31 @@ +# Milestone fieldtest consumer crate — a standalone downstream project (as a C16 +# project would be): path-deps on the engine crates + a cargo git dep on +# data-server, exactly the shape aura-ingest's own manifest takes. +# +# It drives the full walking-skeleton thread from the PUBLIC interface only: +# load_m1_window -> close_stream -> SMA-cross -> Exposure -> SimBroker +# -> Recorder sinks -> f64_field -> summarize -> RunReport. +# Empty [workspace] table: marks this fixture crate as its OWN workspace root, +# so it does not become a member of the aura engine workspace. +[workspace] + +[package] +name = "ms-walking-skeleton-fieldtest" +edition = "2024" +version = "0.0.0" +publish = false + +[[bin]] +name = "real_data_thread" +path = "ms_2_real_data_thread.rs" + +[[bin]] +name = "coherence" +path = "ms_3_epoch_ns_coherence.rs" + +[dependencies] +aura-core = { path = "../../crates/aura-core" } +aura-std = { path = "../../crates/aura-std" } +aura-engine = { path = "../../crates/aura-engine" } +aura-ingest = { path = "../../crates/aura-ingest" } +data-server = { git = "http://192.168.178.103:3000/Brummel/data-server.git", branch = "main" } diff --git a/fieldtests/milestone-walking-skeleton/ms_1_newcomer_cli.sh b/fieldtests/milestone-walking-skeleton/ms_1_newcomer_cli.sh new file mode 100755 index 0000000..61dabed --- /dev/null +++ b/fieldtests/milestone-walking-skeleton/ms_1_newcomer_cli.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Milestone fieldtest — scenario 1: THE NEWCOMER CLI PATH +# +# A downstream user with nothing but the public docs (aura's rustdoc module +# blurb: "aura run bootstraps a built-in sample signal-quality harness ... +# prints the run's metrics + manifest as canonical JSON to stdout") invokes the +# real binary and checks the milestone promise end to end: +# - a single-line JSON RunReport with the documented {manifest, metrics} shape +# - exit 0 +# - byte-identical across two runs (C1 determinism) +# - a NON-degenerate populated trace (C22 "newcomer sees a populated trace") +# - the bad-args / usage contract +# +# Run from the repo root. Builds from HEAD first (never a stale artifact). +set -u +cd "$(git rev-parse --show-toplevel)" +cargo build -q -p aura-cli || { echo "BUILD FAILED"; exit 1; } +BIN=target/debug/aura + +fail=0 + +A=$("$BIN" run); ea=$? +B=$("$BIN" run); eb=$? + +echo "report: $A" +[ "$ea" -eq 0 ] && [ "$eb" -eq 0 ] || { echo "FAIL: run exit $ea/$eb (want 0)"; fail=1; } +[ "$A" = "$B" ] || { echo "FAIL: two runs differ (C1)"; fail=1; } +# single line: no embedded newline +[ "$(printf '%s' "$A" | wc -l)" -eq 0 ] || { echo "FAIL: not single-line"; fail=1; } +# documented shape +case "$A" in + *'"manifest"'*'"metrics"'*'"total_pips"'*'"max_drawdown"'*'"exposure_sign_flips"'*) ;; + *) echo "FAIL: missing documented {manifest,metrics} keys"; fail=1;; +esac +# NON-degenerate: at least one of the metrics is non-zero (a populated trace). +# The synthetic sample stream rises then reverses -> 1 sign flip, real drawdown. +case "$A" in + *'"exposure_sign_flips":0'*) echo "FAIL: degenerate trace (0 sign flips)"; fail=1;; +esac + +# bad-args contract: no subcommand and unknown subcommand -> exit 2, usage on +# stderr, empty stdout. +"$BIN" >/tmp/ms1_o 2>/tmp/ms1_e; e=$? +[ "$e" -eq 2 ] || { echo "FAIL: no-arg exit $e (want 2)"; fail=1; } +[ -s /tmp/ms1_o ] && { echo "FAIL: no-arg wrote stdout"; fail=1; } +grep -q usage /tmp/ms1_e || { echo "FAIL: no usage on stderr"; fail=1; } + +"$BIN" frobnicate >/tmp/ms1_o 2>/tmp/ms1_e; e=$? +[ "$e" -eq 2 ] || { echo "FAIL: bad-subcmd exit $e (want 2)"; fail=1; } + +# OBSERVED ANOMALY (recorded, not asserted): `aura run extra junk` -> exit 0, +# trailing args silently ignored. Documented as a spec_gap in the spec. +"$BIN" run extra junk >/tmp/ms1_o 2>/dev/null; echo "run+junk exit: $? (observed lenient)" + +[ "$fail" -eq 0 ] && echo "SCENARIO 1: PASS" || echo "SCENARIO 1: FAIL" +exit "$fail" diff --git a/fieldtests/milestone-walking-skeleton/ms_2_real_data_thread.rs b/fieldtests/milestone-walking-skeleton/ms_2_real_data_thread.rs new file mode 100644 index 0000000..ef6af4d --- /dev/null +++ b/fieldtests/milestone-walking-skeleton/ms_2_real_data_thread.rs @@ -0,0 +1,160 @@ +//! Milestone fieldtest — scenario 2: THE REAL-DATA THREAD. +//! +//! A downstream author wires the WHOLE walking-skeleton promise over REAL +//! recorded M1 bars, using only the public interface: +//! +//! data-server -> aura_ingest::load_m1_window (ingest, C3 epoch-ns) +//! -> M1Columns::close_stream (SoA close column, C7) +//! -> SMA(fast) / SMA(slow) -> Sub (the SMA-cross signal) +//! -> Exposure (intent/exposure stream, C10) +//! -> SimBroker (sim-optimal pip equity, C10) +//! -> Recorder x2 (sink trace, C8/C22) +//! -> f64_field + summarize (RunMetrics, C12) +//! -> RunReport { manifest, metrics }(structured face, C14/C18) +//! +//! Then asserts determinism (C1): two independent runs over the same window +//! fold byte-identical RunReports. +//! +//! Window: AAPL.US, 2006-08 (the symbol/window the milestone shipped its +//! integration test against). Skips cleanly if /mnt/tickdata is absent. + +use std::sync::mpsc; + +use aura_core::{Firing, Scalar, ScalarKind, Timestamp}; +use aura_engine::{summarize, f64_field, Edge, Harness, RunManifest, RunMetrics, RunReport, SourceSpec, Target}; +use aura_ingest::{load_m1_window, M1Columns}; +use aura_std::{Exposure, Recorder, SimBroker, Sma}; +use data_server::{DataServer, DEFAULT_DATA_PATH}; + +const SYMBOL: &str = "AAPL.US"; +// 2006-08-01 .. 2006-09-01 UTC, inclusive Unix-ms (data-server's contract). +const FROM_MS: i64 = 1_154_390_400_000; +const TO_MS: i64 = 1_157_068_800_000; + +const SMA_FAST: usize = 2; +const SMA_SLOW: usize = 4; +const EXPOSURE_SCALE: f64 = 0.5; +const PIP_SIZE: f64 = 0.0001; + +/// Fold the recorded equity+exposure traces of one run into a RunReport. +fn run_once(prices: &[(Timestamp, Scalar)]) -> RunReport { + // Node array (indices are the wiring vocabulary): + // 0 SMA(fast) 1 SMA(slow) 2 Sub 3 Exposure + // 4 SimBroker 5 Recorder(equity) 6 Recorder(exposure) + let (eq_tx, eq_rx) = mpsc::channel::<(Timestamp, Vec)>(); + let (ex_tx, ex_rx) = mpsc::channel::<(Timestamp, Vec)>(); + + let nodes: Vec> = vec![ + Box::new(Sma::new(SMA_FAST)), + Box::new(Sma::new(SMA_SLOW)), + Box::new(aura_std::Sub::new()), + Box::new(Exposure::new(EXPOSURE_SCALE)), + Box::new(SimBroker::new(PIP_SIZE)), + Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, eq_tx)), + Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, ex_tx)), + ]; + + // One source: the close price. It feeds SMA(fast) slot 0, SMA(slow) slot 0, + // and SimBroker slot 1 (price). + let sources = vec![SourceSpec { + kind: ScalarKind::F64, + targets: vec![ + Target { node: 0, slot: 0 }, + Target { node: 1, slot: 0 }, + Target { node: 4, slot: 1 }, + ], + }]; + + let edges = vec![ + // Sub = SMA_fast - SMA_slow + Edge { from: 0, to: 2, slot: 0, from_field: 0 }, + Edge { from: 1, to: 2, slot: 1, from_field: 0 }, + // Exposure <- Sub + Edge { from: 2, to: 3, slot: 0, from_field: 0 }, + // SimBroker slot 0 = exposure + Edge { from: 3, to: 4, slot: 0, from_field: 0 }, + // equity sink <- SimBroker + Edge { from: 4, to: 5, slot: 0, from_field: 0 }, + // exposure sink <- Exposure + Edge { from: 3, to: 6, slot: 0, from_field: 0 }, + ]; + + let mut harness = Harness::bootstrap(nodes, sources, edges).expect("bootstrap"); + harness.run(vec![prices.to_vec()]); + drop(harness); // close the senders so the receivers drain to completion + + let equity_rows: Vec<(Timestamp, Vec)> = eq_rx.iter().collect(); + let exposure_rows: Vec<(Timestamp, Vec)> = ex_rx.iter().collect(); + + let equity = f64_field(&equity_rows, 0); + let exposure = f64_field(&exposure_rows, 0); + let metrics: RunMetrics = summarize(&equity, &exposure); + + let window = ( + prices.first().map(|p| p.0).unwrap_or(Timestamp(0)), + prices.last().map(|p| p.0).unwrap_or(Timestamp(0)), + ); + RunReport { + manifest: RunManifest { + commit: "fieldtest".into(), + params: vec![ + ("sma_fast".into(), SMA_FAST as f64), + ("sma_slow".into(), SMA_SLOW as f64), + ("exposure_scale".into(), EXPOSURE_SCALE), + ], + window, + seed: 0, + broker: format!("sim-optimal(pip_size={PIP_SIZE})"), + }, + metrics, + } +} + +fn main() { + if !std::path::Path::new(DEFAULT_DATA_PATH).exists() { + eprintln!("SKIP: {DEFAULT_DATA_PATH} absent"); + return; + } + let server = std::sync::Arc::new(DataServer::new(DEFAULT_DATA_PATH)); + if !server.has_symbol(SYMBOL) { + eprintln!("SKIP: data-server has no symbol {SYMBOL}"); + return; + } + + let cols: M1Columns = match load_m1_window(&server, SYMBOL, FROM_MS, TO_MS) { + Some(c) => c, + None => { + eprintln!("SKIP: no data in window for {SYMBOL}"); + return; + } + }; + let prices = cols.close_stream(); + eprintln!("ingested {} M1 close bars over [{FROM_MS},{TO_MS}]", prices.len()); + if let (Some(first), Some(last)) = (prices.first(), prices.last()) { + eprintln!("first ts (epoch-ns): {:?} last ts: {:?}", first.0, last.0); + } + + let r1 = run_once(&prices); + let r2 = run_once(&prices); + + let j1 = r1.to_json(); + let j2 = r2.to_json(); + println!("RunReport: {j1}"); + + // C1: determinism. + assert_eq!(j1, j2, "two runs over identical input must fold identical reports (C1)"); + // Validity: metrics are finite (degenerate-all-zero is valid per C2 warm-up). + assert!(r1.metrics.total_pips.is_finite(), "total_pips finite"); + assert!(r1.metrics.max_drawdown.is_finite() && r1.metrics.max_drawdown >= 0.0, "drawdown finite, >=0"); + + let m: &RunMetrics = &r1.metrics; + let degenerate = m.total_pips == 0.0 && m.max_drawdown == 0.0 && m.exposure_sign_flips == 0; + eprintln!( + "metrics: total_pips={} max_drawdown={} sign_flips={} ({})", + m.total_pips, + m.max_drawdown, + m.exposure_sign_flips, + if degenerate { "DEGENERATE (valid)" } else { "POPULATED" } + ); + println!("SCENARIO 2: PASS (deterministic, finite{})", if degenerate { ", degenerate" } else { ", populated" }); +} diff --git a/fieldtests/milestone-walking-skeleton/ms_3_epoch_ns_coherence.rs b/fieldtests/milestone-walking-skeleton/ms_3_epoch_ns_coherence.rs new file mode 100644 index 0000000..0d4975e --- /dev/null +++ b/fieldtests/milestone-walking-skeleton/ms_3_epoch_ns_coherence.rs @@ -0,0 +1,90 @@ +//! Milestone fieldtest — scenario 3: EPOCH-NS COHERENCE END TO END. +//! +//! Ties C3's one unit normalization (Unix-ms -> canonical epoch-ns) from the +//! ingestion boundary all the way through to the recorded sink trace that the +//! metric stream is folded from. The promise under test: the timestamps a +//! newcomer reads off a recorded trace are aura's canonical epoch-ns, derived +//! once at ingestion and carried unchanged through the run loop. +//! +//! Source records are hand-built `M1Parsed` (data-server's public AoS record — +//! exactly the shape a recorder / live-source edge materializes), with known +//! `time_ms`. We assert: +//! 1. close_stream timestamps == unix_ms_to_epoch_ns(time_ms) for each bar +//! (C3: the boundary normalizes; ms * 1_000_000). +//! 2. the Recorder sink trace timestamps (captured during the run) equal the +//! same epoch-ns values — the normalization survives ingest -> run -> sink. +//! +//! No external data needed: this is a pure-construction coherence check. + +use std::sync::mpsc; + +use aura_core::{Firing, Scalar, ScalarKind, Timestamp}; +use aura_engine::{Edge, Harness, SourceSpec, Target}; +use aura_ingest::{transpose_m1, unix_ms_to_epoch_ns}; +use aura_std::{Recorder, Sma}; +use data_server::records::M1Parsed; + +fn bar(time_ms: i64, close: f64) -> M1Parsed { + M1Parsed { time_ms, open: close, high: close, low: close, close, spread: 0.0, volume: 1 } +} + +fn main() { + // Known minute timestamps (arbitrary but realistic Unix-ms, one minute apart). + let times_ms = [1_154_390_400_000_i64, 1_154_390_460_000, 1_154_390_520_000, 1_154_390_580_000]; + let bars: Vec = times_ms + .iter() + .enumerate() + .map(|(i, &t)| bar(t, 1.0 + i as f64)) + .collect(); + + // INGEST: AoS -> SoA, normalizing time at the one boundary (C3). + let cols = transpose_m1(&bars); + let prices = cols.close_stream(); + + // (1) close_stream carries epoch-ns, not Unix-ms. + let expected_ns: Vec = times_ms.iter().map(|&t| unix_ms_to_epoch_ns(t)).collect(); + let got_ns: Vec = prices.iter().map(|p| p.0).collect(); + assert_eq!(got_ns, expected_ns, "close_stream must carry canonical epoch-ns (C3)"); + // sanity: epoch-ns == ms * 1_000_000 + for (&ms, ns) in times_ms.iter().zip(&expected_ns) { + assert_eq!(ns.0, ms * 1_000_000, "epoch-ns is ms*1e6"); + } + eprintln!("ingest ts: {:?} (epoch-ns)", got_ns); + + // RUN: price -> SMA(2) -> Recorder. We do not need the full strategy here; + // a one-node-plus-sink graph is enough to prove the timestamp carries + // through the run loop into the recorded trace. + let (tx, rx) = mpsc::channel::<(Timestamp, Vec)>(); + let nodes: Vec> = vec![ + Box::new(Sma::new(2)), // 0 + Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)), // 1 + ]; + let sources = vec![SourceSpec { + kind: ScalarKind::F64, + targets: vec![Target { node: 0, slot: 0 }], + }]; + let edges = vec![Edge { from: 0, to: 1, slot: 0, from_field: 0 }]; + + let mut harness = Harness::bootstrap(nodes, sources, edges).expect("bootstrap"); + harness.run(vec![prices.clone()]); + drop(harness); + + let recorded: Vec<(Timestamp, Vec)> = rx.iter().collect(); + let recorded_ts: Vec = recorded.iter().map(|r| r.0).collect(); + eprintln!("sink ts: {:?} (epoch-ns)", recorded_ts); + + // (2) Every recorded timestamp is one of the ingested epoch-ns stamps + // (SMA(2) warms after the first bar, so the sink trace is a suffix of the + // ingested timeline — but every stamp it carries is the canonical epoch-ns). + assert!(!recorded_ts.is_empty(), "sink recorded at least one warmed cycle"); + for ts in &recorded_ts { + assert!( + expected_ns.contains(ts), + "recorded sink ts {ts:?} is not a canonical ingested epoch-ns stamp" + ); + } + // and they are strictly the warmed suffix, in order + assert_eq!(recorded_ts, expected_ns[1..].to_vec(), "sink trace = warmed suffix, in epoch-ns order"); + + println!("SCENARIO 3: PASS (epoch-ns coherent ingest -> run -> sink)"); +}