fix: validate Soul plan adoption across model transition

This commit is contained in:
Meta Vens 2026-08-08 18:16:14 +01:00
commit d3bfbda0c5
6 changed files with 103 additions and 30 deletions

View file

@ -2,7 +2,8 @@ use anyhow::{Result, anyhow};
use epiphany_core::{
RepoFrontierExecutionAmendment, RepoFrontierRoute, amend_repo_frontier_execution,
apply_supervisor_modeling_acceptance_correction, read_coordinator_state,
runtime_repo_model_admission_receipt, runtime_repo_model_admission_review, runtime_spine_cache,
runtime_repo_frontier_plan_decision, runtime_repo_model_admission_receipt,
runtime_repo_model_admission_review, runtime_spine_cache,
};
use sha2::{Digest, Sha256};
use std::{collections::BTreeMap, env, path::PathBuf};
@ -83,6 +84,11 @@ fn main() -> Result<()> {
.ok_or_else(|| anyhow!("route {route_id} admission receipt is absent"))?;
let review = runtime_repo_model_admission_review(&store, &admission.review_id)?
.ok_or_else(|| anyhow!("route {route_id} admission review is absent"))?;
let plan_decision = if admission.frontier_plan_decision_id.is_empty() {
None
} else {
runtime_repo_frontier_plan_decision(&store, &admission.frontier_plan_decision_id)?
};
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
@ -93,6 +99,12 @@ fn main() -> Result<()> {
"admissionResultId": admission.result_id,
"admissionReviewId": admission.review_id,
"admissionSource": review.admission_source,
"admissionPreviousRevision": admission.previous_revision,
"admissionPreviousHash": admission.previous_hash,
"admissionAdmittedRevision": admission.admitted_revision,
"admissionAdmittedHash": admission.admitted_hash,
"frontierPlanDecisionId": admission.frontier_plan_decision_id,
"frontierPlanDecision": plan_decision,
"frontierItemId": route.frontier_item_id,
"frontierItemHash": route.frontier_item_hash,
"originalAction": plan.action,

View file

@ -2583,6 +2583,29 @@ pub(crate) mod tests {
let route = select_and_commit_repo_frontier_route(&store, "2026-07-15T09:00:06Z")?;
assert_eq!(route.adopted_plan.as_ref(), Some(adopted));
assert_eq!(route.source_scope, adopted.safe_paths);
let admission_review = crate::runtime_repo_model_admission_review(
&store,
&admission.review_id,
)?
.expect("plan decision admission review");
crate::coordinator_launch_context::validate_frontier_plan_decision_chain(
&decision,
&admission,
&admission_review,
&route,
)
.map_err(anyhow::Error::msg)?;
let mut substituted_route = route.clone();
substituted_route.model_hash = "substituted-admitted-model".into();
assert!(
crate::coordinator_launch_context::validate_frontier_plan_decision_chain(
&decision,
&admission,
&admission_review,
&substituted_route,
)
.is_err()
);
let grant = crate::substrate_gate_coordinator_implementation_grant(
"adopt-hands-grant".into(),
"adopt-hands-job".into(),

View file

@ -202,32 +202,12 @@ pub fn append_verification_hands_receipt_context(
)
.map_err(|error| format!("failed to load Soul frontier plan decision: {error}"))?
.ok_or_else(|| "Soul frontier admission has no persisted plan decision".to_string())?;
let source_matches = match decision.decision_source.as_ref() {
Some(crate::RepoFrontierPlanDecisionSource::MindWorker { result_id, job_id }) => {
admission_receipt.result_id.as_deref() == Some(result_id.as_str())
&& admission_review.result_id.as_deref() == Some(result_id.as_str())
&& admission_review.job_id.as_deref() == Some(job_id.as_str())
}
Some(crate::RepoFrontierPlanDecisionSource::AuthenticatedOperatorReview { .. }) => {
admission_receipt.result_id.is_none()
&& admission_review.result_id.is_none()
&& admission_review.job_id.is_none()
}
None => false,
};
if decision.decision != crate::RepoFrontierPlanDecision::Adopt
|| decision.model_admission_receipt_id != admission_receipt.receipt_id
|| decision.model_revision != admission_receipt.previous_revision
|| decision.model_hash != admission_receipt.previous_hash
|| decision.frontier_item_id != route.frontier_item_id
|| decision.frontier_item_hash != route.frontier_item_hash
|| !source_matches
{
return Err(
"Soul frontier plan decision does not exactly bind admission, route, and decision source"
.to_string(),
);
}
validate_frontier_plan_decision_chain(
&decision,
&admission_receipt,
&admission_review,
&route,
)?;
plan_decision_chain = Some(decision);
} else if let Some(admission_result_id) = admission_receipt.result_id.as_deref() {
let modeling_acceptances = state
@ -509,6 +489,51 @@ pub fn append_verification_hands_receipt_context(
Ok(context)
}
pub(crate) fn validate_frontier_plan_decision_chain(
decision: &crate::RepoFrontierPlanDecisionReceipt,
admission_receipt: &crate::RepoModelAdmissionReceipt,
admission_review: &crate::RepoModelAdmissionReview,
route: &crate::RepoFrontierRoute,
) -> Result<(), String> {
let source_matches = match decision.decision_source.as_ref() {
Some(crate::RepoFrontierPlanDecisionSource::MindWorker { result_id, job_id }) => {
admission_receipt.result_id.as_deref() == Some(result_id.as_str())
&& admission_review.result_id.as_deref() == Some(result_id.as_str())
&& admission_review.job_id.as_deref() == Some(job_id.as_str())
}
Some(crate::RepoFrontierPlanDecisionSource::AuthenticatedOperatorReview { .. }) => {
admission_receipt.result_id.is_none()
&& admission_review.result_id.is_none()
&& admission_review.job_id.is_none()
}
None => false,
};
if decision.decision != crate::RepoFrontierPlanDecision::Adopt
|| decision.model_admission_receipt_id != admission_receipt.receipt_id
|| decision.model_revision != admission_receipt.previous_revision
|| decision.model_hash != admission_receipt.previous_hash
|| decision.frontier_item_id != route.frontier_item_id
|| admission_receipt.admitted_revision != route.model_revision
|| admission_receipt.admitted_hash != route.model_hash
|| !matches!(
&admission_receipt.purpose,
crate::RepoModelPatchPurpose::AdoptFrontierPlan {
planning_request_id,
candidate_id,
..
} if planning_request_id == &decision.planning_request_id
&& candidate_id == &decision.candidate_id
)
|| !source_matches
{
return Err(
"Soul frontier plan decision does not exactly bind admission, route, and decision source"
.to_string(),
);
}
Ok(())
}
pub fn has_complete_hands_consequence_after_latest_accepted_boundary(
runtime_store_path: &Path,
state: &EpiphanyThreadState,

View file

@ -1497,8 +1497,9 @@ an authenticated operator supplied the decision.
Soul selects and verifies provenance by typed route anatomy. A non-empty
`frontier_plan_decision_id` requires the exact decision receipt, Adopt verdict,
admission receipt binding, base model revision/hash, frontier identity/hash,
and exact Mind-worker result/job or authenticated-operator source. Only an
admission receipt binding, pre-admission model revision/hash and frontier
identity, admission-purpose planning/candidate identity, post-admission route
model revision/hash, and exact Mind-worker result/job or authenticated-operator source. Only an
ordinary worker-result admission may require the Modeling acceptance,
Mind-state-commit, and gateway-review chain. Execution amendments retain their
separate exact receipt chain.
@ -1511,7 +1512,10 @@ validation path despite the legacy generic `WorkerResult` source tag.
Forbidden writer: Soul cannot infer Modeling from result presence, and a plan
decision cannot bypass provenance merely because its admission and review
contain a model-produced result. Missing, substituted, non-Adopt, or
route-mismatched decisions fail closed before worker launch.
route-mismatched decisions fail closed before worker launch. The decision's
pre-adoption frontier-item hash is deliberately not equated with the route's
post-adoption frontier-item hash; Mind's admitted patch is the transition that
changes that item.
## Exact release build cache ownership

View file

@ -72,6 +72,15 @@ The active source repair types all new plan admissions as
uses the already-persisted `frontier_plan_decision_id` as the compatibility
discriminator for v53. It does not rewrite evidence or weaken Soul.
Authenticated release `sha256-53dc65ecf1971fc39a535c3a597723e02ebe73a28936581441b4fe007c98c601`
from `d2538d46` proved the typed branch live, then failed closed because its
first exact-binding predicate incorrectly equated the decision's pre-adoption
frontier hash with the route's post-adoption hash. The follow-up source repair
binds the decision to the pre-admission model, the admission purpose to the
planning request and candidate, and the route to the admitted model. A focused
positive/negative test proves the real transition passes and a substituted
admitted route hash fails.
## Build-time finding
Commit `0f0b006d` made the three named owner manifests drive separate locked Cargo

Binary file not shown.