Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions crates/convex-engine/RUNTIME.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,9 @@ A Live runtime resolves its pricing configuration and pinned references, with
their issuer, calendar, convention and rate-index records, once per accepted
activation, at the first snapshot that needs them, and reuses them at every tick.
All are exact pinned versions, so none changes while that selection holds, and each
snapshot still checks them at its own cutoff. Quotes, fixings and stored curve
revisions are read at every snapshot's cutoff. A Live quote does not read the active
snapshot still checks them at its own cutoff. Every tick's snapshot shares one copy of
the references, written as JSON once and checked once apart from the cutoff (D68).
Quotes, fixings and stored curve revisions are read at every snapshot's cutoff. A Live quote does not read the active
configuration: the runtime keeps the selection it accepted until the engine's
activation refresh or a restart applies another. An activation committed without
the engine therefore waits for one of those. Captured and Replay read the active
Expand Down
5 changes: 5 additions & 0 deletions crates/convex-engine/SNAPSHOTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ one nanosecond beyond it is stale. Reading a cache never renews its age.
Snapshot identity is `sha256:<64 lowercase hexadecimal digits>` over canonical
schema-1 content with domain `convex.snapshot.v1\0`, or complete recipe schema-2
content with domain `convex.snapshot.v2\0`. Original schema-1 bytes remain exact.
The references are one shared, immutable section (D68). Every snapshot of a Live
selection shares the section it pinned: its canonical JSON is written once, its checks
that read no cutoff run once, and each snapshot still checks it at its own cutoff. The
identity streams the same canonical bytes into the hash, so ids and stored snapshots
are unchanged.
See [complete recipe replay](../../docs/CURVE-SNAPSHOT-REPLAY.md). Unordered manifests are
normalized; duplicates, conflicting resource revisions, unknown fields and
out-of-scope inputs fail validation. Deserialization recomputes content identity
Expand Down
11 changes: 6 additions & 5 deletions crates/convex-engine/src/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ impl SnapshotAssembler {
configuration: convex_ports::storage::BondPricingConfig,
) -> Result<MarketSnapshot, TraitError> {
validate_request(request, events)?;
let references = self.references(request, &configuration).await?;
let references = SnapshotReferences::new(self.references(request, &configuration).await?);
let capture = capture_ms(request);
let mut quotes = IngestedQuotes::new()?;
for event in events {
Expand Down Expand Up @@ -290,7 +290,7 @@ impl SnapshotAssembler {
pub(crate) async fn pin(&self, request: &SnapshotAssembly) -> Result<PinnedInputs, TraitError> {
validate_request(request, &[])?;
let configuration = self.configuration(request).await?;
let references = self.references(request, &configuration).await?;
let references = SnapshotReferences::new(self.references(request, &configuration).await?);
Ok(PinnedInputs {
activation: activation_revision(request),
configuration_pin: request.configuration.clone(),
Expand Down Expand Up @@ -326,7 +326,7 @@ impl SnapshotAssembler {
&self,
request: &SnapshotAssembly,
configuration: convex_ports::storage::BondPricingConfig,
references: Vec<ResolvedBondReference>,
references: SnapshotReferences,
quotes: Vec<SnapshotQuote>,
) -> Result<MarketSnapshot, TraitError> {
let keys = required_fixings(request, &references)?;
Expand Down Expand Up @@ -406,13 +406,14 @@ impl IngestedQuotes {
/// The pricing configuration and pinned references of one selection, resolved once at
/// the cutoff of its first snapshot (D56). Every record is an exact pinned version, so
/// none changes while the selection holds; each snapshot still checks them at its own
/// cutoff (`MarketSnapshot::new`).
/// cutoff (`MarketSnapshot::new`). Every snapshot shares the one references section, so
/// its checks that read no cutoff and its canonical JSON are done once (D68).
pub(crate) struct PinnedInputs {
activation: Option<Version>,
configuration_pin: ConfigurationPin,
instruments: Vec<InstrumentPin>,
configuration: convex_ports::storage::BondPricingConfig,
references: Vec<ResolvedBondReference>,
references: SnapshotReferences,
}
impl PinnedInputs {
/// Whether these inputs were resolved for `request`'s activation, configuration and
Expand Down
2 changes: 1 addition & 1 deletion crates/convex-engine/tests/snapshots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ fn a_zero_prices_its_yield_measures_and_keeps_the_router_reason_for_curve_measur
})
.collect();
let mut content = fixture::content();
let reference = &mut content.references[0];
let reference = &mut content.references.make_mut()[0];
reference.record.bond = Some(bond);
reference.issuer = resources[0].clone();
reference.calendar = resources[1].clone();
Expand Down
2 changes: 1 addition & 1 deletion crates/convex-engine/tests/support/calibration_replay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub fn runtime_capture() -> SnapshotCalibration {
pub fn content(mut content: SnapshotContent, capture: SnapshotCalibration) -> SnapshotContent {
content.schema_version = 3;
// This standalone graph consumes no R0 bond reference or market mark.
content.references.clear();
content.references.make_mut().clear();
content.quotes.clear();
// Explicit fictional capture configuration, valid at each original fixture instant.
content.configuration.created_at = 0;
Expand Down
2 changes: 1 addition & 1 deletion crates/convex-ports/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ convex-core = { workspace = true }
# Trait definitions only - NO runtime deps (no reqwest, sqlx, redis, ...)
async-trait = "0.1"
serde = { workspace = true }
serde_json = { workspace = true, features = ["float_roundtrip"] }
serde_json = { workspace = true, features = ["float_roundtrip", "raw_value"] }
sha2 = "0.10.9"
thiserror = { workspace = true }
rust_decimal = { workspace = true }
Expand Down
61 changes: 47 additions & 14 deletions crates/convex-ports/src/reference_master.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,51 @@ pub fn validate_resolved_bond(
}
}

/// The checks of [`validate_resolved_bond`] that read `lookup`: the record was recorded
/// by its cutoff, its terms are effective at its instant, and so is every resource,
/// each recorded by the cutoff. Every other check reads the reference alone, so a
/// reference that passed [`validate_resolved_bond`] at one lookup passes it at another
/// exactly when it passes this (D68).
pub fn validate_resolved_bond_at(
reference: &ResolvedBondReference,
lookup: ReferenceLookup,
) -> Result<(), ReferenceError> {
let terms = reference
.record
.bond
.as_ref()
.ok_or(ReferenceError::NotFound)?
.contractual_terms
.as_ref()
.ok_or(ReferenceError::InvalidInput("missing terms"))?;
if reference.record.recorded_at > lookup.known_at || !terms.effective.contains(lookup.as_of) {
return Err(ReferenceError::NotFound);
}
let floating = reference
.floating
.iter()
.flat_map(|floating| [&floating.index, &floating.calendar]);
[
&reference.issuer,
&reference.calendar,
&reference.convention,
]
.into_iter()
.chain(floating)
.try_for_each(|record| resource_in_scope(record, lookup))
}

/// A resource record known by the lookup's cutoff and effective at its instant.
fn resource_in_scope(record: &MasterRecord, lookup: ReferenceLookup) -> Result<(), ReferenceError> {
if record.recorded_at > lookup.known_at || !record.definition.effective().contains(lookup.as_of)
{
return Err(ReferenceError::InvalidInput(
"reference resource outside effective/known-at scope",
));
}
Ok(())
}

/// A floating rate note the service values: floating terms without a cap or floor, and
/// explicit mechanics pinning its rate index.
pub fn is_valued_floating(bond: &BondReferenceData) -> bool {
Expand Down Expand Up @@ -1114,13 +1159,7 @@ pub fn validate_floating_resources(
.ok_or(ReferenceError::InvalidInput("missing floating terms"))?;
for record in [&floating.index, &floating.calendar] {
record.definition.validate()?;
if record.recorded_at > lookup.known_at
|| !record.definition.effective().contains(lookup.as_of)
{
return Err(ReferenceError::InvalidInput(
"reference resource outside effective/known-at scope",
));
}
resource_in_scope(record, lookup)?;
}
let (MasterDefinition::RateIndex(index), MasterDefinition::Calendar(calendar)) =
(&floating.index.definition, &floating.calendar.definition)
Expand Down Expand Up @@ -1205,13 +1244,7 @@ pub fn validate_service_resources(
}
for record in [issuer, calendar, convention] {
record.definition.validate()?;
if record.recorded_at > lookup.known_at
|| !record.definition.effective().contains(lookup.as_of)
{
return Err(ReferenceError::InvalidInput(
"reference resource outside effective/known-at scope",
));
}
resource_in_scope(record, lookup)?;
}
if let MasterDefinition::Calendar(definition) = &calendar.definition {
if definition.rule_version.as_str() != SUPPORTED_BOND_CALENDAR_RULES {
Expand Down
Loading
Loading