Add private complete mixed Project resolution with retained provenance

This commit is contained in:
2026-09-23 11:15:41 -07:00
parent a578ac98a0
commit 8f0d96ca63
8 changed files with 2135 additions and 32 deletions
+87 -5
View File
@@ -180,14 +180,96 @@ fn check_ids(out: &mut Vec<Issue>, owner: Id, field: &str, ids: &[Id]) {
}
pub fn validate(p: &Project) -> Vec<Issue> {
validate_with_usage(p).0
}
/// Runs the shared static/architectural route once and retains successful static
/// metadata without retaining a Project-borrowing environment. Architectural
/// issue production is outside the static meter and any caller auxiliary ledger;
/// callers must reserve separately before mapping or retaining these issues.
pub(crate) fn validate_with_usage(
p: &Project,
) -> (
Vec<Issue>,
Option<(
crate::analysis::AdmissionLimits,
crate::analysis::AdmissionUsage,
)>,
) {
// Bound analysis trees before architectural visitors can clone an element.
// Admission itself never enters this public validator recursively.
if let Err(failure) = crate::analysis::admit_structure_checked(p, crate::analysis::AdmissionLimits::REFERENCE) {
let mut out = Vec::new();
error(&mut out, failure.object, &failure.code, &failure.field, failure.message);
return out;
let environment = match crate::analysis::admit_structure_checked(
p,
crate::analysis::AdmissionLimits::REFERENCE,
) {
Ok(environment) => environment,
Err(failure) => {
let mut out = Vec::new();
error(
&mut out,
failure.object,
&failure.code,
&failure.field,
failure.message,
);
return (out, None);
}
};
let metadata = (
environment.admission_limits(),
environment.admission_usage(),
);
drop(environment);
(validate_structure(p), Some(metadata))
}
#[cfg(test)]
mod shared_admission_tests {
use super::*;
#[test]
fn successful_static_metadata_survives_architectural_warnings() {
let mut project = Project::empty();
project.name.clear();
let (issues, metadata) = validate_with_usage(&project);
let (limits, usage) = metadata.expect("static admission succeeds");
assert_eq!(limits, crate::analysis::AdmissionLimits::REFERENCE);
assert!(usage.static_events > 0);
assert_eq!(usage.stored_expression_nodes, 0);
assert!(issues.iter().any(|issue| issue.code == "unnamed"
&& issue.element == Some(project.id)
&& issue.severity == Severity::Warning));
}
#[test]
fn static_failure_returns_no_metadata_and_skips_architectural_warnings() {
use crate::analysis::{
AnalysisType, Binding, CalculationDefinition, Definition, DefinitionBody, ScalarType,
};
let mut project = Project::empty();
project.name.clear();
let key = Id::from_u128(10);
project.definitions.insert(
key,
Definition {
id: Id::from_u128(11),
alias: "f".into(),
name: "F".into(),
description: String::new(),
body: DefinitionBody::Calculation(CalculationDefinition {
parameters: Vec::new(),
result: AnalysisType::Scalar(ScalarType::Number),
body: Binding::Unbound,
}),
},
);
let (issues, metadata) = validate_with_usage(&project);
assert!(metadata.is_none());
assert_eq!(issues.len(), 1);
assert_eq!(issues[0].severity, Severity::Error);
assert_eq!(issues[0].element, Some(key));
assert_eq!(issues[0].code, "identity_mismatch");
}
validate_structure(p)
}
/// Architecture/header validation without recursively re-entering analysis admission.