feat(engine): serde foundation + amend C16 dependency policy (cycle D / iter 1)
Iteration 1 of the run-registry cycle (#33): lay the dependency + serde foundation the registry's typed read-path needs, and amend the contract that forbade it. Contract change (load-bearing — C18/C16). C16's blanket "zero-external- dependency by commitment" + "aura-ingest is the sole external-dependency firewall" framing is struck and replaced by a considered, per-case dependency policy: dependencies are admitted by deliberate review (what a crate pulls in vs. what it buys), with particular scrutiny for anything entering the frozen deploy artifact (C13); standard vetted crates (serde, rayon, ...) pass that review and are used wherever they do the job, including in the bot; hand-rolling what a vetted crate does is the anti-pattern. C16's engine/project split + three-tier node reuse are unchanged. The five source comments that asserted the struck framing (aura-engine/aura-ingest Cargo.toml + aura-ingest/report.rs docs) are rewritten to match; no live source or ledger text still asserts it. Per-case justification for serde (the policy now requires one): it gives the run-report types a typed (de)serialization path — exactly what ranking/compare over stored runs needs, and what the writer-only hand-rolled JSON (C14) could never provide; closes the typed-read-path gap (#17). Its closure is tiny, ubiquitous, heavily audited, and its output deterministic (C1-safe). serde_json is test-only this iteration (dev-dependency); no production serde_json yet. Changes: [workspace.dependencies] serde (derive) + serde_json; serde derives on Timestamp (aura-core) and RunMetrics/RunManifest/RunReport (aura-engine), with serde_json round-trip tests (window renders as a [from,to] integer array via the transparent Timestamp newtype). The hand-rolled to_json writers are untouched (still drive stdout). No aura-registry crate, no SweepPoint change — those are iterations 2 and 3. Verified: cargo test --workspace green (incl. the two new round-trip tests); clippy --workspace --all-targets -D warnings clean; no surviving assertion of the struck zero-dep framing in live source or the ledger. refs #33
This commit is contained in:
Generated
+41
@@ -58,6 +58,10 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "aura-core"
|
name = "aura-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aura-engine"
|
name = "aura-engine"
|
||||||
@@ -65,6 +69,8 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aura-core",
|
"aura-core",
|
||||||
"aura-std",
|
"aura-std",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -418,6 +424,12 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.34"
|
version = "0.1.34"
|
||||||
@@ -597,6 +609,16 @@ version = "1.0.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_core"
|
name = "serde_core"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
@@ -617,6 +639,19 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.150"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@@ -898,6 +933,12 @@ dependencies = [
|
|||||||
"zstd",
|
"zstd",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zopfli"
|
name = "zopfli"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
|
|||||||
@@ -21,3 +21,7 @@ edition = "2024"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
license = "proprietary"
|
license = "proprietary"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
|||||||
@@ -6,3 +6,7 @@ license.workspace = true
|
|||||||
publish.workspace = true
|
publish.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
serde = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
/// Canonical engine time: epoch-nanoseconds UTC. Newtype over `i64` (C7).
|
/// Canonical engine time: epoch-nanoseconds UTC. Newtype over `i64` (C7).
|
||||||
/// `Default` (epoch 0) lets `Column<Timestamp>` pre-size its ring.
|
/// `Default` (epoch 0) lets `Column<Timestamp>` pre-size its ring.
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct Timestamp(pub i64);
|
pub struct Timestamp(pub i64);
|
||||||
|
|
||||||
/// The kind tag of a scalar / column, used for the edge-time type check (C7).
|
/// The kind tag of a scalar / column, used for the edge-time type check (C7).
|
||||||
@@ -108,4 +108,15 @@ mod tests {
|
|||||||
assert_eq!(a, b);
|
assert_eq!(a, b);
|
||||||
assert_eq!(s, a);
|
assert_eq!(s, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn timestamp_serde_round_trips() {
|
||||||
|
let ts = Timestamp(1_700_000_000_000_000_000);
|
||||||
|
let json = serde_json::to_string(&ts).expect("serialize Timestamp");
|
||||||
|
// a newtype struct serializes transparently as its inner i64 — this is
|
||||||
|
// what lets RunManifest's `window` render as a [from, to] integer array
|
||||||
|
assert_eq!(json, "1700000000000000000");
|
||||||
|
let back: Timestamp = serde_json::from_str(&json).expect("deserialize Timestamp");
|
||||||
|
assert_eq!(back, ts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ publish.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aura-core = { path = "../aura-core" }
|
aura-core = { path = "../aura-core" }
|
||||||
# No external dependencies: aura-engine is an engine crate and stays
|
# serde is admitted under the amended C16 per-case dependency policy (INDEX.md):
|
||||||
# dependency-pure. The data-server ingestion edge lives in the aura-ingest crate
|
# it gives the run-report types a typed (de)serialization path for the run
|
||||||
# (the external-dependency firewall — cycle 0011, INDEX.md C16), never here.
|
# registry (cycle 0029). serde's output is deterministic (C1-safe).
|
||||||
|
serde = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
aura-std = { path = "../aura-std" }
|
aura-std = { path = "../aura-std" }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
|||||||
@@ -3,15 +3,15 @@
|
|||||||
//! **post-run pure reduction** over a run's recorded streams — a node cannot
|
//! **post-run pure reduction** over a run's recorded streams — a node cannot
|
||||||
//! reduce end-of-run (C8 caps a node at one record per `eval`, with no terminal
|
//! reduce end-of-run (C8 caps a node at one record per `eval`, with no terminal
|
||||||
//! `eval`), so the World drains its recording sinks after [`Harness::run`](crate::Harness::run)
|
//! `eval`), so the World drains its recording sinks after [`Harness::run`](crate::Harness::run)
|
||||||
//! and folds them here. Output is canonical, hand-rolled JSON (C14): the schema
|
//! and folds them here. Output is canonical JSON (C14): the schema is tiny,
|
||||||
//! is tiny, closed, and flat, so the deliberately zero-dependency workspace
|
//! closed, and flat. `to_json` is a hand-rolled writer; the report types also
|
||||||
//! stays zero-dependency.
|
//! derive serde (cycle 0029) for the run registry's typed read-path.
|
||||||
|
|
||||||
use aura_core::{Scalar, Timestamp};
|
use aura_core::{Scalar, Timestamp};
|
||||||
|
|
||||||
/// Summary metrics reduced from a run's recorded streams — the `-> metrics`
|
/// Summary metrics reduced from a run's recorded streams — the `-> metrics`
|
||||||
/// half of C12's atomic sim unit. Pure function of the recorded streams.
|
/// half of C12's atomic sim unit. Pure function of the recorded streams.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct RunMetrics {
|
pub struct RunMetrics {
|
||||||
/// Final cumulative pip equity — the last value of the (cumulative)
|
/// Final cumulative pip equity — the last value of the (cumulative)
|
||||||
/// pip-equity curve. `0.0` if the curve is empty.
|
/// pip-equity curve. `0.0` if the curve is empty.
|
||||||
@@ -30,7 +30,7 @@ pub struct RunMetrics {
|
|||||||
/// The reproducible run descriptor (C18). **Caller-supplied**: the engine
|
/// The reproducible run descriptor (C18). **Caller-supplied**: the engine
|
||||||
/// cannot introspect a git commit, an RNG seed, or a broker label — the World
|
/// cannot introspect a git commit, an RNG seed, or a broker label — the World
|
||||||
/// that bootstraps and runs the harness fills these in.
|
/// that bootstraps and runs the harness fills these in.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct RunManifest {
|
pub struct RunManifest {
|
||||||
/// Node/engine identity: the git commit of the frozen artifact (C18 —
|
/// Node/engine identity: the git commit of the frozen artifact (C18 —
|
||||||
/// commit = identity; the frozen bot *is* a commit).
|
/// commit = identity; the frozen bot *is* a commit).
|
||||||
@@ -49,7 +49,7 @@ pub struct RunManifest {
|
|||||||
/// A run's full structured result: the descriptor plus the metrics it
|
/// A run's full structured result: the descriptor plus the metrics it
|
||||||
/// reproduces. The durable run record of C18 ("stores manifests + metrics,
|
/// reproduces. The durable run record of C18 ("stores manifests + metrics,
|
||||||
/// re-derives full results on demand").
|
/// re-derives full results on demand").
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct RunReport {
|
pub struct RunReport {
|
||||||
pub manifest: RunManifest,
|
pub manifest: RunManifest,
|
||||||
pub metrics: RunMetrics,
|
pub metrics: RunMetrics,
|
||||||
@@ -57,8 +57,8 @@ pub struct RunReport {
|
|||||||
|
|
||||||
impl RunReport {
|
impl RunReport {
|
||||||
/// Render canonical, machine-readable JSON (C14). Hand-rolled — the schema
|
/// Render canonical, machine-readable JSON (C14). Hand-rolled — the schema
|
||||||
/// is tiny, closed, and flat, so the deliberately zero-dependency workspace
|
/// is tiny, closed, and flat. (The same types also derive serde for the run
|
||||||
/// stays so.
|
/// registry's typed read-path, cycle 0029; this writer is kept for stdout.)
|
||||||
///
|
///
|
||||||
/// # Shape
|
/// # Shape
|
||||||
///
|
///
|
||||||
@@ -410,4 +410,27 @@ mod tests {
|
|||||||
r#"{"manifest":{"commit":"abc123","params":{"sma_fast":2,"sma_slow":4,"exposure_scale":1},"window":[1,6],"seed":0,"broker":"sim-optimal(pip_size=1.0)"},"metrics":{"total_pips":12,"max_drawdown":1,"exposure_sign_flips":1}}"#,
|
r#"{"manifest":{"commit":"abc123","params":{"sma_fast":2,"sma_slow":4,"exposure_scale":1},"window":[1,6],"seed":0,"broker":"sim-optimal(pip_size=1.0)"},"metrics":{"total_pips":12,"max_drawdown":1,"exposure_sign_flips":1}}"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn runreport_serde_round_trips() {
|
||||||
|
let report = RunReport {
|
||||||
|
manifest: RunManifest {
|
||||||
|
commit: "abc123".to_string(),
|
||||||
|
params: vec![
|
||||||
|
("sma_fast".to_string(), 2.0),
|
||||||
|
("sma_slow".to_string(), 4.0),
|
||||||
|
("exposure_scale".to_string(), 1.0),
|
||||||
|
],
|
||||||
|
window: (Timestamp(1), Timestamp(6)),
|
||||||
|
seed: 0,
|
||||||
|
broker: "sim-optimal(pip_size=1.0)".to_string(),
|
||||||
|
},
|
||||||
|
metrics: RunMetrics { total_pips: 12.0, max_drawdown: 1.0, exposure_sign_flips: 1 },
|
||||||
|
};
|
||||||
|
let json = serde_json::to_string(&report).expect("serialize RunReport");
|
||||||
|
// window is a 2-element [from, to] array (Timestamp newtype is transparent)
|
||||||
|
assert!(json.contains("\"window\":[1,6]"), "window shape: {json}");
|
||||||
|
let back: RunReport = serde_json::from_str(&json).expect("deserialize RunReport");
|
||||||
|
assert_eq!(back, report);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ publish.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aura-core = { path = "../aura-core" }
|
aura-core = { path = "../aura-core" }
|
||||||
# data-server: aura's first real data source AND its one external dependency
|
# data-server: aura's first real data source. It enters the workspace at the
|
||||||
# (transitively chrono + regex + zip). Isolated in this crate — the firewall
|
# ingestion edge (transitively chrono + regex + zip). Dependencies are admitted
|
||||||
# that keeps aura-core/std/engine zero-external-dep (spec §Architecture).
|
# by the amended C16 per-case policy (INDEX.md), not a blanket zero-dep wall.
|
||||||
data-server = { git = "http://192.168.178.103:3000/Brummel/data-server.git", branch = "main" }
|
data-server = { git = "http://192.168.178.103:3000/Brummel/data-server.git", branch = "main" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -7,9 +7,10 @@
|
|||||||
//! as the engine source-stream shape (`Vec<(Timestamp, Scalar)>`) the SMA-cross
|
//! as the engine source-stream shape (`Vec<(Timestamp, Scalar)>`) the SMA-cross
|
||||||
//! sample strategy consumes.
|
//! sample strategy consumes.
|
||||||
//!
|
//!
|
||||||
//! This crate is the workspace's **external-dependency firewall**: it is the
|
//! This crate is the **data-source ingestion edge**: it links `data-server`
|
||||||
//! only crate that links `data-server` (and its transitive `chrono`/`regex`/
|
//! (and its transitive `chrono`/`regex`/`zip`) and normalizes it into aura's
|
||||||
//! `zip`), so `aura-core`/`aura-std`/`aura-engine` stay zero-external-dependency.
|
//! columns here. Dependencies follow the amended C16 per-case policy (INDEX.md),
|
||||||
|
//! not a blanket zero-dependency wall.
|
||||||
|
|
||||||
use aura_core::{Scalar, Timestamp};
|
use aura_core::{Scalar, Timestamp};
|
||||||
use data_server::records::M1Parsed;
|
use data_server::records::M1Parsed;
|
||||||
|
|||||||
+14
-9
@@ -548,18 +548,23 @@ chosen strategy + broker freeze into a standalone binary. **A project is
|
|||||||
therefore always a Rust program built on the engine.** Beyond the node-reuse
|
therefore always a Rust program built on the engine.** Beyond the node-reuse
|
||||||
tiers, the engine workspace also carries non-node crates — `aura-engine` (the
|
tiers, the engine workspace also carries non-node crates — `aura-engine` (the
|
||||||
run loop), `aura-cli` (the `aura` binary), and **`aura-ingest`** (the
|
run loop), `aura-cli` (the `aura` binary), and **`aura-ingest`** (the
|
||||||
data-source ingestion edge, cycle 0011). `aura-ingest` is the
|
data-source ingestion edge, cycle 0011, where the `data-server` external tree
|
||||||
**external-dependency firewall**: it alone links external crates (via
|
enters). **Dependency policy (amended 2026-06-10, cycle 0029).** Dependencies
|
||||||
`data-server`), keeping the other engine crates and the frozen deploy artifact
|
are admitted by deliberate, **per-case review** — what a crate pulls in weighed
|
||||||
(C13) dependency-pure (see External components). The engine workspace is
|
against what it buys — with **particular scrutiny for anything that enters the
|
||||||
**zero-external-dependency** by commitment — the hand-rolled JSON of C14/C18 and
|
frozen deploy artifact** (C13: this bot = this commit). Well-established
|
||||||
the from-scratch `aura-core` substrate exist precisely to honour it; the one
|
standard crates (`serde`, `rayon`, …) pass that review and are used wherever
|
||||||
sanctioned external tree enters at the ingestion edge and is firewalled there.
|
they do the job, including in the bot. There is **no blanket zero-dependency
|
||||||
|
commitment** and **no blanket admission**; hand-rolling what a vetted standard
|
||||||
|
crate already does is the anti-pattern, not the dependency. This strikes the
|
||||||
|
original *"zero-external-dependency by commitment"* clause and the
|
||||||
|
*"`aura-ingest` is the sole external-dependency firewall"* framing; `aura-ingest`
|
||||||
|
remains the data-source ingestion edge, no longer a dependency wall.
|
||||||
**Forbids.** Project-specific signals in the aura repo (it keeps at most
|
**Forbids.** Project-specific signals in the aura repo (it keeps at most
|
||||||
example/fixture nodes under `examples/` for its own tests); a multi-project
|
example/fixture nodes under `examples/` for its own tests); a multi-project
|
||||||
manager inside aura; a bespoke node registry/marketplace (cargo + Gitea *is* the
|
manager inside aura; a bespoke node registry/marketplace (cargo + Gitea *is* the
|
||||||
package mechanism); an external (crates.io / git) dependency in any engine crate
|
package mechanism). (Dependency admission is governed by the per-case policy
|
||||||
other than the ingestion-edge crate `aura-ingest`.
|
above, not a blanket ban.)
|
||||||
**Why.** The engine/game split keeps the engine sharp and reusable while each
|
**Why.** The engine/game split keeps the engine sharp and reusable while each
|
||||||
project versions its own research with its own forward-queue. Promotion
|
project versions its own research with its own forward-queue. Promotion
|
||||||
(local → shared → std) is the ordinary Rust reuse gradient, no new mechanism.
|
(local → shared → std) is the ordinary Rust reuse gradient, no new mechanism.
|
||||||
|
|||||||
Reference in New Issue
Block a user