Skip to content
Draft
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
2 changes: 2 additions & 0 deletions .github/workflows/quality-gates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ jobs:
- name: Run workflow-nodes tests
run: cargo test -p workflow-nodes --lib

- name: Run runtime registry contract tests
run: cargo test -p pantograph-runtime-registry
- name: Run task summary contract tests
run: |
cargo test -p pantograph-workflow-service --lib workflow::task_run_summary::tests:: -- --list > "$RUNNER_TEMP/task-summary-tests.list"
Expand Down
51 changes: 40 additions & 11 deletions crates/pantograph-runtime-registry/src/runtime_selection_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ pub(crate) fn select_runtime_technical_fit_automatically(
return RuntimeSelectionDecision::new(unselected_decision_with_device_diagnostics(
RuntimeTechnicalFitSelectionMode::Automatic,
Vec::new(),
vec![diagnostic],
vec![*diagnostic],
));
}
};
Expand Down Expand Up @@ -292,7 +292,7 @@ fn automatic_selection_policy_trace(
controlled_exploration_seed_basis: Option<&str>,
history_threshold_state: RuntimeTechnicalFitHistoryThresholdState,
history_ranking_enabled: bool,
) -> Result<RuntimeTechnicalFitSelectionPolicyTrace, RuntimeTechnicalFitDeviceDiagnostic> {
) -> Result<RuntimeTechnicalFitSelectionPolicyTrace, Box<RuntimeTechnicalFitDeviceDiagnostic>> {
let candidate_set_summary = automatic_candidate_set_summary(request, eligible_candidates)?;
Ok(RuntimeTechnicalFitSelectionPolicyTrace {
policy_version: TECHNICAL_FIT_SELECTION_POLICY_VERSION,
Expand Down Expand Up @@ -327,12 +327,12 @@ fn automatic_selection_policy_trace(
fn automatic_candidate_set_summary(
request: &RuntimeTechnicalFitRequest,
eligible_candidates: &[&RuntimeTechnicalFitCandidate],
) -> Result<RuntimeTechnicalFitCandidateSetSummary, RuntimeTechnicalFitDeviceDiagnostic> {
) -> Result<RuntimeTechnicalFitCandidateSetSummary, Box<RuntimeTechnicalFitDeviceDiagnostic>> {
let total_candidate_count = checked_candidate_count(request.candidates.len())?;
let eligible_candidate_count = checked_candidate_count(eligible_candidates.len())?;
let rejected_candidate_count = total_candidate_count
.checked_sub(eligible_candidate_count)
.ok_or_else(candidate_summary_count_diagnostic)?;
.ok_or_else(|| Box::new(candidate_summary_count_diagnostic()))?;

Ok(RuntimeTechnicalFitCandidateSetSummary {
total_candidate_count,
Expand All @@ -346,8 +346,8 @@ fn automatic_candidate_set_summary(
.normalized())
}

fn checked_candidate_count(count: usize) -> Result<u32, RuntimeTechnicalFitDeviceDiagnostic> {
u32::try_from(count).map_err(|_| candidate_summary_count_diagnostic())
fn checked_candidate_count(count: usize) -> Result<u32, Box<RuntimeTechnicalFitDeviceDiagnostic>> {
u32::try_from(count).map_err(|_| Box::new(candidate_summary_count_diagnostic()))
}

fn candidate_summary_count_diagnostic() -> RuntimeTechnicalFitDeviceDiagnostic {
Expand Down Expand Up @@ -822,7 +822,7 @@ fn resource_budget_diagnostic(
};
let reserved_bytes = match active_reserved_bytes(runtime_snapshot, resource_kind) {
Ok(reserved_bytes) => reserved_bytes,
Err(diagnostic) => return Some(diagnostic_context_from_candidate(diagnostic, candidate)),
Err(diagnostic) => return Some(diagnostic_context_from_candidate(*diagnostic, candidate)),
};
let Some(available_bytes) = after_safety_margin_bytes.checked_sub(reserved_bytes) else {
return Some(resource_budget_underflow_diagnostic(
Expand Down Expand Up @@ -871,7 +871,7 @@ fn resource_budget_diagnostic(
fn active_reserved_bytes(
runtime_snapshot: &RuntimeRegistryRuntimeSnapshot,
resource_kind: RuntimeAdmissionResourceKind,
) -> Result<u64, RuntimeTechnicalFitDeviceDiagnostic> {
) -> Result<u64, Box<RuntimeTechnicalFitDeviceDiagnostic>> {
let mut reserved_bytes = 0_u64;
for active_claim in &runtime_snapshot.active_reservation_claims {
for claim in active_claim
Expand All @@ -891,10 +891,10 @@ fn checked_add_claim_bytes(
resource_kind: RuntimeAdmissionResourceKind,
reserved_bytes: u64,
claim: &RuntimeReservationResourceClaim,
) -> Result<u64, RuntimeTechnicalFitDeviceDiagnostic> {
) -> Result<u64, Box<RuntimeTechnicalFitDeviceDiagnostic>> {
reserved_bytes
.checked_add(claim.bytes)
.ok_or_else(|| RuntimeTechnicalFitDeviceDiagnostic {
.ok_or_else(|| Box::new(RuntimeTechnicalFitDeviceDiagnostic {
code: RuntimeTechnicalFitDeviceDiagnosticCode::ResourceAccountingOverflow,
severity: RuntimeTechnicalFitDeviceDiagnosticSeverity::Error,
message: format!(
Expand All @@ -911,7 +911,7 @@ fn checked_add_claim_bytes(
model_id: None,
evidence_key: Some(resource_kind.resource_label().to_string()),
requested_runtime_key: None,
})
}))
}

fn resource_budget_underflow_diagnostic(
Expand Down Expand Up @@ -1132,3 +1132,32 @@ fn candidate_has_available_peak_memory_estimate(candidate: &RuntimeTechnicalFitC
&& estimate.value_bytes().is_some()
})
}

#[cfg(test)]
mod private_diagnostic_tests {
use super::checked_candidate_count;

#[test]
fn candidate_counts_preserve_exact_valid_boundaries() {
assert_eq!(checked_candidate_count(0).unwrap(), 0);
assert_eq!(
checked_candidate_count(u32::MAX as usize).unwrap(),
u32::MAX
);
}

#[test]
#[cfg(target_pointer_width = "64")]
fn candidate_count_overflow_preserves_exact_diagnostic_json() {
let diagnostic = checked_candidate_count(u32::MAX as usize + 1).unwrap_err();
let expected = serde_json::json!({
"code": "no_valid_candidate",
"severity": "error",
"message": "technical-fit candidate set is too large to summarize exactly"
});
assert_eq!(serde_json::to_value(diagnostic.as_ref()).unwrap(), expected);
let decoded: crate::technical_fit::RuntimeTechnicalFitDeviceDiagnostic =
serde_json::from_value(expected).unwrap();
assert_eq!(*diagnostic, decoded);
}
}
9 changes: 9 additions & 0 deletions crates/pantograph-runtime-registry/src/technical_fit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2563,6 +2563,15 @@ fn selector_reports_resource_accounting_overflow_before_selection() {

assert_eq!(decision.selected_candidate_id, None);
assert_eq!(decision.device_diagnostics.len(), 1);
assert_eq!(serde_json::to_value(&decision.device_diagnostics[0]).unwrap(), serde_json::json!({
"code": "resource_accounting_overflow",
"severity": "error",
"message": "technical-fit resource accounting overflowed while summing active ram_bytes claims for runtime 'runtime-overflow'",
"runtime_id": "runtime-overflow",
"backend_key": "pytorch",
"evidence_key": "ram_bytes",
"requested_runtime_key": "pytorch"
}));
assert_eq!(
decision.device_diagnostics[0].code,
RuntimeTechnicalFitDeviceDiagnosticCode::ResourceAccountingOverflow
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Private runtime selection diagnostic errors

Fresh PR #39 aggregate Clippy clears workflow-service and stops at five private runtime-registry Result paths whose error is the same 224-byte RuntimeTechnicalFitDeviceDiagnostic DTO. Box only these errors in automatic_selection_policy_trace, automatic_candidate_set_summary, checked_candidate_count, active_reserved_bytes and checked_add_claim_bytes. Unwrap at the existing automatic decision-vector and resource-budget context adapters. Preserve public signatures, the raw DTO, diagnostic fields/JSON, ranking/accounting decisions and success values.

Boundary tests retain zero/u32-max candidate counts and exact overflow diagnostic JSON (the usize overflow case is explicitly a 64-bit test). The existing public selector resource-overflow test now checks the entire contextualized payload, while full registry tests retain successful selection, budget and underflow behavior. Only failing paths add a box allocation. No broad Result or public layout change is made.

Root approved this bounded registry branch using the existing checkout. Root source review accepted all four files at frozen tree e88e208b5567da1dda64974dd1c65bb4524d5801. Hosted full-crate/aggregate qualification remains pending; no local Rust execution is claimed.
Loading