Files
Aura/docs/plans/0029-run-registry-foundation.md
T
Brummel 8159b785a8 plan: 0029 run-registry foundation (iteration 1)
Iteration 1 of cycle D: amend C16 (blanket zero-dep -> per-case policy) in the
ledger + the five source comments asserting the struck framing; add serde/
serde_json workspace deps; derive Serialize/Deserialize on Timestamp and the
RunMetrics/RunManifest/RunReport report types, with serde_json round-trip tests.
No CLI change, no aura-registry crate, no SweepPoint change (those are iter 2/3).

refs #33
2026-06-10 20:05:20 +02:00

13 KiB

Run Registry — Iteration 1 (foundation) — Implementation Plan

Parent spec: docs/specs/0029-run-registry-sweep-family.md

For agentic workers: REQUIRED SUB-SKILL: use the implement skill to run this plan. Steps use - [ ] checkboxes for tracking.

Goal: Lay the dependency-policy + serde foundation the run registry builds on: amend C16, add serde/serde_json to the workspace, derive Serialize/Deserialize on Timestamp + the run-report types, with round-trip tests — no CLI change, no new crate, no SweepPoint change yet.

Architecture: C16's blanket zero-dependency clause is struck for a per-case policy (ledger + the five source comments that assert the old framing). serde lands as a workspace dependency; Timestamp (aura-core) and RunMetrics/RunManifest/RunReport (aura-engine) gain serde derives, proven by serde_json round-trip tests. The hand-rolled to_json writers are untouched.

Tech Stack: serde (derive) + serde_json (test-only), aura-core, aura-engine, the design ledger.


Files this plan creates or modifies:

  • Modify: docs/design/INDEX.md:535-565 — amend C16 (strike blanket zero-dep / firewall; add per-case policy)
  • Modify: crates/aura-ingest/Cargo.toml:10-12 — rewrite the firewall/zero-dep comment
  • Modify: crates/aura-ingest/src/lib.rs:10-12 — rewrite the firewall/zero-dep module doc
  • Modify: Cargo.toml (root) — add [workspace.dependencies] (serde + serde_json)
  • Modify: crates/aura-core/Cargo.toml:8serde dep + serde_json dev-dep
  • Modify: crates/aura-core/src/scalar.rs:7 — add serde derives to Timestamp
  • Test: crates/aura-core/src/scalar.rs (tests mod ~69-111) — timestamp_serde_round_trips
  • Modify: crates/aura-engine/Cargo.toml:8-18serde dep, rewrite comment, serde_json dev-dep
  • Modify: crates/aura-engine/src/report.rs:6-13,14,33,52 — serde derives on the three report types + doc-comment fixes
  • Test: crates/aura-engine/src/report.rs (tests mod ~198-413) — runreport_serde_round_trips

Task 1: C16 amendment + stale-comment sweep (contract/docs, no test)

Files:

  • Modify: docs/design/INDEX.md:549-562

  • Modify: crates/aura-ingest/Cargo.toml:10-12

  • Modify: crates/aura-ingest/src/lib.rs:10-12

  • Step 1: Amend the C16 body in the ledger

In docs/design/INDEX.md, replace this exact block (lines 551-557, from "aura-ingest is the" through "firewalled there."):

data-source ingestion edge, cycle 0011). `aura-ingest` is the
**external-dependency firewall**: it alone links external crates (via
`data-server`), keeping the other engine crates and the frozen deploy artifact
(C13) dependency-pure (see External components). The engine workspace is
**zero-external-dependency** by commitment — the hand-rolled JSON of C14/C18 and
the from-scratch `aura-core` substrate exist precisely to honour it; the one
sanctioned external tree enters at the ingestion edge and is firewalled there.

with:

data-source ingestion edge, cycle 0011, where the `data-server` external tree
enters). **Dependency policy (amended 2026-06-10, cycle 0029).** Dependencies
are admitted by deliberate, **per-case review** — what a crate pulls in weighed
against what it buys — with **particular scrutiny for anything that enters the
frozen deploy artifact** (C13: this bot = this commit). Well-established
standard crates (`serde`, `rayon`, …) pass that review and are used wherever
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.
  • Step 2: Strike the dependency clause from C16 Forbids

In the same file, in the C16 **Forbids.** sentence (lines 558-562), replace:

package mechanism); an external (crates.io / git) dependency in any engine crate
other than the ingestion-edge crate `aura-ingest`.

with:

package mechanism). (Dependency admission is governed by the per-case policy
above, not a blanket ban.)
  • Step 3: Rewrite the aura-ingest Cargo.toml comment

In crates/aura-ingest/Cargo.toml, replace lines 10-12:

# data-server: aura's first real data source AND its one external dependency
# (transitively chrono + regex + zip). Isolated in this crate — the firewall
# that keeps aura-core/std/engine zero-external-dep (spec §Architecture).

with:

# data-server: aura's first real data source. It enters the workspace at the
# ingestion edge (transitively chrono + regex + zip). Dependencies are admitted
# by the amended C16 per-case policy (INDEX.md), not a blanket zero-dep wall.
  • Step 4: Rewrite the aura-ingest module doc

In crates/aura-ingest/src/lib.rs, replace lines 10-12:

//! This crate is the workspace's **external-dependency firewall**: it is the
//! only crate that links `data-server` (and its transitive `chrono`/`regex`/
//! `zip`), so `aura-core`/`aura-std`/`aura-engine` stay zero-external-dependency.

with:

//! This crate is the **data-source ingestion edge**: it links `data-server`
//! (and its transitive `chrono`/`regex`/`zip`) and normalizes it into aura's
//! columns here. Dependencies follow the amended C16 per-case policy (INDEX.md),
//! not a blanket zero-dependency wall.
  • Step 5: Verify no struck framing survives + build is intact

Run: rg -n "external-dependency firewall|zero-external-dependency by commitment" docs/ crates/ Expected: no matches (the only remaining zero-dependency mentions are the report.rs doc comments, fixed in Task 3).

Run: cargo build --workspace Expected: builds (doc/comment edits do not affect compilation), exit 0.


Task 2: workspace serde deps + Timestamp serde (RED-first)

Files:

  • Modify: Cargo.toml (root)

  • Modify: crates/aura-core/Cargo.toml:8

  • Modify: crates/aura-core/src/scalar.rs:7

  • Test: crates/aura-core/src/scalar.rs (tests mod)

  • Step 1: Declare the workspace dependencies

In the root Cargo.toml, after the [workspace.package] block (after line 23, publish = false), append:


[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
  • Step 2: Add serde to aura-core

In crates/aura-core/Cargo.toml, under [dependencies] (line 8), add the dep and a dev-dep block:

[dependencies]
serde = { workspace = true }

[dev-dependencies]
serde_json = { workspace = true }
  • Step 3: Write the failing round-trip test

In crates/aura-core/src/scalar.rs, inside #[cfg(test)] mod tests (after the use super::*; at line 71), add:

    #[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);
    }
  • Step 4: Run the test to verify it fails

Run: cargo test -p aura-core timestamp_serde_round_trips Expected: FAIL — compile error the trait bound Timestamp: serde::Serialize is not satisfied (the derive is not there yet).

  • Step 5: Add the serde derives to Timestamp

In crates/aura-core/src/scalar.rs, replace line 7:

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]

with:

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)]
  • Step 6: Run the test to verify it passes

Run: cargo test -p aura-core timestamp_serde_round_trips Expected: PASS (1 passed).


Task 3: aura-engine serde + report types + doc fix (RED-first)

Files:

  • Modify: crates/aura-engine/Cargo.toml:8-18

  • Modify: crates/aura-engine/src/report.rs:6-13,14,33,52

  • Test: crates/aura-engine/src/report.rs (tests mod)

  • Step 1: Add serde to aura-engine + rewrite the dependency-purity comment

In crates/aura-engine/Cargo.toml, replace the [dependencies] body (lines 9-12):

aura-core = { path = "../aura-core" }
# No external dependencies: aura-engine is an engine crate and stays
# dependency-pure. The data-server ingestion edge lives in the aura-ingest crate
# (the external-dependency firewall — cycle 0011, INDEX.md C16), never here.

with:

aura-core = { path = "../aura-core" }
# serde is admitted under the amended C16 per-case dependency policy (INDEX.md):
# it gives the run-report types a typed (de)serialization path for the run
# registry (cycle 0029). serde's output is deterministic (C1-safe).
serde = { workspace = true }

Then in the same file's [dev-dependencies] block (after aura-std at line 15/18), add:

serde_json = { workspace = true }
  • Step 2: Write the failing round-trip test

In crates/aura-engine/src/report.rs, inside #[cfg(test)] mod tests (anywhere after the use lines at 200-204; place it right before the closing } of the module), add:

    #[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);
    }
  • Step 3: Run the test to verify it fails

Run: cargo test -p aura-engine runreport_serde_round_trips Expected: FAIL — compile error the trait bound RunReport: serde::Serialize is not satisfied (derives not there yet).

  • Step 4: Add the serde derives to the three report types

In crates/aura-engine/src/report.rs, replace each of these three derive lines:

  • line 14 (above pub struct RunMetrics): #[derive(Clone, Debug, PartialEq)]
  • line 33 (above pub struct RunManifest): #[derive(Clone, Debug, PartialEq)]
  • line 52 (above pub struct RunReport): #[derive(Clone, Debug, PartialEq)]

each with:

#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
  • Step 5: Run the test to verify it passes

Run: cargo test -p aura-engine runreport_serde_round_trips Expected: PASS (1 passed).

  • Step 6: Fix the now-false zero-dependency doc comments

In crates/aura-engine/src/report.rs, replace the module-doc tail (lines 6-8):

//! and folds them here. Output is canonical, hand-rolled JSON (C14): the schema
//! is tiny, closed, and flat, so the deliberately zero-dependency workspace
//! stays zero-dependency.

with:

//! and folds them here. Output is canonical JSON (C14): the schema is tiny,
//! closed, and flat. `to_json` is a hand-rolled writer; the report types also
//! derive serde (cycle 0029) for the run registry's typed read-path.

and replace the to_json doc tail (lines 11-13):

    /// Render canonical, machine-readable JSON (C14). Hand-rolled — the schema
    /// is tiny, closed, and flat, so the deliberately zero-dependency workspace
    /// stays so.

with:

    /// Render canonical, machine-readable JSON (C14). Hand-rolled — the schema
    /// is tiny, closed, and flat. (The same types also derive serde for the run
    /// registry's typed read-path, cycle 0029; this writer is kept for stdout.)
  • Step 7: Full workspace gate

Run: cargo test --workspace Expected: PASS — all tests green, including timestamp_serde_round_trips and runreport_serde_round_trips.

Run: cargo clippy --workspace --all-targets -- -D warnings Expected: no warnings, exit 0.

Run: rg -n "zero-dependency|zero-external-dep|external-dependency firewall" docs/ crates/ Expected: no matches — every assertion of the struck framing is gone.