Files
Aura/crates/aura-engine/tests/position_event_table.rs
T
Brummel b91c89f964 feat(broker-foundation): InstrumentSpec deploy metadata + PositionEvent schema
The realistic-broker milestone's (C10 A-side) two foundation value types — no
broker, no derivation yet, fully tested.

#113 — InstrumentSpec gains six deploy-grade fields (instrument_id,
contract_size, pip_value_per_lot, min_lot, lot_step, quote_currency) and the
vetted table is populated for GER40/FRA40/EURUSD/GBPUSD/USDCAD. The struct stays
Copy (quote_currency is &'static str); the _ => None refuse-don't-guess arm is the
permanent floor. This is the milestone's permanent authored floor (recorded-metadata
tier is forward work, #124).

#114 — PositionAction { Buy, Sell, Close } (closed enum, serde-encoded as a bare
i64 0/1/2 via From/TryFrom) + PositionEvent in aura-engine beside RunMetrics,
faithful to the C10 column spec: event_ts, action, position_id, instrument_id,
unsigned volume; no open_ts; direction is the action (no signed-volume trick).
Re-exported from the crate root.

Derived fork decisions (recorded on #113): quote_currency &'static str; action as
ordinal i64; stable instrument_id + overridable vetted values.

Verified by the orchestrator: cargo test --workspace = 458 passed / 0 failed;
clippy --all-targets -D warnings clean. An adversarial 4-lens review (ledger
faithfulness, downstream soundness, serde/wire robustness, test honesty) returned
3 SOUND + 1 CONCERN: quote_currency &'static str cannot hold a runtime-sourced
currency at the #124 resolver — not a blocker (#115/#116 read only the authored
floor), logged on #124 as a deliberate deferred design choice. The implementer's
deviation from the plan snippet (vec![..] -> [..]) avoids clippy::useless_vec;
semantics identical.

Two tester-authored consumer-boundary E2E tests added beyond the inline units:
instrument_spec_deploy_metadata.rs (public resolution seam) and
position_event_table.rs (persisted bare-int wire shape + reversal table).

closes #113 #114
2026-06-22 19:51:17 +02:00

87 lines
4.2 KiB
Rust

//! End-to-end verification of the C10 derived position-event table (#114) at the
//! public crate boundary: the World consumes `PositionAction` / `PositionEvent`
//! via the `aura_engine` re-exports and persists the audit table the same way
//! the run registry persists a `RunReport` — through serde_json. These tests pin
//! the *consumer-visible wire shape* (the persisted column form), distinct from
//! the crate-internal type-level unit tests in `report.rs`.
use aura_engine::{PositionAction, PositionEvent, Timestamp};
/// A minimal stop-and-reverse audit table as a broker would derive it: open
/// long, then at one later instant Close-before-open into a short (a reversal,
/// two events sharing `event_ts`). The smallest table that exercises every
/// action variant and the same-ts reversal shape.
fn reversal_table() -> Vec<PositionEvent> {
let open = Timestamp(1_000);
let flip = Timestamp(2_000);
vec![
PositionEvent {
event_ts: open,
action: PositionAction::Buy,
position_id: 1,
instrument_id: 3,
volume: 0.10,
},
// reversal at one instant: close the long first, then open the short.
PositionEvent {
event_ts: flip,
action: PositionAction::Close,
position_id: 1,
instrument_id: 3,
volume: 0.10,
},
PositionEvent {
event_ts: flip,
action: PositionAction::Sell,
position_id: 2,
instrument_id: 3,
volume: 0.10,
},
]
}
/// Property: a derived position-event table survives a full serde_json
/// round-trip through the public re-exports byte-for-byte equal, AND the
/// persisted `action` column is a **bare integer** (C7-scalar / ledger column
/// shape), never a tagged-enum object. The World persists this audit table with
/// the same encoder it uses for `RunReport`; if `action` serialised as
/// `{"Sell":null}` it would break the flat columnar contract and any tool
/// reading the table as `action: i64`. Pins the consumer-visible on-disk shape.
#[test]
fn position_event_table_round_trips_with_bare_int_action_column() {
let table = reversal_table();
let json = serde_json::to_string(&table).expect("serialize audit table");
// action persists as a bare i64 (Buy=0, Sell=1, Close=2), not a tagged enum.
assert!(json.contains("\"action\":0"), "Buy must encode as action:0 — {json}");
assert!(json.contains("\"action\":1"), "Sell must encode as action:1 — {json}");
assert!(json.contains("\"action\":2"), "Close must encode as action:2 — {json}");
assert!(!json.contains("\"Buy\""), "action must not be a tagged enum — {json}");
let back: Vec<PositionEvent> = serde_json::from_str(&json).expect("deserialize audit table");
assert_eq!(back, table, "the audit table must round-trip byte-for-byte (C18 reproducibility)");
}
/// Property: the persisted `PositionAction` integer mapping is the **stable
/// wire encoding** `Buy=0, Sell=1, Close=2`, and any out-of-range integer is
/// **rejected** on read rather than silently coerced. This is the contract every
/// persisted audit table depends on: renumbering the variants (or accepting a
/// stray 3 as some action) would silently mis-read every historical table.
/// Exercised through the public re-export as a downstream reader would.
#[test]
fn position_action_wire_encoding_is_stable_and_validated() {
// forward: the literal i64 each variant persists as.
assert_eq!(serde_json::to_string(&PositionAction::Buy).unwrap(), "0");
assert_eq!(serde_json::to_string(&PositionAction::Sell).unwrap(), "1");
assert_eq!(serde_json::to_string(&PositionAction::Close).unwrap(), "2");
// backward: each valid code reads back to its variant.
assert_eq!(serde_json::from_str::<PositionAction>("0").unwrap(), PositionAction::Buy);
assert_eq!(serde_json::from_str::<PositionAction>("1").unwrap(), PositionAction::Sell);
assert_eq!(serde_json::from_str::<PositionAction>("2").unwrap(), PositionAction::Close);
// an out-of-range code is refused on read, not coerced (the validation arm).
assert!(serde_json::from_str::<PositionAction>("3").is_err());
assert!(serde_json::from_str::<PositionAction>("-1").is_err());
}