swactor/tests/mvp_system/weight_lifecycle_guarantees.rs

225 lines
8.1 KiB
Rust

//! Black-box contract tests for MVP stage-local weight lifecycle.
//!
//! These tests intentionally know only the public weight-work surface:
//!
//! - `ProvisionStage` assignment in
//! - artifact, parse, allocation, binding, and cache outcomes in
//! - `WeightsReady`, `StageReady`, and `StageFault` out
//!
//! They assert the guarantees in
//! `specs/mvp_system/weight_lifecycle_contract.md`.
use mvp_system::weight_lifecycle as weights;
// A valid assignment gives the stage exactly one layer range and one source.
// Tests vary only source or failure outcome so the assignment contract remains
// visible.
fn valid_assignment() -> weights::WeightAssignment {
weights::WeightAssignment {
run_id: weights::RunId(7),
stage_index: 1,
plan_layer_range: weights::LayerRange {
start: 12,
end_exclusive: 24,
},
assigned_layer_range: weights::LayerRange {
start: 12,
end_exclusive: 24,
},
source: weights::WeightSource::WholeGguf {
uri: "test://model.gguf".into(),
},
}
}
// Weight loading is intentionally opaque. The harness accepts public loader and
// worker outcomes and records only stage-visible events and commands.
fn new_weight_harness() -> weights::WeightLifecycleHarness {
weights::WeightLifecycleHarness::new(weights::NodeId(11))
}
// The success facts represent the observable prerequisites for WeightsReady:
// artifact bytes exist, the assigned layer range is valid, and the worker has
// loaded or bound that range.
fn successful_load_events() -> Vec<weights::WeightEvent> {
vec![
weights::WeightEvent::ArtifactAvailable {
bytes: weights::ArtifactBytes::Local,
},
weights::WeightEvent::LayerRangeValidated,
weights::WeightEvent::WorkerRangeBound,
]
}
// Each failure case maps one public loader or worker failure to the stable
// stage fault reason expected at the control boundary.
fn failure_cases() -> Vec<(weights::WeightEvent, weights::StageFaultReason)> {
vec![
(
weights::WeightEvent::DownloadFailed,
weights::StageFaultReason::WeightDownloadFailed,
),
(
weights::WeightEvent::ParseFailed,
weights::StageFaultReason::WeightParseFailed,
),
(
weights::WeightEvent::DeviceAllocationFailed,
weights::StageFaultReason::DeviceAllocationFailed,
),
(
weights::WeightEvent::BindingFailed,
weights::StageFaultReason::WeightBindingFailed,
),
(
weights::WeightEvent::InvalidLayerRange,
weights::StageFaultReason::InvalidLayerRange,
),
]
}
// This proves a stage receives weight source and exactly one assigned layer
// range from provisioning, validates it against the plan, and does not claim
// graph-visible ownership outside that range.
#[test]
fn assignment_is_stage_local_and_range_limited() {
// Start weight work from the provisioned assignment.
let mut harness = new_weight_harness();
harness.observe(weights::WeightEvent::Provisioned(valid_assignment()));
// The load command may use the physical source, but its graph-visible layer
// range must be the assigned range.
for command in harness.commands() {
if let weights::WeightCommand::LoadOrBindRange { range, .. } = command {
assert_eq!(
*range,
weights::LayerRange {
start: 12,
end_exclusive: 24,
}
);
}
}
// There must be no command claiming ownership of neighboring layers.
assert!(!harness.commands().iter().any(|command| {
matches!(
command,
weights::WeightCommand::AdvertiseLoadedLayerRange {
range,
..
} if range.start < 12 || range.end_exclusive > 24
)
}));
}
// This proves whole GGUF download, shard download, and cache use are physical
// mechanisms with the same public outcome: WeightsReady or StageFault.
#[test]
fn supported_physical_sources_have_same_visible_success_contract() {
// Exercise every supported source without asserting how bytes are obtained.
let sources = vec![
weights::WeightSource::WholeGguf {
uri: "test://model.gguf".into(),
},
weights::WeightSource::ShardSet {
uris: vec!["test://model.layers.12-24.gguf".into()],
},
weights::WeightSource::CachedArtifact {
cache_key: "model:layers:12-24".into(),
},
];
for source in sources {
// Install the source in an otherwise valid assignment.
let mut assignment = valid_assignment();
assignment.source = source;
let mut harness = new_weight_harness();
harness.observe(weights::WeightEvent::Provisioned(assignment));
// Drive the same public success facts for every source.
for event in successful_load_events() {
harness.observe(event);
}
// The system-visible success outcome is WeightsReady.
assert!(harness.events().iter().any(|event| {
matches!(
event,
weights::WeightLifecycleEvent::WeightsReady {
run_id: weights::RunId(7),
stage_index: 1,
}
)
}));
}
}
// This proves WeightsReady requires artifact availability, layer validation,
// and worker bind/load completion, and that WeightsReady precedes StageReady.
#[test]
fn weights_ready_requires_all_weight_facts_and_precedes_stage_ready() {
// Start from a valid assignment.
let mut harness = new_weight_harness();
harness.observe(weights::WeightEvent::Provisioned(valid_assignment()));
// Feed every success fact except the final one and prove no prefix is
// sufficient for WeightsReady.
let mut events = successful_load_events();
let final_event = events.pop().expect("fixture has final weight event");
for event in events {
harness.observe(event);
assert!(!harness.events().iter().any(|event| {
matches!(event, weights::WeightLifecycleEvent::WeightsReady { .. })
}));
}
// The final weight prerequisite emits WeightsReady.
harness.observe(final_event);
let weights_ready_pos = harness
.events()
.iter()
.position(|event| matches!(event, weights::WeightLifecycleEvent::WeightsReady { .. }))
.expect("WeightsReady must be emitted");
// StageReady may occur only after the StageController observes WeightsReady
// and the other local setup prerequisites.
harness.observe(weights::WeightEvent::OtherStagePrerequisitesReady);
let stage_ready_pos = harness
.events()
.iter()
.position(|event| matches!(event, weights::WeightLifecycleEvent::StageReady { .. }))
.expect("StageReady must be emitted after prerequisites");
assert!(weights_ready_pos < stage_ready_pos);
}
// This proves every weight failure source faults the stage and suppresses both
// WeightsReady and StageReady.
#[test]
fn weight_failures_emit_stage_fault_without_readiness() {
// Each failure source gets an isolated attempt.
for (failure_event, expected_reason) in failure_cases() {
let mut harness = new_weight_harness();
harness.observe(weights::WeightEvent::Provisioned(valid_assignment()));
// Deliver the public failure outcome from weight work.
harness.observe(failure_event);
// The stable fault reason must be observable.
assert!(harness.events().iter().any(|event| {
matches!(
event,
weights::WeightLifecycleEvent::StageFault {
reason,
..
} if *reason == expected_reason
)
}));
// Readiness cannot also be emitted after a faulted weight attempt.
assert!(!harness.events().iter().any(|event| {
matches!(event, weights::WeightLifecycleEvent::WeightsReady { .. })
|| matches!(event, weights::WeightLifecycleEvent::StageReady { .. })
}));
}
}