Bind frontier planning launches atomically
This commit is contained in:
parent
de3414c8c0
commit
6cbe47313c
13 changed files with 675 additions and 27 deletions
|
|
@ -603,6 +603,7 @@ pub fn build_epiphany_role_launch_request_with_dynamic_context(
|
|||
dynamic_prompt_context,
|
||||
proposal_modeling_context: None,
|
||||
claim_repair_context: None,
|
||||
frontier_planning_context: None,
|
||||
active_subgoal_id: state.active_subgoal_id.clone(),
|
||||
active_subgoals: state
|
||||
.subgoals
|
||||
|
|
@ -645,6 +646,7 @@ pub fn build_epiphany_role_launch_request_with_dynamic_context(
|
|||
max_runtime_seconds,
|
||||
proposal_modeling_request_id: None,
|
||||
claim_repair_request_id: None,
|
||||
frontier_planning_request_id: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -749,6 +751,7 @@ pub fn build_epiphany_job_launch_request(
|
|||
max_runtime_seconds,
|
||||
proposal_modeling_request_id: None,
|
||||
claim_repair_request_id: None,
|
||||
frontier_planning_request_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,33 @@ fn commit_coordinator_job_launch_in_cache(
|
|||
} else {
|
||||
None
|
||||
};
|
||||
let frontier_planning_launch =
|
||||
if let Some(request_id) = request.frontier_planning_request_id.as_deref() {
|
||||
let (planning, identity) =
|
||||
validate_frontier_planning_launch(cache, current_state, request, request_id)?;
|
||||
let projection = RepoFrontierPlanningContextProjection::from_request(&planning);
|
||||
match &mut effective_launch_document {
|
||||
EpiphanyWorkerLaunchDocument::Role(document) => {
|
||||
if document.proposal_modeling_context.is_some()
|
||||
|| document.claim_repair_context.is_some()
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"frontier planning context is exclusive of Modeling authority contexts"
|
||||
));
|
||||
}
|
||||
document.frontier_planning_context = Some(projection);
|
||||
}
|
||||
EpiphanyWorkerLaunchDocument::Reorient(_) => {
|
||||
return Err(anyhow!(
|
||||
"reorient launch cannot carry frontier planning context"
|
||||
));
|
||||
}
|
||||
}
|
||||
let bytes = rmp_serde::to_vec_named(&effective_launch_document)?;
|
||||
Some((planning, identity, format!("{:x}", Sha256::digest(bytes))))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let prepared = prepare_runtime_spine_heartbeat_job(
|
||||
&cache,
|
||||
RuntimeSpineHeartbeatJobOptions {
|
||||
|
|
@ -135,6 +162,7 @@ fn commit_coordinator_job_launch_in_cache(
|
|||
organ_launch_contract: request.organ_launch_contract.clone(),
|
||||
proposal_modeling_request_id: request.proposal_modeling_request_id.clone(),
|
||||
claim_repair_request_id: request.claim_repair_request_id.clone(),
|
||||
frontier_planning_request_id: request.frontier_planning_request_id.clone(),
|
||||
created_at: created_at.clone(),
|
||||
},
|
||||
)?;
|
||||
|
|
@ -208,6 +236,33 @@ fn commit_coordinator_job_launch_in_cache(
|
|||
.0,
|
||||
);
|
||||
}
|
||||
if let Some((planning, identity, worker_launch_document_sha256)) = frontier_planning_launch {
|
||||
let launch_binding = RepoFrontierPlanningLaunchBinding {
|
||||
schema_version: REPO_FRONTIER_PLANNING_LAUNCH_BINDING_SCHEMA_VERSION.into(),
|
||||
binding_record_id: format!("repo-frontier-planning-launch-{}", planning.request_id),
|
||||
planning_request_id: planning.request_id,
|
||||
job_id: plan.backend_job_id.clone(),
|
||||
binding_id: request.binding_id.clone(),
|
||||
runtime_id: identity.runtime_id,
|
||||
thread_id: planning.thread_id,
|
||||
launched_at: created_at.clone(),
|
||||
worker_launch_document_sha256,
|
||||
contract: REPO_FRONTIER_PLANNING_LAUNCH_BINDING_CONTRACT.into(),
|
||||
};
|
||||
if cache
|
||||
.get::<RepoFrontierPlanningLaunchBinding>(&launch_binding.binding_record_id)?
|
||||
.is_some()
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"frontier planning request is already bound to a launch"
|
||||
));
|
||||
}
|
||||
batch.push(
|
||||
cache
|
||||
.prepare_entry(&launch_binding.binding_record_id, &launch_binding)?
|
||||
.0,
|
||||
);
|
||||
}
|
||||
if request.binding_id == EPIPHANY_RESEARCH_ROLE_BINDING_ID {
|
||||
let grant = substrate_gate_repo_access_grant_for_launch(
|
||||
format!("substrate-grant-{}", plan.backend_job_id),
|
||||
|
|
@ -240,13 +295,15 @@ pub fn plan_coordinator_job_launch(
|
|||
launcher_job_id: String,
|
||||
backend_job_id: String,
|
||||
) -> Result<EpiphanyCoordinatorJobLaunchPlan> {
|
||||
let (caller_proposal_projection, caller_repair_projection) = match &request.launch_document {
|
||||
EpiphanyWorkerLaunchDocument::Role(document) => (
|
||||
document.proposal_modeling_context.as_ref(),
|
||||
document.claim_repair_context.as_ref(),
|
||||
),
|
||||
EpiphanyWorkerLaunchDocument::Reorient(_) => (None, None),
|
||||
};
|
||||
let (caller_proposal_projection, caller_repair_projection, caller_planning_projection) =
|
||||
match &request.launch_document {
|
||||
EpiphanyWorkerLaunchDocument::Role(document) => (
|
||||
document.proposal_modeling_context.as_ref(),
|
||||
document.claim_repair_context.as_ref(),
|
||||
document.frontier_planning_context.as_ref(),
|
||||
),
|
||||
EpiphanyWorkerLaunchDocument::Reorient(_) => (None, None, None),
|
||||
};
|
||||
if caller_proposal_projection.is_some() {
|
||||
return Err(anyhow!(
|
||||
"caller-prepopulated proposal Modeling context is forbidden; coordinator commit owns projection"
|
||||
|
|
@ -257,9 +314,23 @@ pub fn plan_coordinator_job_launch(
|
|||
"caller-prepopulated claim repair context is forbidden; coordinator commit owns projection"
|
||||
));
|
||||
}
|
||||
if request.proposal_modeling_request_id.is_some() && request.claim_repair_request_id.is_some() {
|
||||
if caller_planning_projection.is_some() {
|
||||
return Err(anyhow!(
|
||||
"proposal Modeling and claim repair launches are mutually exclusive"
|
||||
"caller-prepopulated frontier planning context is forbidden; coordinator commit owns projection"
|
||||
));
|
||||
}
|
||||
if [
|
||||
request.proposal_modeling_request_id.is_some(),
|
||||
request.claim_repair_request_id.is_some(),
|
||||
request.frontier_planning_request_id.is_some(),
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|present| *present)
|
||||
.count()
|
||||
> 1
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"proposal Modeling, claim repair, and frontier planning launches are mutually exclusive"
|
||||
));
|
||||
}
|
||||
if let Some(expected) = request.expected_revision
|
||||
|
|
@ -278,6 +349,10 @@ pub fn plan_coordinator_job_launch(
|
|||
let mut cache = runtime_spine_cache(runtime_store)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
validate_claim_repair_launch(&cache, state, request, request_id)?;
|
||||
} else if let Some(request_id) = request.frontier_planning_request_id.as_deref() {
|
||||
let mut cache = runtime_spine_cache(runtime_store)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
validate_frontier_planning_launch(&cache, state, request, request_id)?;
|
||||
} else if request.owner_role == EPIPHANY_MODELING_OWNER_ROLE {
|
||||
// Ordinary Modeling launches remain valid, but carry no proposal authority.
|
||||
}
|
||||
|
|
@ -335,6 +410,60 @@ pub fn plan_coordinator_job_launch(
|
|||
})
|
||||
}
|
||||
|
||||
fn validate_frontier_planning_launch(
|
||||
cache: &CultCache,
|
||||
state: &EpiphanyThreadState,
|
||||
launch: &EpiphanyJobLaunchRequest,
|
||||
request_id: &str,
|
||||
) -> Result<(RepoFrontierPlanningRequest, EpiphanyRuntimeIdentity)> {
|
||||
if match &launch.launch_document {
|
||||
EpiphanyWorkerLaunchDocument::Role(document) => {
|
||||
document.frontier_planning_context.is_some()
|
||||
}
|
||||
EpiphanyWorkerLaunchDocument::Reorient(_) => false,
|
||||
} {
|
||||
return Err(anyhow!("caller cannot author frontier planning context"));
|
||||
}
|
||||
if launch.owner_role != EPIPHANY_IMAGINATION_OWNER_ROLE
|
||||
|| launch.binding_id != EPIPHANY_IMAGINATION_ROLE_BINDING_ID
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"frontier planning may only be carried by the Imagination role launch"
|
||||
));
|
||||
}
|
||||
let planning = cache
|
||||
.get::<RepoFrontierPlanningRequest>(request_id)?
|
||||
.ok_or_else(|| anyhow!("frontier planning request {request_id:?} does not exist"))?;
|
||||
crate::runtime_spine::validate_current_repo_frontier_planning_request(cache, &planning)?;
|
||||
let identity = cache
|
||||
.get::<EpiphanyRuntimeIdentity>(RUNTIME_IDENTITY_KEY)?
|
||||
.ok_or_else(|| anyhow!("frontier planning launch requires runtime identity"))?;
|
||||
let persisted_state = cache
|
||||
.get::<crate::EpiphanyThreadStateEntry>(crate::THREAD_STATE_KEY)?
|
||||
.ok_or_else(|| anyhow!("frontier planning launch requires authoritative thread state"))?;
|
||||
let persisted_state_value = persisted_state.state()?;
|
||||
if planning.request_id != request_id
|
||||
|| planning.runtime_id != identity.runtime_id
|
||||
|| planning.thread_id != persisted_state.thread_id
|
||||
|| persisted_state_value != *state
|
||||
|| launch.launch_document.thread_id() != planning.thread_id
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"frontier planning launch provenance binding mismatch"
|
||||
));
|
||||
}
|
||||
if cache
|
||||
.get_all::<RepoFrontierPlanningLaunchBinding>()?
|
||||
.iter()
|
||||
.any(|binding| binding.planning_request_id == request_id)
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"frontier planning request is already bound to a launch"
|
||||
));
|
||||
}
|
||||
Ok((planning, identity))
|
||||
}
|
||||
|
||||
fn validate_claim_repair_launch(
|
||||
cache: &CultCache,
|
||||
state: &EpiphanyThreadState,
|
||||
|
|
@ -666,6 +795,49 @@ pub(crate) mod tests {
|
|||
Ok((store, state, launch, repair))
|
||||
}
|
||||
|
||||
fn frontier_planning_launch_fixture(
|
||||
root: &Path,
|
||||
suffix: &str,
|
||||
) -> Result<(
|
||||
std::path::PathBuf,
|
||||
EpiphanyThreadState,
|
||||
EpiphanyJobLaunchRequest,
|
||||
RepoFrontierPlanningRequest,
|
||||
)> {
|
||||
let (store, _) =
|
||||
crate::runtime_spine::tests::claim_challenge_fixture(root, suffix, "Imagination")?;
|
||||
let planning = crate::select_and_commit_repo_frontier_planning_request(
|
||||
&store,
|
||||
"2026-07-15T09:00:02Z",
|
||||
)?;
|
||||
let mut cache = coordinator_acceptance_cache(&store)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
let mut state = cache
|
||||
.get_required::<EpiphanyThreadStateEntry>(THREAD_STATE_KEY)?
|
||||
.state()?;
|
||||
for link in &mut state.runtime_links {
|
||||
if link.binding_id == EPIPHANY_IMAGINATION_ROLE_BINDING_ID
|
||||
&& link.runtime_result_id.is_none()
|
||||
{
|
||||
link.runtime_result_id = Some(format!("prior-imagination-result-{suffix}"));
|
||||
}
|
||||
}
|
||||
cache.put(
|
||||
THREAD_STATE_KEY,
|
||||
&EpiphanyThreadStateEntry::from_state(&planning.thread_id, &state)?,
|
||||
)?;
|
||||
let mut launch = build_epiphany_role_launch_request(
|
||||
&planning.thread_id,
|
||||
EpiphanyRoleResultRoleId::Imagination,
|
||||
Some(state.revision),
|
||||
Some(60),
|
||||
&state,
|
||||
)
|
||||
.map_err(|error| anyhow!(error))?;
|
||||
launch.frontier_planning_request_id = Some(planning.request_id.clone());
|
||||
Ok((store, state, launch, planning))
|
||||
}
|
||||
|
||||
fn research_launch(state: &EpiphanyThreadState) -> EpiphanyJobLaunchRequest {
|
||||
build_epiphany_role_launch_request(
|
||||
"thread-1",
|
||||
|
|
@ -1016,6 +1188,228 @@ pub(crate) mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frontier_planning_launch_is_coordinator_owned_exact_and_single_use() -> Result<()> {
|
||||
let root = tempfile::tempdir()?;
|
||||
let (store, state, launch, planning) =
|
||||
frontier_planning_launch_fixture(root.path(), "exact")?;
|
||||
for mutation in 0..5 {
|
||||
let mut forged = launch.clone();
|
||||
match mutation {
|
||||
0 => forged.owner_role = "Modeling".into(),
|
||||
1 => forged.binding_id = "wrong-binding".into(),
|
||||
2 => {
|
||||
if let EpiphanyWorkerLaunchDocument::Role(document) =
|
||||
&mut forged.launch_document
|
||||
{
|
||||
document.frontier_planning_context = Some(
|
||||
RepoFrontierPlanningContextProjection::from_request(&planning),
|
||||
);
|
||||
}
|
||||
}
|
||||
3 => forged.claim_repair_request_id = Some("dual-repair".into()),
|
||||
_ => forged.proposal_modeling_request_id = Some("dual-proposal".into()),
|
||||
}
|
||||
let before = std::fs::read(&store)?;
|
||||
assert!(
|
||||
plan_coordinator_job_launch(
|
||||
&state,
|
||||
&forged,
|
||||
&store,
|
||||
format!("launcher-forged-{mutation}"),
|
||||
format!("backend-forged-{mutation}"),
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(std::fs::read(&store)?, before);
|
||||
}
|
||||
let plan = plan_coordinator_job_launch(
|
||||
&state,
|
||||
&launch,
|
||||
&store,
|
||||
"launcher-planning".into(),
|
||||
"backend-planning".into(),
|
||||
)?;
|
||||
let committed = commit_coordinator_job_launch(
|
||||
&store,
|
||||
&planning.thread_id,
|
||||
&state,
|
||||
&launch,
|
||||
&plan,
|
||||
"2026-07-15T09:00:03Z".into(),
|
||||
)?;
|
||||
let persisted = runtime_worker_launch_request(&store, "backend-planning")?
|
||||
.expect("planning worker launch");
|
||||
let projection = match persisted.launch_document()? {
|
||||
EpiphanyWorkerLaunchDocument::Role(document) => document
|
||||
.frontier_planning_context
|
||||
.expect("coordinator-owned planning context"),
|
||||
EpiphanyWorkerLaunchDocument::Reorient(_) => panic!("planning cannot reorient"),
|
||||
};
|
||||
assert_eq!(projection.request_id, planning.request_id);
|
||||
assert_eq!(projection.runtime_id, planning.runtime_id);
|
||||
assert_eq!(projection.thread_id, planning.thread_id);
|
||||
let cache = coordinator_acceptance_cache(&store)?;
|
||||
let binding_key = format!("repo-frontier-planning-launch-{}", planning.request_id);
|
||||
let binding = cache
|
||||
.get::<RepoFrontierPlanningLaunchBinding>(&binding_key)?
|
||||
.expect("request-keyed planning launch binding");
|
||||
assert_eq!(binding.job_id, "backend-planning");
|
||||
assert_eq!(binding.runtime_id, planning.runtime_id);
|
||||
assert_eq!(binding.thread_id, planning.thread_id);
|
||||
assert_eq!(
|
||||
binding.worker_launch_document_sha256,
|
||||
format!("{:x}", Sha256::digest(&persisted.launch_document_msgpack))
|
||||
);
|
||||
let mut second = build_epiphany_role_launch_request(
|
||||
&planning.thread_id,
|
||||
EpiphanyRoleResultRoleId::Imagination,
|
||||
Some(committed.epiphany_state.revision),
|
||||
Some(60),
|
||||
&committed.epiphany_state,
|
||||
)
|
||||
.map_err(|error| anyhow!(error))?;
|
||||
second.frontier_planning_request_id = Some(planning.request_id);
|
||||
let before = std::fs::read(&store)?;
|
||||
assert!(
|
||||
plan_coordinator_job_launch(
|
||||
&committed.epiphany_state,
|
||||
&second,
|
||||
&store,
|
||||
"launcher-second".into(),
|
||||
"backend-second".into(),
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(std::fs::read(&store)?, before);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frontier_planning_launch_refuses_swapped_request_bytes_without_writes() -> Result<()> {
|
||||
let root = tempfile::tempdir()?;
|
||||
for mutation in 0..8 {
|
||||
let (store, state, launch, planning) =
|
||||
frontier_planning_launch_fixture(root.path(), &format!("causal-{mutation}"))?;
|
||||
let mut corrupt = planning.clone();
|
||||
match mutation {
|
||||
0 => corrupt.model_hash = "swapped-model".into(),
|
||||
1 => corrupt.admission_receipt_id = "swapped-admission".into(),
|
||||
2 => corrupt.frontier_item_id = "swapped-frontier".into(),
|
||||
3 => corrupt.frontier_item_hash = "swapped-frontier-hash".into(),
|
||||
4 => corrupt.selected_organ = "Hands".into(),
|
||||
5 => corrupt.source_scope.push("outside/scope".into()),
|
||||
6 => corrupt.runtime_id = "swapped-runtime".into(),
|
||||
_ => corrupt.thread_id = "swapped-thread".into(),
|
||||
}
|
||||
let mut cache = coordinator_acceptance_cache(&store)?;
|
||||
cache.put(&corrupt.request_id, &corrupt)?;
|
||||
let before = std::fs::read(&store)?;
|
||||
assert!(
|
||||
plan_coordinator_job_launch(
|
||||
&state,
|
||||
&launch,
|
||||
&store,
|
||||
format!("launcher-causal-{mutation}"),
|
||||
format!("backend-causal-{mutation}"),
|
||||
)
|
||||
.is_err(),
|
||||
"planning causal mutation {mutation} must be refused"
|
||||
);
|
||||
assert_eq!(std::fs::read(&store)?, before);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concurrent_frontier_planning_launches_leave_only_winner_artifacts() -> Result<()> {
|
||||
let root = tempfile::tempdir()?;
|
||||
let (store, state, launch, planning) =
|
||||
frontier_planning_launch_fixture(root.path(), "race")?;
|
||||
let left_plan = plan_coordinator_job_launch(
|
||||
&state,
|
||||
&launch,
|
||||
&store,
|
||||
"launcher-left".into(),
|
||||
"backend-left".into(),
|
||||
)?;
|
||||
let right_plan = plan_coordinator_job_launch(
|
||||
&state,
|
||||
&launch,
|
||||
&store,
|
||||
"launcher-right".into(),
|
||||
"backend-right".into(),
|
||||
)?;
|
||||
let spawn = |store: std::path::PathBuf,
|
||||
state: EpiphanyThreadState,
|
||||
launch: EpiphanyJobLaunchRequest,
|
||||
plan: EpiphanyCoordinatorJobLaunchPlan,
|
||||
thread: String,
|
||||
at: &'static str| {
|
||||
std::thread::spawn(move || {
|
||||
commit_coordinator_job_launch(&store, &thread, &state, &launch, &plan, at.into())
|
||||
})
|
||||
};
|
||||
let left = spawn(
|
||||
store.clone(),
|
||||
state.clone(),
|
||||
launch.clone(),
|
||||
left_plan,
|
||||
planning.thread_id.clone(),
|
||||
"2026-07-15T09:00:03Z",
|
||||
);
|
||||
let right = spawn(
|
||||
store.clone(),
|
||||
state,
|
||||
launch,
|
||||
right_plan,
|
||||
planning.thread_id,
|
||||
"2026-07-15T09:00:04Z",
|
||||
);
|
||||
let outcomes = [left.join().unwrap(), right.join().unwrap()];
|
||||
assert_eq!(outcomes.iter().filter(|outcome| outcome.is_ok()).count(), 1);
|
||||
let mut cache = coordinator_acceptance_cache(&store)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
let bindings = cache
|
||||
.get_all::<RepoFrontierPlanningLaunchBinding>()?
|
||||
.into_iter()
|
||||
.filter(|binding| binding.planning_request_id == planning.request_id)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(bindings.len(), 1);
|
||||
let winner = bindings[0].job_id.as_str();
|
||||
let loser = if winner == "backend-left" {
|
||||
"backend-right"
|
||||
} else {
|
||||
assert_eq!(winner, "backend-right");
|
||||
"backend-left"
|
||||
};
|
||||
for job_id in [winner] {
|
||||
assert!(cache.get::<EpiphanyRuntimeJob>(job_id)?.is_some());
|
||||
assert!(
|
||||
cache
|
||||
.get::<EpiphanyRuntimeWorkerLaunchRequest>(job_id)?
|
||||
.is_some()
|
||||
);
|
||||
assert!(
|
||||
cache
|
||||
.get::<EpiphanyRuntimeEvent>(&format!("event-job-opened-{job_id}"))?
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
assert!(cache.get::<EpiphanyRuntimeJob>(loser)?.is_none());
|
||||
assert!(
|
||||
cache
|
||||
.get::<EpiphanyRuntimeWorkerLaunchRequest>(loser)?
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
cache
|
||||
.get::<EpiphanyRuntimeEvent>(&format!("event-job-opened-{loser}"))?
|
||||
.is_none()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claim_repair_launch_refuses_every_swapped_causal_field_without_new_writes() -> Result<()> {
|
||||
let root = tempfile::tempdir()?;
|
||||
|
|
|
|||
|
|
@ -1133,6 +1133,7 @@ mod tests {
|
|||
organ_launch_contract: launch_request.organ_launch_contract.clone(),
|
||||
proposal_modeling_request_id: None,
|
||||
claim_repair_request_id: None,
|
||||
frontier_planning_request_id: None,
|
||||
created_at: "2026-06-02T00:00:00Z".to_string(),
|
||||
},
|
||||
)?;
|
||||
|
|
|
|||
|
|
@ -976,11 +976,14 @@ pub use surfaces::EpiphanyTokenUsageSnapshot;
|
|||
pub use surfaces::EpiphanyViewLens;
|
||||
pub use surfaces::EpiphanyWorkerLaunchDocument;
|
||||
pub use surfaces::REORIENT_WORKER_OUTPUT_CONTRACT_ID;
|
||||
pub use surfaces::REPO_FRONTIER_PLANNING_CONTEXT_CONTRACT;
|
||||
pub use surfaces::REPO_FRONTIER_PLANNING_CONTEXT_SCHEMA_VERSION;
|
||||
pub use surfaces::REPO_FRONTIER_PROPOSAL_MODELING_CONTEXT_CONTRACT;
|
||||
pub use surfaces::REPO_FRONTIER_PROPOSAL_MODELING_CONTEXT_SCHEMA_VERSION;
|
||||
pub use surfaces::REPO_MODEL_CLAIM_REPAIR_CONTEXT_CONTRACT;
|
||||
pub use surfaces::REPO_MODEL_CLAIM_REPAIR_CONTEXT_SCHEMA_VERSION;
|
||||
pub use surfaces::ROLE_WORKER_OUTPUT_CONTRACT_ID;
|
||||
pub use surfaces::RepoFrontierPlanningContextProjection;
|
||||
pub use surfaces::RepoFrontierProposalModelingContextProjection;
|
||||
pub use surfaces::RepoModelClaimRepairContextProjection;
|
||||
pub use surfaces::build_reorient_acceptance_bundle;
|
||||
|
|
|
|||
|
|
@ -30,12 +30,12 @@ pub const REPO_FRONTIER_MODELING_REQUEST_CONTRACT: &str =
|
|||
pub const REPO_FRONTIER_WORK_PROPOSAL_SCHEMA_VERSION: &str =
|
||||
"epiphany.repo_frontier_work_proposal.v0";
|
||||
pub const REPO_FRONTIER_PLANNING_REQUEST_SCHEMA_VERSION: &str =
|
||||
"epiphany.self.repo_frontier_planning_request.v0";
|
||||
"epiphany.self.repo_frontier_planning_request.v1";
|
||||
pub const REPO_FRONTIER_PLAN_CANDIDATE_SCHEMA_VERSION: &str =
|
||||
"epiphany.imagination.repo_frontier_plan_candidate.v0";
|
||||
pub const REPO_FRONTIER_PLAN_ADOPTION_SCHEMA_VERSION: &str =
|
||||
"epiphany.mind.repo_frontier_plan_adoption.v0";
|
||||
pub const REPO_FRONTIER_PLANNING_CONTRACT: &str = "epiphany.repo_frontier_planning.v0";
|
||||
pub const REPO_FRONTIER_PLANNING_CONTRACT: &str = "epiphany.repo_frontier_planning.v1";
|
||||
pub const REPO_FRONTIER_WORK_PROPOSAL_CONTRACT: &str =
|
||||
"epiphany.repo_frontier_work_proposal.inert.v0";
|
||||
pub const REPO_FRONTIER_PROPOSAL_MODELING_REQUEST_SCHEMA_VERSION: &str =
|
||||
|
|
@ -57,6 +57,10 @@ pub const REPO_MODEL_CLAIM_REPAIR_LAUNCH_BINDING_SCHEMA_VERSION: &str =
|
|||
"epiphany.coordinator.repo_model_claim_repair_launch_binding.v0";
|
||||
pub const REPO_MODEL_CLAIM_REPAIR_LAUNCH_BINDING_CONTRACT: &str =
|
||||
"epiphany.repo_model_claim_repair_launch_binding.v0";
|
||||
pub const REPO_FRONTIER_PLANNING_LAUNCH_BINDING_SCHEMA_VERSION: &str =
|
||||
"epiphany.coordinator.repo_frontier_planning_launch_binding.v0";
|
||||
pub const REPO_FRONTIER_PLANNING_LAUNCH_BINDING_CONTRACT: &str =
|
||||
"epiphany.repo_frontier_planning_launch_binding.v0";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
|
@ -355,6 +359,38 @@ pub struct RepoFrontierPlanningRequest {
|
|||
pub requested_at: String,
|
||||
#[cultcache(key = 10)]
|
||||
pub contract: String,
|
||||
#[cultcache(key = 11)]
|
||||
pub runtime_id: String,
|
||||
#[cultcache(key = 12)]
|
||||
pub thread_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "epiphany.coordinator.repo_frontier_planning_launch_binding",
|
||||
schema = "RepoFrontierPlanningLaunchBinding"
|
||||
)]
|
||||
pub struct RepoFrontierPlanningLaunchBinding {
|
||||
#[cultcache(key = 0)]
|
||||
pub schema_version: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub binding_record_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub planning_request_id: String,
|
||||
#[cultcache(key = 3)]
|
||||
pub job_id: String,
|
||||
#[cultcache(key = 4)]
|
||||
pub binding_id: String,
|
||||
#[cultcache(key = 5)]
|
||||
pub runtime_id: String,
|
||||
#[cultcache(key = 6)]
|
||||
pub thread_id: String,
|
||||
#[cultcache(key = 7)]
|
||||
pub launched_at: String,
|
||||
#[cultcache(key = 8)]
|
||||
pub worker_launch_document_sha256: String,
|
||||
#[cultcache(key = 9)]
|
||||
pub contract: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
use crate::EpiphanyWorkerLaunchDocument;
|
||||
use crate::agent_launch::{EPIPHANY_MODELING_OWNER_ROLE, EPIPHANY_MODELING_ROLE_BINDING_ID};
|
||||
use crate::agent_launch::{
|
||||
EPIPHANY_IMAGINATION_OWNER_ROLE, EPIPHANY_IMAGINATION_ROLE_BINDING_ID,
|
||||
EPIPHANY_MODELING_OWNER_ROLE, EPIPHANY_MODELING_ROLE_BINDING_ID,
|
||||
};
|
||||
use crate::agent_memory::AGENT_MEMORY_TYPE;
|
||||
use crate::continuity_gateway::ContinuityRecoveryReceipt;
|
||||
use crate::continuity_gateway::*;
|
||||
|
|
@ -59,7 +62,7 @@ use crate::repo_model_gateway::{
|
|||
REPO_MODEL_MIGRATION_CONTRACT, REPO_MODEL_MIGRATION_RECEIPT_SCHEMA_VERSION,
|
||||
REPO_MODEL_MIGRATION_RECEIPT_TYPE, RepoFrontierHandsAuthority, RepoFrontierModelingRequest,
|
||||
RepoFrontierNextOrgan, RepoFrontierPlanAdoption, RepoFrontierPlanCandidate,
|
||||
RepoFrontierPlanDecision, RepoFrontierPlanningRequest,
|
||||
RepoFrontierPlanDecision, RepoFrontierPlanningLaunchBinding, RepoFrontierPlanningRequest,
|
||||
RepoFrontierProposalModelingLaunchBinding, RepoFrontierProposalModelingRequest,
|
||||
RepoFrontierRoute, RepoFrontierVerdictDisposition, RepoFrontierWorkProposal,
|
||||
RepoModelAdmissionReceipt, RepoModelAdmissionReview, RepoModelClaimChallenge,
|
||||
|
|
@ -328,6 +331,8 @@ pub struct EpiphanyRuntimeWorkerLaunchRequest {
|
|||
pub proposal_modeling_request_id: Option<String>,
|
||||
#[cultcache(key = 12, default)]
|
||||
pub claim_repair_request_id: Option<String>,
|
||||
#[cultcache(key = 13, default)]
|
||||
pub frontier_planning_request_id: Option<String>,
|
||||
}
|
||||
|
||||
impl EpiphanyRuntimeWorkerLaunchRequest {
|
||||
|
|
@ -683,6 +688,7 @@ pub struct RuntimeSpineHeartbeatJobOptions {
|
|||
pub organ_launch_contract: EpiphanyLaunchOrganContract,
|
||||
pub proposal_modeling_request_id: Option<String>,
|
||||
pub claim_repair_request_id: Option<String>,
|
||||
pub frontier_planning_request_id: Option<String>,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
|
|
@ -732,6 +738,7 @@ pub struct EpiphanyJobLaunchRequest {
|
|||
pub max_runtime_seconds: Option<u64>,
|
||||
pub proposal_modeling_request_id: Option<String>,
|
||||
pub claim_repair_request_id: Option<String>,
|
||||
pub frontier_planning_request_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
|
@ -786,6 +793,7 @@ pub fn runtime_spine_cache(store_path: impl AsRef<Path>) -> Result<CultCache> {
|
|||
cache.register_entry_type::<RepoFrontierProposalModelingRequest>()?;
|
||||
cache.register_entry_type::<RepoFrontierProposalModelingLaunchBinding>()?;
|
||||
cache.register_entry_type::<RepoFrontierPlanningRequest>()?;
|
||||
cache.register_entry_type::<RepoFrontierPlanningLaunchBinding>()?;
|
||||
cache.register_entry_type::<RepoFrontierPlanCandidate>()?;
|
||||
cache.register_entry_type::<RepoFrontierPlanAdoption>()?;
|
||||
cache.register_entry_type::<RepoFrontierVerificationRequest>()?;
|
||||
|
|
@ -1054,6 +1062,12 @@ pub fn open_runtime_spine_heartbeat_job(
|
|||
options.claim_repair_request_id.as_deref(),
|
||||
&options.launch_document,
|
||||
)?;
|
||||
validate_frontier_planning_launch_carrier(
|
||||
&options.role,
|
||||
&options.binding_id,
|
||||
options.frontier_planning_request_id.as_deref(),
|
||||
&options.launch_document,
|
||||
)?;
|
||||
validate_non_empty(&options.binding_id, "binding id")?;
|
||||
validate_non_empty(&options.authority_scope, "authority scope")?;
|
||||
validate_non_empty(&options.instruction, "instruction")?;
|
||||
|
|
@ -1139,6 +1153,7 @@ pub fn open_runtime_spine_heartbeat_job(
|
|||
organ_launch_contract,
|
||||
proposal_modeling_request_id: options.proposal_modeling_request_id,
|
||||
claim_repair_request_id: options.claim_repair_request_id,
|
||||
frontier_planning_request_id: options.frontier_planning_request_id,
|
||||
};
|
||||
cache.put(&job_id, &request)?;
|
||||
Ok(job)
|
||||
|
|
@ -1166,6 +1181,12 @@ pub fn prepare_runtime_spine_heartbeat_job(
|
|||
options.claim_repair_request_id.as_deref(),
|
||||
&options.launch_document,
|
||||
)?;
|
||||
validate_frontier_planning_launch_carrier(
|
||||
&options.role,
|
||||
&options.binding_id,
|
||||
options.frontier_planning_request_id.as_deref(),
|
||||
&options.launch_document,
|
||||
)?;
|
||||
validate_non_empty(&options.binding_id, "binding id")?;
|
||||
validate_non_empty(&options.authority_scope, "authority scope")?;
|
||||
validate_non_empty(&options.instruction, "instruction")?;
|
||||
|
|
@ -1277,6 +1298,7 @@ pub fn prepare_runtime_spine_heartbeat_job(
|
|||
organ_launch_contract: options.organ_launch_contract,
|
||||
proposal_modeling_request_id: options.proposal_modeling_request_id,
|
||||
claim_repair_request_id: options.claim_repair_request_id,
|
||||
frontier_planning_request_id: options.frontier_planning_request_id,
|
||||
};
|
||||
let envelopes = vec![
|
||||
cache.prepare_entry(RUNTIME_IDENTITY_KEY, &identity)?.0,
|
||||
|
|
@ -1354,6 +1376,39 @@ fn validate_claim_repair_launch_carrier(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_frontier_planning_launch_carrier(
|
||||
role: &str,
|
||||
binding_id: &str,
|
||||
planning_request_id: Option<&str>,
|
||||
launch_document: &EpiphanyWorkerLaunchDocument,
|
||||
) -> Result<()> {
|
||||
let projection = match launch_document {
|
||||
EpiphanyWorkerLaunchDocument::Role(document) => document.frontier_planning_context.as_ref(),
|
||||
EpiphanyWorkerLaunchDocument::Reorient(_) => None,
|
||||
};
|
||||
let Some(request_id) = planning_request_id else {
|
||||
if projection.is_some() {
|
||||
return Err(anyhow!(
|
||||
"frontier planning context requires its typed request id"
|
||||
));
|
||||
}
|
||||
return Ok(());
|
||||
};
|
||||
validate_non_empty(request_id, "frontier planning request id")?;
|
||||
if role != EPIPHANY_IMAGINATION_OWNER_ROLE || binding_id != EPIPHANY_IMAGINATION_ROLE_BINDING_ID
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"frontier planning request may only be transported by the Imagination role launch"
|
||||
));
|
||||
}
|
||||
let projection = projection
|
||||
.ok_or_else(|| anyhow!("frontier planning request requires its typed context"))?;
|
||||
if projection.request_id != request_id {
|
||||
return Err(anyhow!("frontier planning context/request mismatch"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn runtime_job_snapshot(
|
||||
store_path: impl AsRef<Path>,
|
||||
job_id: &str,
|
||||
|
|
@ -2748,7 +2803,11 @@ pub fn select_and_commit_repo_frontier_planning_request(
|
|||
let runtime_store = runtime_store.as_ref();
|
||||
let mut cache = runtime_spine_cache(runtime_store)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
require_identity(&cache)?;
|
||||
let identity = require_identity(&cache)?;
|
||||
let thread = cache
|
||||
.get::<crate::EpiphanyThreadStateEntry>(crate::THREAD_STATE_KEY)?
|
||||
.ok_or_else(|| anyhow!("planning request requires authoritative thread state"))?;
|
||||
thread.state()?;
|
||||
let backing = SingleFileMessagePackBackingStore::new(runtime_store);
|
||||
let model_envelope = backing
|
||||
.pull_all()?
|
||||
|
|
@ -2792,7 +2851,13 @@ pub fn select_and_commit_repo_frontier_planning_request(
|
|||
}
|
||||
let request_id = format!(
|
||||
"repo-frontier-planning-{:x}",
|
||||
Sha256::digest(format!("{model_hash}:{}:{item_hash}", item.id).as_bytes())
|
||||
Sha256::digest(
|
||||
format!(
|
||||
"{}:{}:{model_hash}:{}:{item_hash}",
|
||||
identity.runtime_id, thread.thread_id, item.id
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
);
|
||||
let request = RepoFrontierPlanningRequest {
|
||||
schema_version: REPO_FRONTIER_PLANNING_REQUEST_SCHEMA_VERSION.into(),
|
||||
|
|
@ -2806,6 +2871,8 @@ pub fn select_and_commit_repo_frontier_planning_request(
|
|||
source_scope: item.source_scope.clone(),
|
||||
requested_at: at.into(),
|
||||
contract: REPO_FRONTIER_PLANNING_CONTRACT.into(),
|
||||
runtime_id: identity.runtime_id,
|
||||
thread_id: thread.thread_id,
|
||||
};
|
||||
if let Some(existing) = cache.get::<RepoFrontierPlanningRequest>(&request_id)? {
|
||||
let mut retry = request.clone();
|
||||
|
|
@ -2834,6 +2901,72 @@ pub fn select_and_commit_repo_frontier_planning_request(
|
|||
Ok(request)
|
||||
}
|
||||
|
||||
pub(crate) fn validate_current_repo_frontier_planning_request(
|
||||
cache: &CultCache,
|
||||
request: &RepoFrontierPlanningRequest,
|
||||
) -> Result<()> {
|
||||
chrono::DateTime::parse_from_rfc3339(&request.requested_at)
|
||||
.map_err(|_| anyhow!("planning request timestamp must be RFC3339"))?;
|
||||
let identity = require_identity(cache)?;
|
||||
let thread = cache
|
||||
.get::<crate::EpiphanyThreadStateEntry>(crate::THREAD_STATE_KEY)?
|
||||
.ok_or_else(|| anyhow!("planning request requires authoritative thread state"))?;
|
||||
thread.state()?;
|
||||
let entry = cache
|
||||
.get::<crate::EpiphanyMemoryGraphEntry>(crate::MEMORY_GRAPH_KEY)?
|
||||
.ok_or_else(|| anyhow!("planning request requires canonical model"))?;
|
||||
crate::validate_memory_graph_entry(&entry)?;
|
||||
let model = entry.snapshot()?;
|
||||
let model_hash = crate::memory_graph_model_hash(&model)?;
|
||||
let receipts = cache
|
||||
.get_all::<RepoModelAdmissionReceipt>()?
|
||||
.into_iter()
|
||||
.filter(|receipt| {
|
||||
receipt.schema_version == REPO_MODEL_ADMISSION_RECEIPT_SCHEMA_VERSION
|
||||
&& receipt.contract == REPO_MODEL_ADMISSION_CONTRACT
|
||||
&& receipt.admitted_revision == model.model_revision
|
||||
&& receipt.admitted_hash == model_hash
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if receipts.len() != 1 {
|
||||
return Err(anyhow!(
|
||||
"planning request requires exactly one current admission receipt"
|
||||
));
|
||||
}
|
||||
let challenges = current_repo_model_claim_challenges(cache, &model, &model_hash)?;
|
||||
let item = actionable_imagination_frontier_item(&model, &challenges)
|
||||
.ok_or_else(|| anyhow!("planning request frontier is no longer actionable"))?;
|
||||
let item_hash = format!("{:x}", Sha256::digest(rmp_serde::to_vec_named(item)?));
|
||||
let expected_request_id = format!(
|
||||
"repo-frontier-planning-{:x}",
|
||||
Sha256::digest(
|
||||
format!(
|
||||
"{}:{}:{model_hash}:{}:{item_hash}",
|
||||
identity.runtime_id, thread.thread_id, item.id
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
);
|
||||
if request.schema_version != REPO_FRONTIER_PLANNING_REQUEST_SCHEMA_VERSION
|
||||
|| request.contract != REPO_FRONTIER_PLANNING_CONTRACT
|
||||
|| request.request_id != expected_request_id
|
||||
|| request.model_revision != model.model_revision
|
||||
|| request.model_hash != model_hash
|
||||
|| request.admission_receipt_id != receipts[0].receipt_id
|
||||
|| request.frontier_item_id != item.id
|
||||
|| request.frontier_item_hash != item_hash
|
||||
|| request.selected_organ != "Imagination"
|
||||
|| request.source_scope != item.source_scope
|
||||
|| request.runtime_id != identity.runtime_id
|
||||
|| request.thread_id != thread.thread_id
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"planning request does not exactly bind current model, frontier, runtime, and thread"
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn actionable_imagination_frontier_item<'a>(
|
||||
model: &'a crate::EpiphanyMemoryGraphSnapshot,
|
||||
challenges: &[RepoModelClaimChallenge],
|
||||
|
|
@ -9853,6 +9986,7 @@ pub(crate) mod tests {
|
|||
dynamic_prompt_context: None,
|
||||
proposal_modeling_context: None,
|
||||
claim_repair_context: None,
|
||||
frontier_planning_context: None,
|
||||
active_subgoal_id: None,
|
||||
active_subgoals: Vec::new(),
|
||||
active_graph_node_ids: vec!["node-model".to_string()],
|
||||
|
|
@ -9876,6 +10010,7 @@ pub(crate) mod tests {
|
|||
),
|
||||
proposal_modeling_request_id: None,
|
||||
claim_repair_request_id: None,
|
||||
frontier_planning_request_id: None,
|
||||
created_at: "2026-05-06T00:02:00Z".to_string(),
|
||||
},
|
||||
)?;
|
||||
|
|
@ -9946,6 +10081,7 @@ pub(crate) mod tests {
|
|||
dynamic_prompt_context: None,
|
||||
proposal_modeling_context: None,
|
||||
claim_repair_context: None,
|
||||
frontier_planning_context: None,
|
||||
active_subgoal_id: None,
|
||||
active_subgoals: Vec::new(),
|
||||
active_graph_node_ids: vec!["runtime-spine".to_string()],
|
||||
|
|
@ -10032,6 +10168,7 @@ pub(crate) mod tests {
|
|||
dynamic_prompt_context: None,
|
||||
proposal_modeling_context: None,
|
||||
claim_repair_context: None,
|
||||
frontier_planning_context: None,
|
||||
active_subgoal_id: None,
|
||||
active_subgoals: Vec::new(),
|
||||
active_graph_node_ids: Vec::new(),
|
||||
|
|
|
|||
|
|
@ -251,6 +251,7 @@ mod tests {
|
|||
dynamic_prompt_context: None,
|
||||
proposal_modeling_context: None,
|
||||
claim_repair_context: None,
|
||||
frontier_planning_context: None,
|
||||
active_subgoal_id: None,
|
||||
active_subgoals: Vec::new(),
|
||||
active_graph_node_ids: Vec::new(),
|
||||
|
|
@ -275,6 +276,7 @@ mod tests {
|
|||
max_runtime_seconds: None,
|
||||
proposal_modeling_request_id: None,
|
||||
claim_repair_request_id: None,
|
||||
frontier_planning_request_id: None,
|
||||
};
|
||||
let grant = substrate_gate_repo_access_grant_for_launch(
|
||||
"grant-1".to_string(),
|
||||
|
|
|
|||
|
|
@ -144,11 +144,14 @@ pub use worker_launch::EpiphanyReorientWorkerLaunchDocument;
|
|||
pub use worker_launch::EpiphanyRoleWorkerLaunchDocument;
|
||||
pub use worker_launch::EpiphanyWorkerLaunchDocument;
|
||||
pub use worker_launch::REORIENT_WORKER_OUTPUT_CONTRACT_ID;
|
||||
pub use worker_launch::REPO_FRONTIER_PLANNING_CONTEXT_CONTRACT;
|
||||
pub use worker_launch::REPO_FRONTIER_PLANNING_CONTEXT_SCHEMA_VERSION;
|
||||
pub use worker_launch::REPO_FRONTIER_PROPOSAL_MODELING_CONTEXT_CONTRACT;
|
||||
pub use worker_launch::REPO_FRONTIER_PROPOSAL_MODELING_CONTEXT_SCHEMA_VERSION;
|
||||
pub use worker_launch::REPO_MODEL_CLAIM_REPAIR_CONTEXT_CONTRACT;
|
||||
pub use worker_launch::REPO_MODEL_CLAIM_REPAIR_CONTEXT_SCHEMA_VERSION;
|
||||
pub use worker_launch::ROLE_WORKER_OUTPUT_CONTRACT_ID;
|
||||
pub use worker_launch::RepoFrontierPlanningContextProjection;
|
||||
pub use worker_launch::RepoFrontierProposalModelingContextProjection;
|
||||
pub use worker_launch::RepoModelClaimRepairContextProjection;
|
||||
pub use worker_launch::build_reorient_job_launch_request;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ pub const REPO_MODEL_CLAIM_REPAIR_CONTEXT_SCHEMA_VERSION: &str =
|
|||
"epiphany.worker.repo_model_claim_repair_context.v0";
|
||||
pub const REPO_MODEL_CLAIM_REPAIR_CONTEXT_CONTRACT: &str =
|
||||
"epiphany.repo_model_claim_repair_context.v0";
|
||||
pub const REPO_FRONTIER_PLANNING_CONTEXT_SCHEMA_VERSION: &str =
|
||||
"epiphany.worker.repo_frontier_planning_context.v0";
|
||||
pub const REPO_FRONTIER_PLANNING_CONTEXT_CONTRACT: &str =
|
||||
"epiphany.repo_frontier_planning_context.v0";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", tag = "documentKind")]
|
||||
|
|
@ -89,6 +93,8 @@ pub struct EpiphanyRoleWorkerLaunchDocument {
|
|||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub claim_repair_context: Option<RepoModelClaimRepairContextProjection>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub frontier_planning_context: Option<RepoFrontierPlanningContextProjection>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub active_subgoal_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub active_subgoals: Vec<EpiphanySubgoal>,
|
||||
|
|
@ -256,6 +262,45 @@ impl RepoModelClaimRepairContextProjection {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RepoFrontierPlanningContextProjection {
|
||||
pub schema_version: String,
|
||||
pub contract: String,
|
||||
pub request_id: String,
|
||||
pub model_revision: u64,
|
||||
pub model_hash: String,
|
||||
pub admission_receipt_id: String,
|
||||
pub frontier_item_id: String,
|
||||
pub frontier_item_hash: String,
|
||||
pub selected_organ: String,
|
||||
#[serde(default)]
|
||||
pub source_scope: Vec<String>,
|
||||
pub requested_at: String,
|
||||
pub runtime_id: String,
|
||||
pub thread_id: String,
|
||||
}
|
||||
|
||||
impl RepoFrontierPlanningContextProjection {
|
||||
pub(crate) fn from_request(request: &crate::RepoFrontierPlanningRequest) -> Self {
|
||||
Self {
|
||||
schema_version: REPO_FRONTIER_PLANNING_CONTEXT_SCHEMA_VERSION.into(),
|
||||
contract: REPO_FRONTIER_PLANNING_CONTEXT_CONTRACT.into(),
|
||||
request_id: request.request_id.clone(),
|
||||
model_revision: request.model_revision,
|
||||
model_hash: request.model_hash.clone(),
|
||||
admission_receipt_id: request.admission_receipt_id.clone(),
|
||||
frontier_item_id: request.frontier_item_id.clone(),
|
||||
frontier_item_hash: request.frontier_item_hash.clone(),
|
||||
selected_organ: request.selected_organ.clone(),
|
||||
source_scope: request.source_scope.clone(),
|
||||
requested_at: request.requested_at.clone(),
|
||||
runtime_id: request.runtime_id.clone(),
|
||||
thread_id: request.thread_id.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_reorient_job_launch_request(
|
||||
input: EpiphanyReorientLaunchRequestInput<'_>,
|
||||
) -> EpiphanyJobLaunchRequest {
|
||||
|
|
@ -298,6 +343,7 @@ pub fn build_reorient_job_launch_request(
|
|||
max_runtime_seconds: input.max_runtime_seconds,
|
||||
proposal_modeling_request_id: None,
|
||||
claim_repair_request_id: None,
|
||||
frontier_planning_request_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -948,3 +948,20 @@ embedded receipt binds back to the artifact target and audit; forged
|
|||
cardinality: Epiphany inherits a CultMesh command id, so the coordinator must
|
||||
ensure one typed command id per crossing rather than letting multiple posts share
|
||||
one canonical `crossing_<command>` receipt.
|
||||
|
||||
## Repo frontier planning launch artery (2026-07-15)
|
||||
|
||||
Self's planning request is now v1 and binds the exact current RepoModel,
|
||||
admission receipt, actionable Imagination frontier, runtime identity, and
|
||||
authoritative thread. Coordinator launch commit replays that entire chain from
|
||||
one transaction snapshot, forbids caller-authored planning projection and
|
||||
mixed Modeling/repair/planning authority, injects the typed Imagination context,
|
||||
and atomically persists a request-keyed
|
||||
`RepoFrontierPlanningLaunchBinding`. Its SHA-256 covers the exact launch bytes
|
||||
stored for the worker. Hostile tests cover swapped model/admission/frontier/
|
||||
scope/organ/runtime/thread fields, wrong role/binding, prepopulated projection,
|
||||
dual authority, single-use replay, and two concurrent launches; only one wins
|
||||
and the loser leaves no job, worker launch, or opened event. Full core proof:
|
||||
322 passed, 1 ignored. Next build the exclusive immutable Imagination result
|
||||
echo carrying the typed candidate, then Mind-only Adopt/Refuse/Hold admission
|
||||
and the narrow Adopt model transition that makes Hands routing true.
|
||||
|
|
|
|||
|
|
@ -1,18 +1,24 @@
|
|||
# Repo Frontier Planning Authority Map
|
||||
|
||||
## Current wound
|
||||
## Current state
|
||||
|
||||
The planning family has typed documents but not a live organ chain. Self can
|
||||
persist an exact `RepoFrontierPlanningRequest`. Nothing launches Imagination
|
||||
from it. `RepoFrontierPlanCandidate` and `RepoFrontierPlanAdoption` were exposed
|
||||
through public persistence functions, allowing callers to author documents in
|
||||
Imagination and Mind namespaces without either organ participating. Adopt then
|
||||
blocked duplicate planning requests but changed no model bytes and affected no
|
||||
Hands route. The type names were substituting for cognition.
|
||||
The planning family now has a real Self-to-Imagination launch artery. Self's
|
||||
`RepoFrontierPlanningRequest` binds the uniquely admitted current model, exact
|
||||
actionable frontier, runtime, and authoritative thread. Coordinator commit
|
||||
replays those bytes, injects the exact typed Imagination projection, and
|
||||
atomically persists a request-keyed launch binding whose hash covers the stored
|
||||
worker launch document. Caller-authored projections, mixed authority carriers,
|
||||
stale requests, causal substitutions, and duplicate/racing launches are
|
||||
refused without loser artifacts.
|
||||
|
||||
The public candidate/adoption writers are removed. The existing internal
|
||||
functions remain temporarily as test scaffolding while the real nerve replaces
|
||||
them; they are not production authority.
|
||||
The downstream nerve is still incomplete. Imagination has no exclusive
|
||||
immutable result carrying the candidate; Mind has no correlated admission; the
|
||||
old internal candidate/adoption fixture writers still exist; and Adopt still
|
||||
changes no RepoModel bytes or Hands truth.
|
||||
|
||||
The public candidate/adoption writers remain removed. The existing internal
|
||||
functions are test scaffolding only and die when result correlation and Mind
|
||||
admission replace them.
|
||||
|
||||
## Target authority
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -714,4 +714,4 @@ current_status:
|
|||
- Service lifecycle receipt history is now event-only: `load_epiphany_cultmesh_daemon_service_lifecycle_receipts` filters out the `epiphany-local/daemon-service-lifecycle-receipt/latest` mirror, while `load_latest_epiphany_cultmesh_daemon_service_lifecycle_receipt` remains the latest-state reader. Focused unit test `service_lifecycle_receipt_history_excludes_latest_mirror` proves two per-receipt writes return exactly two history records and latest points at the second. Fresh smoke `verse-query-lifecycle-history-filter-smoke` and live overview/triage `local-20260618-145632-497-efac6f0d` / `local-20260618-145632-321-867947af` prove compact swarm sights still expose service lifecycle attention plus the present elevated runbook artifact without daemon pokes or service mutation. Do not use a broad unscoped cargo test filter for this check on Windows; it fans out across bin harnesses and hit a broken pipe during this pass.
|
||||
- `epiphany-verse-query seed-compact` is the compact seed receipt for wrappers and read-only rites. The older `seed` command still prints the full local Verse context for explicit debugging, but compact wrapper modes now avoid full `seed`/final `query` readback after the combined overview proof exposed Windows debug stack overflow from full-context serialization pressure.
|
||||
last_completed_repo_swarm_cut: Huginn live-fire has crossed the first real repo Body from Persona pressure to branch-local Hands commit and Soul/Mind closure, and now has repeatable closure and redacted-public-proof mouths. On `E:\Projects\Huginn` branch `epiphany/huginn/first-awakening`, `tools/epiphany_local_run.ps1 -Mode repo-persona-intake` accepted item `huginn-inspection-readback-livefire`, Weksa/Persona intake remained sealed, and `epiphany-work derive-plan` lowered it to model-authored safe family `repo.status_section` for `README.md` only. `tools/epiphany_local_run.ps1 -Mode repo-swarm-run` advanced four bounded pulses: run-from-plan, adopt-from-plan, execute-from-plan, and close-from-execute. Huginn commit `e9b07c38fceabbdc89ea35c2156326c80c0393a2` updated only `README.md`; closure receipt `.epiphany\work\work-close-huginn-inspection-readback-livefire.json` records status `closed`, Soul verdict `passed`, Mind state commit `repo-work-close-huginn-inspection-readback-livefire-mind-commit`, safe family `repo.status_section`, path scope `README.md`, Bifrost publication gate, and `privateStateExposed=false`. Verification also ran Huginn's TypeScript compiler through concrete Node path: `C:\Program Files\nodejs\node.exe E:\Projects\Huginn\node_modules\typescript\bin\tsc -p tsconfig.json` passed. Native verifier `epiphany-repo-livefire-closure-smoke --workspace E:\Projects\Huginn --item huginn-inspection-readback-livefire --expected-path README.md --expected-family repo.status_section --expected-gate awaiting-publication --expected-blocker bifrost-publication-missing` checks closure receipt, Mind map entry, overview gate/blocker, authority seals, and `git show` changed paths, returning `status=ok`, `soulVerdict=passed`, `publicationGate=Bifrost`, and `privateStateExposed=false`. Wrapper `tools/epiphany_local_run.ps1 -Mode repo-livefire-closure -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exposes the same read-only proof through the local operator mouth; artifact `local-20260621-190156-803-2f85ba15` printed commit, path, gate, blocker, Soul verdict, Mind commit receipt, and `privateStateExposed=False` without push, merge, publication, or target mutation. New wrapper `tools/epiphany_local_run.ps1 -Mode repo-work-public-proof -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exports the redacted proof bundle and repo-local Verse index without publishing; artifact `local-20260621-190645-345-4555b1dc` wrote `.epiphany\public\proof-bundles\repo-work-public-proof-huginn-inspection-readback-livefire.json`, proof id `repo-work-public-proof-huginn-inspection-readback-livefire`, SHA-256 `857545b199f3e37eb06c2e757e5cfc201958e46667bb1f8403fd333a22620f63`, nine artifact rows, zero publication rows, and `privateStateExposed=False`. swarm overview readback `local-20260621-190701-140-b9519ec6` sees the `REPO-WORK-PUBLIC-PROOF` row and action row from Huginn's repo-local Verse with the same SHA-256 while Bifrost publication remains missing. Launcher compact modes are now named compact operator-context modes instead of read-only modes, and `bifrost-public-proof` summaries print item, target public Verse, room, proof SHA-256, review receipts, and credit receipts so publication receipts are auditable without claiming upstream sync.
|
||||
next_action: Build the repo-frontier planning nerve from the authority map in notes/repo-frontier-planning-authority-map.md. Self's exact current-model planning request is the only sound existing owner. Public put_repo_frontier_plan_candidate and put_repo_frontier_plan_adoption exports are cut because they let callers impersonate Imagination and Mind; the remaining internal functions are fixture scaffolding and must die when replaced. Next add one coordinator-owned typed Imagination context projection and request-keyed atomic launch binding, one exclusive planning-request echo on an immutable v1 worker result containing the candidate, and one Mind-only correlated Adopt/Refuse/Hold admission. Adopt must cause a narrow RepoModel transition that makes downstream Hands truth real; it cannot merely suppress later planning while routes ignore it. Refuse/Hold remain inert immutable receipts. Generic Imagination statePatch.planning is not this authority. After the nerve lands, add the production semantic index over typed graph claims so Modeling maintains a live searchable body map instead of forcing Hands to rediscover anatomy. Do not revive Proprioception, epiphany-work, metadata adapters, generic work-order routers, or inference from namespaces/prompt prose/evidence coincidence. Keep closure's zero-substring invariant.
|
||||
next_action: Continue the repo-frontier planning nerve from notes/repo-frontier-planning-authority-map.md. The Self-to-Imagination launch artery is now real: planning request v1 binds runtime/thread, coordinator commit replays current model/admission/frontier/challenges/state, injects the typed projection, and atomically persists one request-keyed launch binding with the exact stored launch-document hash; hostile substitutions and a two-launch race leave no loser artifacts. Next add one exclusive planning-request echo on immutable worker result v1 containing the typed candidate, then replace the internal candidate/adoption fixture writers with Mind-only correlated Adopt/Refuse/Hold admission. Adopt must cause a narrow RepoModel transition that makes downstream Hands truth real; Refuse/Hold remain inert immutable receipts. After the nerve lands, add the production semantic index over typed graph claims so Modeling maintains a live searchable body map instead of forcing Hands to rediscover anatomy. Do not revive Proprioception, epiphany-work, metadata adapters, generic work-order routers, or inference from namespaces/prompt prose/evidence coincidence. Keep closure's zero-substring invariant.
|
||||
|
|
|
|||
Reference in a new issue