Close canonical RepoModel organ loop
This commit is contained in:
parent
995d783fd3
commit
0c90e4471d
28 changed files with 4070 additions and 278 deletions
|
|
@ -195,6 +195,10 @@ pub fn epiphany_role_launch_output_schema(role_id: EpiphanyRoleResultRoleId) ->
|
|||
"additionalProperties": false
|
||||
}
|
||||
});
|
||||
if role_id == EpiphanyRoleResultRoleId::Verification {
|
||||
properties["verificationRequestId"] = serde_json::json!({"type": "string", "minLength": 1});
|
||||
properties["frontierRouteId"] = serde_json::json!({"type": "string", "minLength": 1});
|
||||
}
|
||||
let mut required = vec![
|
||||
"roleId",
|
||||
"verdict",
|
||||
|
|
@ -202,6 +206,10 @@ pub fn epiphany_role_launch_output_schema(role_id: EpiphanyRoleResultRoleId) ->
|
|||
"nextSafeMove",
|
||||
"filesInspected",
|
||||
];
|
||||
if role_id == EpiphanyRoleResultRoleId::Verification {
|
||||
required.push("verificationRequestId");
|
||||
required.push("frontierRouteId");
|
||||
}
|
||||
if role_id == EpiphanyRoleResultRoleId::Imagination {
|
||||
if let Some(map) = properties.as_object_mut() {
|
||||
map.insert(
|
||||
|
|
@ -279,42 +287,88 @@ pub fn epiphany_role_launch_output_schema(role_id: EpiphanyRoleResultRoleId) ->
|
|||
} else if role_id == EpiphanyRoleResultRoleId::Modeling {
|
||||
if let Some(map) = properties.as_object_mut() {
|
||||
map.insert(
|
||||
"statePatch".to_string(),
|
||||
"repoModelPatch".to_string(),
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"description": "Required reviewable statePatch for Mind admission from Modeling. Use only graphs, graphFrontier, graphCheckpoint, scratch, investigationCheckpoint, observations, and evidence. The patch must include at least one durable modeling field, not observations/evidence alone.",
|
||||
"anyOf": [
|
||||
{"required": ["graphs"]},
|
||||
{"required": ["graphFrontier"]},
|
||||
{"required": ["graphCheckpoint"]},
|
||||
{"required": ["scratch"]},
|
||||
{"required": ["investigationCheckpoint"]}
|
||||
],
|
||||
"description": "Required typed proposal against the canonical repository model. This is ingress for later review, not admission authority.",
|
||||
"required": ["patch_id", "base_revision", "base_hash", "applied_at", "purpose", "operations"],
|
||||
"properties": {
|
||||
"investigationCheckpoint": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"disposition": {
|
||||
"type": "string",
|
||||
"enum": ["resume_ready", "regather_required"]
|
||||
"patch_id": {"type": "string", "minLength": 1},
|
||||
"base_revision": {"type": "integer", "minimum": 0},
|
||||
"base_hash": {"type": "string", "minLength": 1},
|
||||
"applied_at": {"type": "string", "minLength": 1},
|
||||
"purpose": {
|
||||
"description": "Use {kind: evolution} for ordinary Modeling output; frontier closure requires separately routed authority.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["kind"],
|
||||
"properties": {"kind": {"const": "evolution"}},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["kind", "route_id", "soul_verdict_receipt_id"],
|
||||
"properties": {
|
||||
"kind": {"const": "incorporate_frontier_verdict"},
|
||||
"route_id": {"type": "string", "minLength": 1},
|
||||
"soul_verdict_receipt_id": {"type": "string", "minLength": 1}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"operations": {"type": "array", "minItems": 1, "items": {"type": "object"}}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"repoFrontierModelingRequestId".to_string(),
|
||||
serde_json::json!({"type": "string", "minLength": 1}),
|
||||
);
|
||||
map.insert(
|
||||
"statePatch".to_string(),
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"description": "Optional generic Mind-reviewable observations/evidence only. Repository anatomy belongs exclusively in repoModelPatch.",
|
||||
"properties": {
|
||||
"observations": {"type": "array"},
|
||||
"evidence": {"type": "array"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}),
|
||||
);
|
||||
}
|
||||
required.push("frontierNodeIds");
|
||||
required.push("statePatch");
|
||||
required.push("repoModelPatch");
|
||||
}
|
||||
serde_json::json!({
|
||||
let mut schema = serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": properties,
|
||||
"required": required,
|
||||
"additionalProperties": true
|
||||
})
|
||||
});
|
||||
if role_id == EpiphanyRoleResultRoleId::Modeling {
|
||||
schema["allOf"] = serde_json::json!([{
|
||||
"if": {
|
||||
"properties": {
|
||||
"repoModelPatch": {
|
||||
"properties": {
|
||||
"purpose": {
|
||||
"properties": {"kind": {"const": "incorporate_frontier_verdict"}},
|
||||
"required": ["kind"]
|
||||
}
|
||||
},
|
||||
"required": ["purpose"]
|
||||
}
|
||||
},
|
||||
"required": ["repoModelPatch"]
|
||||
},
|
||||
"then": {"required": ["repoFrontierModelingRequestId"]}
|
||||
}]);
|
||||
}
|
||||
schema
|
||||
}
|
||||
|
||||
pub fn epiphany_reorient_launch_output_schema() -> serde_json::Value {
|
||||
|
|
@ -358,7 +412,7 @@ const EPIPHANY_SPECIALIST_PROMPTS_TOML: &str = include_str!("prompts/epiphany_sp
|
|||
const EPIPHANY_WORKER_BOUNDARY_PROMPT: &str = r#"## Epiphany Worker Boundary
|
||||
You are one bounded Epiphany worker for this launch only. Your authority comes from the typed launch document, the role-local instruction, and the declared output contract.
|
||||
Do the role, name uncertainty, and return the required JSON object. Do not become the coordinator, do not accept or promote your own output, do not invent durable state outside an allowed statePatch, and do not treat model transport or Codex machinery as prompt authority.
|
||||
If you learned a durable role-local habit, you may include a bounded selfPatch. Project truth belongs in statePatch or evidence, not memory."#;
|
||||
If you learned a durable role-local habit, you may include a bounded selfPatch. Project truth belongs in the role's typed output artifact or evidence, not memory."#;
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct EpiphanySpecialistPromptConfig {
|
||||
|
|
@ -822,6 +876,31 @@ mod tests {
|
|||
assert!(!prompt.contains("Heartbeat: every lane"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modeling_schema_exposes_only_typed_evolution_or_verdict_incorporation() {
|
||||
let schema = epiphany_role_launch_output_schema(EpiphanyRoleResultRoleId::Modeling);
|
||||
let purposes = schema["properties"]["repoModelPatch"]["properties"]["purpose"]["oneOf"]
|
||||
.as_array()
|
||||
.expect("typed Modeling purpose alternatives");
|
||||
assert_eq!(purposes.len(), 2);
|
||||
assert_eq!(purposes[0]["properties"]["kind"]["const"], "evolution");
|
||||
assert_eq!(
|
||||
purposes[1]["properties"]["kind"]["const"],
|
||||
"incorporate_frontier_verdict"
|
||||
);
|
||||
assert!(
|
||||
purposes[1]["required"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|value| value == "route_id")
|
||||
);
|
||||
assert_eq!(
|
||||
schema["allOf"][0]["then"]["required"][0],
|
||||
"repoFrontierModelingRequestId"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reorient_worker_prompt_is_bounded_not_full_persistent_memory() {
|
||||
let prompt = build_epiphany_reorient_launch_instruction(CoreEpiphanyReorientAction::Resume);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ use epiphany_core::HANDS_COMMAND_RECEIPT_TYPE;
|
|||
use epiphany_core::HANDS_COMMIT_RECEIPT_TYPE;
|
||||
use epiphany_core::HANDS_PATCH_RECEIPT_TYPE;
|
||||
use epiphany_core::HandsActionIntent;
|
||||
use epiphany_core::REPO_FRONTIER_HANDS_AUTHORITY_CONTRACT;
|
||||
use epiphany_core::REPO_FRONTIER_HANDS_AUTHORITY_SCHEMA_VERSION;
|
||||
use epiphany_core::RepoFrontierHandsAuthority;
|
||||
use epiphany_core::RuntimeSpineEventOptions;
|
||||
use epiphany_core::RuntimeSpineInitOptions;
|
||||
use epiphany_core::RuntimeSpineSessionOptions;
|
||||
|
|
@ -21,8 +24,10 @@ use epiphany_core::load_epiphany_cultmesh_swarm_brake;
|
|||
use epiphany_core::put_coordinator_run_receipt;
|
||||
use epiphany_core::put_hands_action_intent;
|
||||
use epiphany_core::put_hands_action_review;
|
||||
use epiphany_core::put_repo_frontier_hands_authority;
|
||||
use epiphany_core::put_substrate_gate_repo_access_grant_receipt;
|
||||
use epiphany_core::runtime_spine_status;
|
||||
use epiphany_core::select_and_commit_repo_frontier_route;
|
||||
use epiphany_core::substrate_gate_coordinator_implementation_grant;
|
||||
use serde_json::Value;
|
||||
use serde_json::json;
|
||||
|
|
@ -802,9 +807,10 @@ fn record_hands_implementation_gate(
|
|||
artifact_dir: &Path,
|
||||
thread_id: &str,
|
||||
step_index: usize,
|
||||
status: &Value,
|
||||
_status: &Value,
|
||||
) -> Result<Value> {
|
||||
let requested_at = now();
|
||||
let route = select_and_commit_repo_frontier_route(runtime_store, &requested_at)?;
|
||||
let suffix = format!(
|
||||
"{}-{}-{}",
|
||||
sanitize_id(thread_id),
|
||||
|
|
@ -813,7 +819,7 @@ fn record_hands_implementation_gate(
|
|||
);
|
||||
let runtime_job_id = format!("hands-implementation-{suffix}");
|
||||
let grant_id = format!("substrate-grant-{runtime_job_id}");
|
||||
let requested_paths = implementation_requested_paths(status);
|
||||
let requested_paths = route.source_scope.clone();
|
||||
|
||||
let substrate_grant = substrate_gate_coordinator_implementation_grant(
|
||||
grant_id.clone(),
|
||||
|
|
@ -862,6 +868,22 @@ fn record_hands_implementation_gate(
|
|||
HANDS_COMMIT_RECEIPT_TYPE.to_string(),
|
||||
];
|
||||
put_hands_action_review(runtime_store, &review)?;
|
||||
let authority = RepoFrontierHandsAuthority {
|
||||
schema_version: REPO_FRONTIER_HANDS_AUTHORITY_SCHEMA_VERSION.to_string(),
|
||||
authority_id: format!("repo-frontier-hands-authority-{}", intent.intent_id),
|
||||
route_id: route.route_id.clone(),
|
||||
model_revision: route.model_revision,
|
||||
model_hash: route.model_hash.clone(),
|
||||
frontier_item_id: route.frontier_item_id.clone(),
|
||||
frontier_item_hash: route.frontier_item_hash.clone(),
|
||||
hands_intent_id: intent.intent_id.clone(),
|
||||
hands_review_id: review.review_id.clone(),
|
||||
substrate_grant_receipt_id: grant_id.clone(),
|
||||
requested_paths: requested_paths.clone(),
|
||||
granted_at: review.reviewed_at.clone(),
|
||||
contract: REPO_FRONTIER_HANDS_AUTHORITY_CONTRACT.to_string(),
|
||||
};
|
||||
put_repo_frontier_hands_authority(runtime_store, &authority)?;
|
||||
|
||||
Ok(json!({
|
||||
"status": "ready",
|
||||
|
|
@ -869,6 +891,10 @@ fn record_hands_implementation_gate(
|
|||
"substrateGateGrantReceiptId": grant_id,
|
||||
"intentId": intent.intent_id,
|
||||
"reviewId": review.review_id,
|
||||
"routeId": route.route_id,
|
||||
"modelRevision": route.model_revision,
|
||||
"modelHash": route.model_hash,
|
||||
"frontierItemId": route.frontier_item_id,
|
||||
"requestedPaths": requested_paths,
|
||||
"requiredReceipts": review.required_receipts,
|
||||
"recordPassCommand": hands_record_pass_command(runtime_store, artifact_dir),
|
||||
|
|
@ -905,49 +931,6 @@ fn hands_record_pass_command(runtime_store: &Path, artifact_dir: &Path) -> Value
|
|||
})
|
||||
}
|
||||
|
||||
fn implementation_requested_paths(status: &Value) -> Vec<String> {
|
||||
let mut paths = Vec::new();
|
||||
for pointer in [
|
||||
"/scene/scene/investigationCheckpoint/codeRefs",
|
||||
"/scene/scene/investigationCheckpoint/code_refs",
|
||||
"/scene/scene/graphFrontier/dirtyPaths",
|
||||
"/scene/scene/graphFrontier/dirty_paths",
|
||||
] {
|
||||
collect_requested_paths_at(status, pointer, &mut paths);
|
||||
}
|
||||
paths.sort();
|
||||
paths.dedup();
|
||||
if paths.is_empty() {
|
||||
paths.push(".".to_string());
|
||||
}
|
||||
paths
|
||||
}
|
||||
|
||||
fn collect_requested_paths_at(status: &Value, pointer: &str, paths: &mut Vec<String>) {
|
||||
match status.pointer(pointer) {
|
||||
Some(Value::Array(items)) => {
|
||||
for item in items {
|
||||
if let Some(path) = item
|
||||
.as_str()
|
||||
.or_else(|| item.get("path").and_then(Value::as_str))
|
||||
{
|
||||
push_requested_path(paths, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Value::String(path)) => push_requested_path(paths, path),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_requested_path(paths: &mut Vec<String>, path: &str) {
|
||||
let normalized = path.trim().replace('\\', "/");
|
||||
if normalized.is_empty() {
|
||||
return;
|
||||
}
|
||||
paths.push(normalized);
|
||||
}
|
||||
|
||||
fn launch_role(
|
||||
runtime_store: &Path,
|
||||
thread_id: &str,
|
||||
|
|
@ -1890,35 +1873,6 @@ mod tests {
|
|||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implementation_requested_paths_use_checkpoint_and_frontier_scope() {
|
||||
let status = json!({
|
||||
"scene": {
|
||||
"scene": {
|
||||
"investigationCheckpoint": {
|
||||
"codeRefs": [
|
||||
{"path": "src\\main.rs"},
|
||||
{"path": "src/main.rs"},
|
||||
{"path": " notes/demo.md "}
|
||||
]
|
||||
},
|
||||
"graphFrontier": {
|
||||
"dirtyPaths": ["tests/demo.rs"]
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
implementation_requested_paths(&status),
|
||||
vec![
|
||||
"notes/demo.md".to_string(),
|
||||
"src/main.rs".to_string(),
|
||||
"tests/demo.rs".to_string()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hands_implementation_gate_persists_grant_intent_and_review() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ use epiphany_core::interpret_runtime_role_worker_result;
|
|||
use epiphany_core::read_accepted_coordinator_state;
|
||||
use epiphany_core::recommend_crrc_action;
|
||||
use epiphany_core::recommend_reorientation;
|
||||
use epiphany_core::runtime_has_actionable_hands_frontier;
|
||||
use epiphany_core::runtime_job_snapshot;
|
||||
use epiphany_core::runtime_role_worker_result;
|
||||
use epiphany_state_model::EpiphanyThreadState;
|
||||
|
|
@ -408,6 +409,8 @@ fn run_native_status(args: &Args) -> Result<Value> {
|
|||
verification_finding.as_ref(),
|
||||
None,
|
||||
);
|
||||
let hands_frontier_ready = runtime_has_actionable_hands_frontier(&runtime_store_path)
|
||||
.context("failed to derive actionable Hands frontier from runtime-spine state")?;
|
||||
let coordinator = derive_coordinator_status(EpiphanyCoordinatorStatusInput {
|
||||
state_status,
|
||||
checkpoint_present: state_ref
|
||||
|
|
@ -441,6 +444,7 @@ fn run_native_status(args: &Args) -> Result<Value> {
|
|||
.verification_result_allows_implementation,
|
||||
verification_result_needs_evidence: finding_signals.verification_result_needs_evidence,
|
||||
reorient_finding_accepted: finding_signals.reorient_finding_accepted,
|
||||
hands_frontier_ready,
|
||||
});
|
||||
let coordinator_json = coordinator_status_json(&coordinator)?;
|
||||
let tool_invocations = native_tool_invocation_surface(&runtime_store_path)?;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use crate::coordinator_results::latest_runtime_link;
|
|||
use crate::coordinator_state::changed_fields;
|
||||
use crate::*;
|
||||
use epiphany_state_model::EpiphanyThreadState;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
|
@ -205,6 +206,27 @@ pub fn accept_coordinator_role_finding(
|
|||
available.push(SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_TYPE.to_string());
|
||||
}
|
||||
} else if role_id == EpiphanyRoleResultRoleId::Verification {
|
||||
let request_id = finding
|
||||
.verification_request_id
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Verification finding omitted verificationRequestId"))?;
|
||||
finding
|
||||
.frontier_route_id
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Verification finding omitted frontierRouteId"))?;
|
||||
let request = crate::runtime_repo_frontier_verification_request(store, request_id)?
|
||||
.ok_or_else(|| anyhow::anyhow!("Verification finding names a missing request"))?;
|
||||
crate::put_repo_frontier_verification_request(store, &request)?;
|
||||
validate_verification_finding_binding(&finding, &request)?;
|
||||
if request.hands_intent_id.trim().is_empty()
|
||||
|| request.hands_patch_receipt_id.trim().is_empty()
|
||||
|| request.hands_command_receipt_id.trim().is_empty()
|
||||
|| request.hands_commit_receipt_id.trim().is_empty()
|
||||
{
|
||||
return Err(anyhow::anyhow!(
|
||||
"Verification finding does not echo its exact frontier request and route"
|
||||
));
|
||||
}
|
||||
let verdict = soul_verdict_receipt_from_verification_finding(
|
||||
format!("soul-verdict-{}", update.accepted_receipt_id),
|
||||
&finding,
|
||||
|
|
@ -225,6 +247,44 @@ pub fn accept_coordinator_role_finding(
|
|||
update.state_update.clone(),
|
||||
reference_turn_id,
|
||||
)?;
|
||||
if role_id == EpiphanyRoleResultRoleId::Modeling {
|
||||
let result_id = finding
|
||||
.runtime_result_id
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Modeling finding has no runtime result id"))?;
|
||||
let job_id = finding
|
||||
.runtime_job_id
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Modeling finding has no runtime job id"))?;
|
||||
let result = runtime_role_worker_result(store, job_id)?
|
||||
.ok_or_else(|| anyhow::anyhow!("Modeling runtime result is missing"))?;
|
||||
if result.result_id != result_id {
|
||||
return Err(anyhow::anyhow!("Modeling finding/result identity mismatch"));
|
||||
}
|
||||
let patch_bytes = result
|
||||
.repo_model_patch_msgpack
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Modeling result has no repoModelPatch"))?;
|
||||
let patch = result
|
||||
.repo_model_patch()?
|
||||
.ok_or_else(|| anyhow::anyhow!("Modeling repoModelPatch failed to decode"))?;
|
||||
let candidate_review = RepoModelAdmissionReview {
|
||||
schema_version: REPO_MODEL_ADMISSION_REVIEW_SCHEMA_VERSION.to_string(),
|
||||
review_id: format!("repo-model-review-{result_id}"),
|
||||
result_id: result_id.to_string(),
|
||||
job_id: job_id.to_string(),
|
||||
patch_id: patch.patch_id.clone(),
|
||||
patch_sha256: format!("{:x}", Sha256::digest(patch_bytes)),
|
||||
base_revision: patch.base_revision,
|
||||
base_hash: patch.base_hash.clone(),
|
||||
decision: MindGatewayDecision::Accept,
|
||||
evidence_ids: finding.evidence_ids.clone(),
|
||||
reviewed_at: accepted_at.clone(),
|
||||
contract: REPO_MODEL_ADMISSION_CONTRACT.to_string(),
|
||||
};
|
||||
let review = stable_repo_model_admission_review(store, candidate_review)?;
|
||||
commit_repo_model_admission(store, result_id, &review)?;
|
||||
}
|
||||
let commit = mind_state_commit_receipt(
|
||||
format!("mind-commit-{}", update.accepted_receipt_id),
|
||||
&update.mind_review,
|
||||
|
|
@ -252,6 +312,48 @@ pub fn accept_coordinator_role_finding(
|
|||
})
|
||||
}
|
||||
|
||||
fn stable_repo_model_admission_review(
|
||||
store: &Path,
|
||||
candidate: RepoModelAdmissionReview,
|
||||
) -> anyhow::Result<RepoModelAdmissionReview> {
|
||||
match coordinator_acceptance_cache(store)?
|
||||
.get::<RepoModelAdmissionReview>(&candidate.review_id)?
|
||||
{
|
||||
Some(existing)
|
||||
if existing.result_id == candidate.result_id
|
||||
&& existing.job_id == candidate.job_id
|
||||
&& existing.patch_id == candidate.patch_id
|
||||
&& existing.patch_sha256 == candidate.patch_sha256
|
||||
&& existing.base_revision == candidate.base_revision
|
||||
&& existing.base_hash == candidate.base_hash
|
||||
&& existing.decision == candidate.decision
|
||||
&& existing.evidence_ids == candidate.evidence_ids
|
||||
&& existing.schema_version == candidate.schema_version
|
||||
&& existing.contract == candidate.contract =>
|
||||
{
|
||||
Ok(existing)
|
||||
}
|
||||
Some(_) => Err(anyhow::anyhow!(
|
||||
"stable repo model review id belongs to different admission bytes"
|
||||
)),
|
||||
None => Ok(candidate),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_verification_finding_binding(
|
||||
finding: &EpiphanyRoleFindingInterpretation,
|
||||
request: &RepoFrontierVerificationRequest,
|
||||
) -> anyhow::Result<()> {
|
||||
if finding.verification_request_id.as_deref() != Some(request.request_id.as_str())
|
||||
|| finding.frontier_route_id.as_deref() != Some(request.route_id.as_str())
|
||||
{
|
||||
return Err(anyhow::anyhow!(
|
||||
"Verification finding does not echo its exact frontier request and route"
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn completed_role_finding(
|
||||
runtime_store_path: Option<&Path>,
|
||||
state: &EpiphanyThreadState,
|
||||
|
|
@ -563,7 +665,11 @@ pub fn build_native_role_acceptance_update(
|
|||
));
|
||||
}
|
||||
};
|
||||
if role_id != EpiphanyRoleResultRoleId::Verification && finding.state_patch.is_none() {
|
||||
if matches!(
|
||||
role_id,
|
||||
EpiphanyRoleResultRoleId::Imagination | EpiphanyRoleResultRoleId::Research
|
||||
) && finding.state_patch.is_none()
|
||||
{
|
||||
return Err("completed role finding did not include a reviewable statePatch".to_string());
|
||||
}
|
||||
if !errors.is_empty() {
|
||||
|
|
@ -738,6 +844,57 @@ fn role_label_lower(role_id: EpiphanyRoleResultRoleId) -> &'static str {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn verification_finding_refuses_swapped_request_or_route() {
|
||||
let request = RepoFrontierVerificationRequest {
|
||||
schema_version: REPO_FRONTIER_VERIFICATION_REQUEST_SCHEMA_VERSION.to_string(),
|
||||
request_id: "verification-request-1".to_string(),
|
||||
route_id: "frontier-route-1".to_string(),
|
||||
model_revision: 1,
|
||||
model_hash: "model-hash".to_string(),
|
||||
frontier_item_id: "frontier-1".to_string(),
|
||||
frontier_item_hash: "frontier-hash".to_string(),
|
||||
hands_intent_id: "intent-1".to_string(),
|
||||
hands_review_id: "review-1".to_string(),
|
||||
hands_patch_receipt_id: "patch-1".to_string(),
|
||||
hands_command_receipt_id: "command-1".to_string(),
|
||||
hands_commit_receipt_id: "commit-1".to_string(),
|
||||
requested_at: "2026-07-13T00:00:00Z".to_string(),
|
||||
contract: REPO_FRONTIER_VERIFICATION_REQUEST_CONTRACT.to_string(),
|
||||
};
|
||||
let mut finding = EpiphanyRoleFindingInterpretation {
|
||||
verdict: Some("pass".to_string()),
|
||||
summary: Some("verified".to_string()),
|
||||
next_safe_move: None,
|
||||
checkpoint_summary: None,
|
||||
scratch_summary: None,
|
||||
files_inspected: Vec::new(),
|
||||
frontier_node_ids: Vec::new(),
|
||||
evidence_ids: Vec::new(),
|
||||
artifact_refs: Vec::new(),
|
||||
runtime_result_id: Some("result-1".to_string()),
|
||||
runtime_job_id: Some("job-1".to_string()),
|
||||
open_questions: Vec::new(),
|
||||
evidence_gaps: Vec::new(),
|
||||
risks: Vec::new(),
|
||||
state_patch: None,
|
||||
repo_model_patch: None,
|
||||
self_patch: None,
|
||||
self_persistence: None,
|
||||
job_error: None,
|
||||
item_error: None,
|
||||
verification_request_id: Some(request.request_id.clone()),
|
||||
frontier_route_id: Some(request.route_id.clone()),
|
||||
};
|
||||
validate_verification_finding_binding(&finding, &request).expect("exact binding");
|
||||
|
||||
finding.frontier_route_id = Some("frontier-route-2".to_string());
|
||||
assert!(validate_verification_finding_binding(&finding, &request).is_err());
|
||||
finding.frontier_route_id = Some(request.route_id.clone());
|
||||
finding.verification_request_id = Some("verification-request-2".to_string());
|
||||
assert!(validate_verification_finding_binding(&finding, &request).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completed_finding_admission_refuses_missing_binding() {
|
||||
let error = completed_role_finding(
|
||||
|
|
@ -905,4 +1062,167 @@ mod tests {
|
|||
assert!(cache.get::<MindStateCommitReceipt>("commit-9")?.is_some());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepted_soul_verdict_preserves_exact_frontier_binding() -> anyhow::Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("acceptance-soul.cc");
|
||||
let state = EpiphanyThreadState {
|
||||
revision: 11,
|
||||
..Default::default()
|
||||
};
|
||||
let review = MindGatewayReview {
|
||||
schema_version: MIND_GATEWAY_REVIEW_SCHEMA_VERSION.to_string(),
|
||||
gateway_id: "review-11".to_string(),
|
||||
source_kind: "role".to_string(),
|
||||
source_role_id: "verification".to_string(),
|
||||
decision: MindGatewayDecision::Accept,
|
||||
allowed_effects: Vec::new(),
|
||||
refused_effects: Vec::new(),
|
||||
reasons: Vec::new(),
|
||||
contract: "test".to_string(),
|
||||
};
|
||||
let commit = mind_state_commit_receipt(
|
||||
"commit-11".to_string(),
|
||||
&review,
|
||||
11,
|
||||
Vec::new(),
|
||||
"2026-07-13T00:00:00Z".to_string(),
|
||||
);
|
||||
let soul = SoulVerdictReceipt {
|
||||
schema_version: SOUL_VERDICT_RECEIPT_SCHEMA_VERSION.to_string(),
|
||||
receipt_id: "soul-11".to_string(),
|
||||
source_result_id: "result-11".to_string(),
|
||||
source_job_id: "job-11".to_string(),
|
||||
verdict: "pass".to_string(),
|
||||
summary: "exact chain verified".to_string(),
|
||||
evidence_ids: Vec::new(),
|
||||
risks: Vec::new(),
|
||||
emitted_at: "2026-07-13T00:00:00Z".to_string(),
|
||||
contract: "test".to_string(),
|
||||
verification_request_id: "verification-request-11".to_string(),
|
||||
frontier_route_id: "frontier-route-11".to_string(),
|
||||
};
|
||||
commit_state_with_mind_witness(
|
||||
&store,
|
||||
"thread-11",
|
||||
&state,
|
||||
&state,
|
||||
&review,
|
||||
&commit,
|
||||
&[EpiphanyAcceptancePrerequisite::Soul(soul.clone())],
|
||||
)?;
|
||||
let cache = coordinator_acceptance_cache(&store)?;
|
||||
let stored = cache.get_required::<SoulVerdictReceipt>("soul-11")?;
|
||||
assert_eq!(stored, soul);
|
||||
assert_eq!(stored.verification_request_id, "verification-request-11");
|
||||
assert_eq!(stored.frontier_route_id, "frontier-route-11");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_model_admission_retry_reuses_review_and_commits_fresh_thread_acceptance()
|
||||
-> anyhow::Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store = temp.path().join("split-model-retry.cc");
|
||||
crate::initialize_runtime_spine(
|
||||
&store,
|
||||
crate::RuntimeSpineInitOptions {
|
||||
runtime_id: "split-model-retry".to_string(),
|
||||
display_name: "Split model retry".to_string(),
|
||||
created_at: "2026-07-13T09:00:00Z".to_string(),
|
||||
},
|
||||
)?;
|
||||
let existing = RepoModelAdmissionReview {
|
||||
schema_version: REPO_MODEL_ADMISSION_REVIEW_SCHEMA_VERSION.to_string(),
|
||||
review_id: "repo-model-review-result-split".to_string(),
|
||||
result_id: "result-split".to_string(),
|
||||
job_id: "job-split".to_string(),
|
||||
patch_id: "patch-split".to_string(),
|
||||
patch_sha256: "a".repeat(64),
|
||||
base_revision: 4,
|
||||
base_hash: "b".repeat(64),
|
||||
decision: MindGatewayDecision::Accept,
|
||||
evidence_ids: vec!["evidence-split".to_string()],
|
||||
reviewed_at: "2026-07-13T09:00:01Z".to_string(),
|
||||
contract: REPO_MODEL_ADMISSION_CONTRACT.to_string(),
|
||||
};
|
||||
let mut cache = coordinator_acceptance_cache(&store)?;
|
||||
cache.put(&existing.review_id, &existing)?;
|
||||
cache.put(
|
||||
"repo-model-admission-repo-model-review-result-split",
|
||||
&crate::RepoModelAdmissionReceipt {
|
||||
schema_version: crate::REPO_MODEL_ADMISSION_RECEIPT_SCHEMA_VERSION.to_string(),
|
||||
receipt_id: "repo-model-admission-repo-model-review-result-split".to_string(),
|
||||
review_id: existing.review_id.clone(),
|
||||
result_id: existing.result_id.clone(),
|
||||
patch_id: existing.patch_id.clone(),
|
||||
patch_sha256: existing.patch_sha256.clone(),
|
||||
previous_revision: 4,
|
||||
previous_hash: existing.base_hash.clone(),
|
||||
admitted_revision: 5,
|
||||
admitted_hash: "c".repeat(64),
|
||||
admitted_at: existing.reviewed_at.clone(),
|
||||
contract: REPO_MODEL_ADMISSION_CONTRACT.to_string(),
|
||||
purpose: crate::RepoModelPatchPurpose::Evolution,
|
||||
frontier_route_id: String::new(),
|
||||
verification_request_id: String::new(),
|
||||
soul_verdict_receipt_id: String::new(),
|
||||
frontier_modeling_request_id: String::new(),
|
||||
},
|
||||
)?;
|
||||
let mut fresh = existing.clone();
|
||||
fresh.reviewed_at = "2026-07-13T09:05:00Z".to_string();
|
||||
assert_eq!(stable_repo_model_admission_review(&store, fresh)?, existing);
|
||||
|
||||
let state = EpiphanyThreadState::default();
|
||||
let next = EpiphanyThreadState {
|
||||
revision: 1,
|
||||
acceptance_receipts: vec![epiphany_state_model::EpiphanyAcceptanceReceipt {
|
||||
id: "accept-modeling-fresh-nonce".to_string(),
|
||||
result_id: "result-split".to_string(),
|
||||
job_id: "job-split".to_string(),
|
||||
binding_id: "modeling-worker".to_string(),
|
||||
surface: "roleAccept".to_string(),
|
||||
role_id: "modeling".to_string(),
|
||||
status: "accepted".to_string(),
|
||||
accepted_at: "2026-07-13T09:05:00Z".to_string(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
let mind = MindGatewayReview {
|
||||
schema_version: MIND_GATEWAY_REVIEW_SCHEMA_VERSION.to_string(),
|
||||
gateway_id: "mind-fresh-nonce".to_string(),
|
||||
source_kind: "role".to_string(),
|
||||
source_role_id: "modeling".to_string(),
|
||||
decision: MindGatewayDecision::Accept,
|
||||
allowed_effects: vec!["state".to_string()],
|
||||
refused_effects: Vec::new(),
|
||||
reasons: Vec::new(),
|
||||
contract: "fresh retry".to_string(),
|
||||
};
|
||||
let commit = mind_state_commit_receipt(
|
||||
"mind-commit-fresh-nonce".to_string(),
|
||||
&mind,
|
||||
next.revision,
|
||||
vec!["AcceptanceReceipts".to_string()],
|
||||
"2026-07-13T09:05:00Z".to_string(),
|
||||
);
|
||||
commit_state_with_mind_witness(&store, "thread-split", &state, &next, &mind, &commit, &[])?;
|
||||
let cache = coordinator_acceptance_cache(&store)?;
|
||||
assert_eq!(
|
||||
cache
|
||||
.get_required::<EpiphanyThreadStateEntry>(THREAD_STATE_KEY)?
|
||||
.state()?
|
||||
.acceptance_receipts[0]
|
||||
.id,
|
||||
"accept-modeling-fresh-nonce"
|
||||
);
|
||||
assert_eq!(
|
||||
cache.get_all::<crate::RepoModelAdmissionReceipt>()?.len(),
|
||||
1
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,14 @@ use crate::EpiphanyMemoryContextPacket;
|
|||
use crate::EpiphanyMemoryContextQuery;
|
||||
use crate::EpiphanyMemoryProfile;
|
||||
use crate::EpiphanyPromptContextInput;
|
||||
use crate::RepoMemoryGraphRefresh;
|
||||
use crate::RuntimeHandsReceiptChainSummary;
|
||||
use crate::ensure_runtime_repo_model;
|
||||
use crate::load_epiphany_cultmesh_cluster_topology;
|
||||
use crate::load_epiphany_cultmesh_status;
|
||||
use crate::load_latest_epiphany_cultmesh_work_loop_telemetry;
|
||||
use crate::load_memory_graph_snapshot;
|
||||
use crate::memory_graph_from_epiphany_graphs;
|
||||
use crate::plan_memory_graph_context_cut;
|
||||
use crate::query_epiphany_local_verse_context;
|
||||
use crate::refresh_or_validate_repo_memory_graph;
|
||||
use crate::render_epiphany_prompt_context;
|
||||
use crate::runtime_hands_command_receipt;
|
||||
use crate::runtime_hands_commit_receipt;
|
||||
|
|
@ -138,6 +137,16 @@ pub fn append_verification_hands_receipt_context(
|
|||
else {
|
||||
return Ok(context);
|
||||
};
|
||||
let verification_request = crate::commit_repo_frontier_verification_request_for_chain(
|
||||
runtime_store_path,
|
||||
&chain,
|
||||
&Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true),
|
||||
)
|
||||
.map_err(|error| format!("failed to commit Soul frontier verification request: {error}"))?;
|
||||
let route =
|
||||
crate::runtime_repo_frontier_route(runtime_store_path, &verification_request.route_id)
|
||||
.map_err(|error| format!("failed to reload Soul frontier route: {error}"))?
|
||||
.ok_or_else(|| "Soul frontier route disappeared after request commit".to_string())?;
|
||||
let telemetry = work_loop_telemetry_from_hands_chain(
|
||||
&chain,
|
||||
runtime_store_path,
|
||||
|
|
@ -155,6 +164,14 @@ pub fn append_verification_hands_receipt_context(
|
|||
},
|
||||
)?;
|
||||
context.push_str("\n\n<verification_work_loop_telemetry>\n");
|
||||
context.push_str(&format!(
|
||||
"verificationRequestId: {}\nfrontierRouteId: {}\nfrontierItemId: {}\nrouteQuestion: {}\nrouteGap: {}\n",
|
||||
verification_request.request_id,
|
||||
route.route_id,
|
||||
route.frontier_item_id,
|
||||
route.question,
|
||||
route.gap,
|
||||
));
|
||||
context.push_str(
|
||||
"Soul is reviewing typed internal CultMesh telemetry for concrete Hands consequence evidence produced after the latest accepted Verification finding.\n",
|
||||
);
|
||||
|
|
@ -227,6 +244,42 @@ pub fn append_modeling_work_loop_telemetry_context(
|
|||
runtime_store_path: &Path,
|
||||
state: &EpiphanyThreadState,
|
||||
) -> Result<String, String> {
|
||||
let Some(accepted_verification) = latest_unique_accepted_verification(state)? else {
|
||||
return Ok(context);
|
||||
};
|
||||
let modeling_request =
|
||||
crate::commit_repo_frontier_modeling_request(runtime_store_path, &accepted_verification)
|
||||
.map_err(|error| {
|
||||
format!("failed to commit typed frontier Modeling request: {error}")
|
||||
})?;
|
||||
let route = crate::runtime_repo_frontier_route(runtime_store_path, &modeling_request.route_id)
|
||||
.map_err(|error| format!("failed to reload Modeling frontier route: {error}"))?
|
||||
.ok_or_else(|| "Modeling frontier route disappeared after request commit".to_string())?;
|
||||
let disposition = match modeling_request.allowed_disposition {
|
||||
crate::RepoFrontierVerdictDisposition::Resolved => "resolved",
|
||||
crate::RepoFrontierVerdictDisposition::Blocked => "blocked",
|
||||
};
|
||||
context.push_str("\n\n<repo_frontier_modeling_request>\n");
|
||||
context.push_str(&format!(
|
||||
"modelingRequestId: {}\nmodelRevision: {}\nmodelHash: {}\nfrontierRouteId: {}\nfrontierItemId: {}\nfrontierItemHash: {}\nverificationRequestId: {}\nsoulVerdictReceiptId: {}\nverificationResultId: {}\nverificationJobId: {}\nverificationAcceptanceReceiptId: {}\nallowedDisposition: {}\nrouteQuestion: {}\nrouteGap: {}\n",
|
||||
modeling_request.request_id,
|
||||
modeling_request.model_revision,
|
||||
modeling_request.model_hash,
|
||||
modeling_request.route_id,
|
||||
modeling_request.frontier_item_id,
|
||||
modeling_request.frontier_item_hash,
|
||||
modeling_request.verification_request_id,
|
||||
modeling_request.soul_verdict_receipt_id,
|
||||
modeling_request.verification_result_id,
|
||||
modeling_request.verification_job_id,
|
||||
modeling_request.verification_acceptance_receipt_id,
|
||||
disposition,
|
||||
route.question,
|
||||
route.gap,
|
||||
));
|
||||
context.push_str("Echo repoFrontierModelingRequestId exactly. Use purpose kind incorporate_frontier_verdict with the exact frontierRouteId and soulVerdictReceiptId. Revise only the routed frontier item to allowedDisposition; this request grants no other model mutation.\n");
|
||||
context.push_str("</repo_frontier_modeling_request>");
|
||||
|
||||
let store = local_verse_store_path(runtime_store_path);
|
||||
let Some(mut telemetry) =
|
||||
load_latest_epiphany_cultmesh_work_loop_telemetry(&store, EPIPHANY_LOCAL_VERSE_RUNTIME_ID)
|
||||
|
|
@ -287,6 +340,35 @@ pub fn append_modeling_work_loop_telemetry_context(
|
|||
Ok(context)
|
||||
}
|
||||
|
||||
fn latest_unique_accepted_verification(
|
||||
state: &EpiphanyThreadState,
|
||||
) -> Result<Option<EpiphanyAcceptanceReceipt>, String> {
|
||||
let accepted = state
|
||||
.acceptance_receipts
|
||||
.iter()
|
||||
.filter(|receipt| {
|
||||
receipt.role_id == "verification"
|
||||
&& receipt.surface == "roleAccept"
|
||||
&& receipt.status == "accepted"
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let Some(latest_at) = accepted
|
||||
.iter()
|
||||
.map(|receipt| receipt.accepted_at.as_str())
|
||||
.max()
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let latest = accepted
|
||||
.into_iter()
|
||||
.filter(|receipt| receipt.accepted_at == latest_at)
|
||||
.collect::<Vec<_>>();
|
||||
if latest.len() != 1 {
|
||||
return Err("latest accepted Verification result is temporally ambiguous; refusing Modeling substitution".to_string());
|
||||
}
|
||||
Ok(Some(latest[0].clone()))
|
||||
}
|
||||
|
||||
fn latest_accepted_verification_timestamp(state: &EpiphanyThreadState) -> Option<&str> {
|
||||
state
|
||||
.acceptance_receipts
|
||||
|
|
@ -640,37 +722,21 @@ fn launch_memory_context(
|
|||
.unwrap_or_else(|_| runtime_store_path.to_path_buf())
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
let stored = load_memory_graph_snapshot(&memory_graph_store).map_err(|error| {
|
||||
format!(
|
||||
"failed to validate memory graph store {}: {error}",
|
||||
memory_graph_store.display()
|
||||
)
|
||||
})?;
|
||||
let canonical = stored.as_ref().is_some_and(|snapshot| {
|
||||
snapshot.model_revision > 0
|
||||
|| !snapshot.model_hash.is_empty()
|
||||
|| !snapshot.frontier.is_empty()
|
||||
});
|
||||
let (snapshot, refresh) = if canonical {
|
||||
(
|
||||
stored.expect("canonical snapshot was present"),
|
||||
RepoMemoryGraphRefresh::Reused,
|
||||
)
|
||||
} else {
|
||||
refresh_or_validate_repo_memory_graph(
|
||||
&memory_graph_store,
|
||||
&source_identity,
|
||||
state,
|
||||
repo_root,
|
||||
format!("bridge-launch-state-rev-{}", state.revision),
|
||||
)
|
||||
.map_err(|error| {
|
||||
format!(
|
||||
"failed to bootstrap memory graph store {}: {error}",
|
||||
memory_graph_store.display()
|
||||
)
|
||||
})?
|
||||
};
|
||||
let bootstrap = memory_graph_from_epiphany_graphs(
|
||||
format!("bridge-launch-state-rev-{}", state.revision),
|
||||
&state.graphs,
|
||||
&source_identity,
|
||||
state.revision,
|
||||
repo_root,
|
||||
)
|
||||
.map_err(|error| format!("failed to derive initial runtime RepoModel: {error}"))?;
|
||||
let (snapshot, migration) = ensure_runtime_repo_model(
|
||||
runtime_store_path,
|
||||
&memory_graph_store,
|
||||
&bootstrap,
|
||||
&Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
)
|
||||
.map_err(|error| format!("failed to load runtime-spine RepoModel: {error}"))?;
|
||||
|
||||
let mut packet = plan_memory_graph_context_cut(
|
||||
&snapshot,
|
||||
|
|
@ -682,12 +748,10 @@ fn launch_memory_context(
|
|||
..Default::default()
|
||||
},
|
||||
);
|
||||
if refresh == RepoMemoryGraphRefresh::Refreshed {
|
||||
packet.warnings.push(format!(
|
||||
"Memory graph store was refreshed from current thread-state repo graph at {}.",
|
||||
memory_graph_store.display()
|
||||
));
|
||||
}
|
||||
packet.warnings.push(format!(
|
||||
"RepoModel is runtime-spine authority via migration receipt {} (source {}).",
|
||||
migration.receipt_id, migration.source_store
|
||||
));
|
||||
if packet.nodes.is_empty() && packet.summaries.is_empty() {
|
||||
packet.warnings.push(
|
||||
"Memory graph context is empty for this launch focus; the accepted repo graph may be thin or stale.".to_string(),
|
||||
|
|
@ -718,6 +782,7 @@ mod tests {
|
|||
use crate::hands_commit_receipt_for_review;
|
||||
use crate::hands_patch_receipt_for_review;
|
||||
use crate::initialize_runtime_spine;
|
||||
use crate::load_memory_graph_snapshot;
|
||||
use crate::memory_graph_model_hash;
|
||||
use crate::open_runtime_spine_heartbeat_job;
|
||||
use crate::put_hands_action_intent;
|
||||
|
|
@ -729,9 +794,131 @@ mod tests {
|
|||
use crate::seed_epiphany_local_verse_context;
|
||||
use crate::write_memory_graph_snapshot;
|
||||
use epiphany_state_model::EpiphanyAcceptanceReceipt;
|
||||
use sha2::Digest;
|
||||
use std::fs;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn admit_and_authorize_context_hands(
|
||||
store: &Path,
|
||||
intent: &HandsActionIntent,
|
||||
review: &crate::HandsActionReview,
|
||||
) -> anyhow::Result<()> {
|
||||
let bootstrap = EpiphanyMemoryGraphSnapshot {
|
||||
schema_version: Some(MEMORY_GRAPH_SCHEMA_VERSION.to_string()),
|
||||
graph_id: "context-runtime-model".to_string(),
|
||||
domains: vec![EpiphanyMemoryDomain {
|
||||
id: "repo".to_string(),
|
||||
profile: crate::EpiphanyMemoryProfile::RepoArchitecture,
|
||||
title: "Repository".to_string(),
|
||||
lifecycle: EpiphanyMemoryLifecycle::Accepted,
|
||||
..Default::default()
|
||||
}],
|
||||
nodes: vec![EpiphanyMemoryNode {
|
||||
id: "claim-context-hands".to_string(),
|
||||
domain_id: "repo".to_string(),
|
||||
profile: crate::EpiphanyMemoryProfile::RepoArchitecture,
|
||||
kind: EpiphanyMemoryNodeKind::RuntimeContract,
|
||||
title: "Context Hands".to_string(),
|
||||
claim: "Verification consumes a routed Hands chain.".to_string(),
|
||||
question: "Is the chain exact?".to_string(),
|
||||
action_implication: "Route the exact source scope.".to_string(),
|
||||
source_hashes: vec!["anchor:missing".to_string()],
|
||||
lifecycle: EpiphanyMemoryLifecycle::Accepted,
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
let legacy = store.with_extension("context-legacy.msgpack");
|
||||
let (current, _) =
|
||||
crate::ensure_runtime_repo_model(store, legacy, &bootstrap, "2026-06-12T00:00:00Z")?;
|
||||
let patch = crate::RepoModelPatch {
|
||||
patch_id: "context-route-patch".to_string(),
|
||||
base_revision: current.model_revision,
|
||||
base_hash: memory_graph_model_hash(¤t)?,
|
||||
applied_at: "2026-06-12T00:00:03Z".to_string(),
|
||||
purpose: crate::RepoModelPatchPurpose::Evolution,
|
||||
operations: vec![crate::RepoModelPatchOperation::UpsertFrontier {
|
||||
item: RepoFrontierItem {
|
||||
id: "context-hands-frontier".to_string(),
|
||||
migration_body: "Verify the routed Hands chain.".to_string(),
|
||||
question: "Does verification see the exact receipts?".to_string(),
|
||||
gap: "Unrouted consequences are invalid.".to_string(),
|
||||
target_claim_ids: vec!["claim-context-hands".to_string()],
|
||||
source_scope: intent.requested_paths.clone(),
|
||||
recommended_next_organ: "Hands".to_string(),
|
||||
status: RepoFrontierStatus::Active,
|
||||
..Default::default()
|
||||
},
|
||||
}],
|
||||
};
|
||||
let patch_bytes = rmp_serde::to_vec_named(&patch)?;
|
||||
let result = crate::EpiphanyRuntimeRoleWorkerResult {
|
||||
schema_version: crate::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION.to_string(),
|
||||
result_id: "context-route-result".to_string(),
|
||||
job_id: "context-route-job".to_string(),
|
||||
role_id: "modeling".to_string(),
|
||||
verdict: "checkpoint-ready".to_string(),
|
||||
summary: "Context route".to_string(),
|
||||
next_safe_move: "Mind admission".to_string(),
|
||||
checkpoint_summary: None,
|
||||
scratch_summary: None,
|
||||
files_inspected: vec!["epiphany-core/src/runtime_spine.rs".to_string()],
|
||||
frontier_node_ids: vec!["claim-context-hands".to_string()],
|
||||
evidence_ids: vec!["context-route-evidence".to_string()],
|
||||
artifact_refs: Vec::new(),
|
||||
open_questions: Vec::new(),
|
||||
evidence_gaps: Vec::new(),
|
||||
risks: Vec::new(),
|
||||
state_patch_msgpack: None,
|
||||
self_patch_msgpack: None,
|
||||
item_error: None,
|
||||
metadata: std::collections::BTreeMap::new(),
|
||||
repo_model_patch_msgpack: Some(patch_bytes.clone()),
|
||||
verification_request_id: None,
|
||||
frontier_route_id: None,
|
||||
repo_frontier_modeling_request_id: None,
|
||||
};
|
||||
crate::put_runtime_role_worker_result(store, &result)?;
|
||||
crate::commit_repo_model_admission(
|
||||
store,
|
||||
&result.result_id,
|
||||
&crate::RepoModelAdmissionReview {
|
||||
schema_version: crate::REPO_MODEL_ADMISSION_REVIEW_SCHEMA_VERSION.to_string(),
|
||||
review_id: "context-route-admission".to_string(),
|
||||
result_id: result.result_id.clone(),
|
||||
job_id: result.job_id.clone(),
|
||||
patch_id: patch.patch_id,
|
||||
patch_sha256: format!("{:x}", sha2::Sha256::digest(&patch_bytes)),
|
||||
base_revision: patch.base_revision,
|
||||
base_hash: patch.base_hash,
|
||||
decision: crate::MindGatewayDecision::Accept,
|
||||
evidence_ids: result.evidence_ids.clone(),
|
||||
reviewed_at: "2026-06-12T00:00:04Z".to_string(),
|
||||
contract: crate::REPO_MODEL_ADMISSION_CONTRACT.to_string(),
|
||||
},
|
||||
)?;
|
||||
let route = crate::select_and_commit_repo_frontier_route(store, "2026-06-12T00:00:05Z")?;
|
||||
crate::put_repo_frontier_hands_authority(
|
||||
store,
|
||||
&crate::RepoFrontierHandsAuthority {
|
||||
schema_version: crate::REPO_FRONTIER_HANDS_AUTHORITY_SCHEMA_VERSION.to_string(),
|
||||
authority_id: "context-route-authority".to_string(),
|
||||
route_id: route.route_id,
|
||||
model_revision: route.model_revision,
|
||||
model_hash: route.model_hash,
|
||||
frontier_item_id: route.frontier_item_id,
|
||||
frontier_item_hash: route.frontier_item_hash,
|
||||
hands_intent_id: intent.intent_id.clone(),
|
||||
hands_review_id: review.review_id.clone(),
|
||||
substrate_grant_receipt_id: intent.substrate_gate_grant_receipt_id.clone(),
|
||||
requested_paths: intent.requested_paths.clone(),
|
||||
granted_at: "2026-06-12T00:00:06Z".to_string(),
|
||||
contract: crate::REPO_FRONTIER_HANDS_AUTHORITY_CONTRACT.to_string(),
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_launch_context_has_no_bridge_or_host_dependency() {
|
||||
let source = include_str!("coordinator_launch_context.rs");
|
||||
|
|
@ -856,7 +1043,7 @@ mod tests {
|
|||
assert!(rendered.contains("Yggdrasil"));
|
||||
assert!(rendered.contains("Memory graph"));
|
||||
assert!(local_verse_store_path(&runtime_store).exists());
|
||||
assert!(memory_graph_store_path(&runtime_store).exists());
|
||||
assert!(runtime_store.exists());
|
||||
|
||||
let launch_request = build_epiphany_role_launch_request_with_dynamic_context(
|
||||
"thread-1",
|
||||
|
|
@ -927,6 +1114,47 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ordinary_modeling_has_no_verdict_request_and_tied_acceptance_refuses_substitution() {
|
||||
let missing_store = Path::new("definitely-missing-runtime.cc");
|
||||
let ordinary = EpiphanyThreadState::default();
|
||||
assert_eq!(
|
||||
append_modeling_work_loop_telemetry_context(
|
||||
"ordinary".to_string(),
|
||||
missing_store,
|
||||
&ordinary,
|
||||
)
|
||||
.expect("ordinary Modeling does not touch verdict authority"),
|
||||
"ordinary"
|
||||
);
|
||||
|
||||
let receipt = |id: &str, result: &str| EpiphanyAcceptanceReceipt {
|
||||
id: id.to_string(),
|
||||
result_id: result.to_string(),
|
||||
job_id: format!("job-{result}"),
|
||||
binding_id: "verification-review-worker".to_string(),
|
||||
surface: "roleAccept".to_string(),
|
||||
role_id: "verification".to_string(),
|
||||
status: "accepted".to_string(),
|
||||
accepted_at: "2026-07-13T07:00:00Z".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
let ambiguous = EpiphanyThreadState {
|
||||
acceptance_receipts: vec![
|
||||
receipt("accept-a", "result-a"),
|
||||
receipt("accept-b", "result-b"),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
let error = append_modeling_work_loop_telemetry_context(
|
||||
"ambiguous".to_string(),
|
||||
missing_store,
|
||||
&ambiguous,
|
||||
)
|
||||
.expect_err("equal-time accepted results must not be selected by adjacency");
|
||||
assert!(error.contains("temporally ambiguous"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verification_launch_context_includes_hands_receipt_chain() -> anyhow::Result<()> {
|
||||
let temp = std::env::temp_dir().join(format!("epiphany-hands-context-{}", Uuid::new_v4()));
|
||||
|
|
@ -952,7 +1180,7 @@ mod tests {
|
|||
role: "epiphany-hands".to_string(),
|
||||
authority_scope: "epiphany.role.implementation".to_string(),
|
||||
requested_action: "continueImplementation".to_string(),
|
||||
requested_paths: vec![".".to_string()],
|
||||
requested_paths: vec!["epiphany-core/src/runtime_spine.rs".to_string()],
|
||||
substrate_gate_grant_receipt_id: "substrate-grant-context".to_string(),
|
||||
requested_at: "2026-06-12T00:00:01Z".to_string(),
|
||||
contract: "Test Hands intent.".to_string(),
|
||||
|
|
@ -962,7 +1190,7 @@ mod tests {
|
|||
&crate::substrate_gate_coordinator_implementation_grant(
|
||||
"substrate-grant-context".to_string(),
|
||||
"hands-job-context".to_string(),
|
||||
vec![".".to_string()],
|
||||
vec!["epiphany-core/src/runtime_spine.rs".to_string()],
|
||||
"2026-06-12T00:00:00Z".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
|
@ -980,6 +1208,7 @@ mod tests {
|
|||
"2026-06-12T00:00:02Z".to_string(),
|
||||
);
|
||||
put_hands_action_review(&runtime_store, &review)?;
|
||||
admit_and_authorize_context_hands(&runtime_store, &intent, &review)?;
|
||||
let patch = hands_patch_receipt_for_review(
|
||||
"hands-patch-context".to_string(),
|
||||
&intent,
|
||||
|
|
@ -1042,6 +1271,10 @@ mod tests {
|
|||
assert!(context.contains("hands-patch-context"));
|
||||
assert!(context.contains("hands-command-context"));
|
||||
assert!(context.contains("hands-commit-context"));
|
||||
assert!(context.contains("verificationRequestId:"));
|
||||
assert!(context.contains("frontierRouteId:"));
|
||||
assert!(context.contains("Does verification see the exact receipts?"));
|
||||
assert!(context.contains("Unrouted consequences are invalid."));
|
||||
assert!(context.contains("resolvedReceiptPayloads"));
|
||||
assert!(context.contains("resolvedReceiptPayloadPreviews"));
|
||||
assert!(context.contains("test result: ok"));
|
||||
|
|
@ -1086,6 +1319,65 @@ mod tests {
|
|||
assertion.contains("verification_launch_context_includes_hands_receipt_chain")
|
||||
}));
|
||||
|
||||
let verification_request_id = context
|
||||
.lines()
|
||||
.find_map(|line| line.strip_prefix("verificationRequestId: "))
|
||||
.expect("rendered exact verification request id");
|
||||
let verification_request = crate::runtime_repo_frontier_verification_request(
|
||||
&runtime_store,
|
||||
verification_request_id,
|
||||
)?
|
||||
.expect("persisted exact verification request");
|
||||
let verification_result = crate::EpiphanyRuntimeRoleWorkerResult {
|
||||
schema_version: crate::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION.to_string(),
|
||||
result_id: "result-verification-context".to_string(),
|
||||
job_id: "verification-job-context".to_string(),
|
||||
role_id: "verification".to_string(),
|
||||
verdict: "pass".to_string(),
|
||||
summary: "accepted prior verification".to_string(),
|
||||
next_safe_move: "Model the verified consequence.".to_string(),
|
||||
checkpoint_summary: None,
|
||||
scratch_summary: None,
|
||||
files_inspected: Vec::new(),
|
||||
frontier_node_ids: Vec::new(),
|
||||
evidence_ids: vec!["verification-proof-context".to_string()],
|
||||
artifact_refs: Vec::new(),
|
||||
open_questions: Vec::new(),
|
||||
evidence_gaps: Vec::new(),
|
||||
risks: Vec::new(),
|
||||
state_patch_msgpack: None,
|
||||
self_patch_msgpack: None,
|
||||
item_error: None,
|
||||
metadata: std::collections::BTreeMap::new(),
|
||||
repo_model_patch_msgpack: None,
|
||||
verification_request_id: Some(verification_request.request_id.clone()),
|
||||
frontier_route_id: Some(verification_request.route_id.clone()),
|
||||
repo_frontier_modeling_request_id: None,
|
||||
};
|
||||
crate::put_runtime_role_worker_result(&runtime_store, &verification_result)?;
|
||||
crate::put_soul_verdict_receipt(
|
||||
&runtime_store,
|
||||
&crate::SoulVerdictReceipt {
|
||||
schema_version: crate::SOUL_VERDICT_RECEIPT_SCHEMA_VERSION.to_string(),
|
||||
receipt_id: "soul-verdict-context".to_string(),
|
||||
source_result_id: verification_result.result_id.clone(),
|
||||
source_job_id: verification_result.job_id.clone(),
|
||||
verdict: verification_result.verdict.clone(),
|
||||
summary: verification_result.summary.clone(),
|
||||
evidence_ids: verification_result.evidence_ids.clone(),
|
||||
risks: verification_result.risks.clone(),
|
||||
emitted_at: "2026-06-12T00:00:07Z".to_string(),
|
||||
contract: "accepted exact verification".to_string(),
|
||||
verification_request_id: verification_request.request_id.clone(),
|
||||
frontier_route_id: verification_request.route_id.clone(),
|
||||
},
|
||||
)?;
|
||||
let mut state_cache = crate::runtime_spine_cache(&runtime_store)?;
|
||||
state_cache.put(
|
||||
crate::THREAD_STATE_KEY,
|
||||
&crate::EpiphanyThreadStateEntry::from_state("context-thread", &state)?,
|
||||
)?;
|
||||
|
||||
let modeling_context = append_modeling_work_loop_telemetry_context(
|
||||
"<epiphany_dynamic_context></epiphany_dynamic_context>".to_string(),
|
||||
&runtime_store,
|
||||
|
|
@ -1096,6 +1388,14 @@ mod tests {
|
|||
assert!(modeling_context.contains("hands-patch-context"));
|
||||
assert!(modeling_context.contains("accept-verification-context"));
|
||||
assert!(modeling_context.contains("Soul acceptance telemetry"));
|
||||
assert!(modeling_context.contains("<repo_frontier_modeling_request>"));
|
||||
assert!(modeling_context.contains("soulVerdictReceiptId: soul-verdict-context"));
|
||||
assert!(modeling_context.contains("verificationResultId: result-verification-context"));
|
||||
assert!(
|
||||
modeling_context
|
||||
.contains("verificationAcceptanceReceiptId: accept-verification-context")
|
||||
);
|
||||
assert!(modeling_context.contains("allowedDisposition: resolved"));
|
||||
let telemetry = load_latest_epiphany_cultmesh_work_loop_telemetry(
|
||||
local_verse_store_path(&runtime_store),
|
||||
EPIPHANY_LOCAL_VERSE_RUNTIME_ID,
|
||||
|
|
|
|||
|
|
@ -300,10 +300,13 @@ fn interpret_role_lifecycle_failure(
|
|||
evidence_gaps: Vec::new(),
|
||||
risks: Vec::new(),
|
||||
state_patch: None,
|
||||
repo_model_patch: None,
|
||||
self_patch: None,
|
||||
self_persistence: None,
|
||||
job_error: Some(result.summary.clone()),
|
||||
item_error: None,
|
||||
verification_request_id: None,
|
||||
frontier_route_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -246,10 +246,13 @@ mod tests {
|
|||
evidence_gaps: Vec::new(),
|
||||
risks: Vec::new(),
|
||||
state_patch: None,
|
||||
repo_model_patch: None,
|
||||
self_patch: None,
|
||||
self_persistence: None,
|
||||
job_error: None,
|
||||
item_error: None,
|
||||
verification_request_id: None,
|
||||
frontier_route_id: None,
|
||||
};
|
||||
let mut patch = EpiphanyRoleStatePatchDocument::default();
|
||||
patch
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ mod process_observation;
|
|||
mod promotion;
|
||||
mod prompt_context;
|
||||
mod proposal;
|
||||
mod repo_model_gateway;
|
||||
mod retrieval;
|
||||
mod runtime_spine;
|
||||
mod soul_gateway;
|
||||
|
|
@ -622,9 +623,10 @@ pub use memory_graph::RepoFrontierStatus;
|
|||
pub use memory_graph::RepoMemoryGraphRefresh;
|
||||
pub use memory_graph::RepoModelPatch;
|
||||
pub use memory_graph::RepoModelPatchOperation;
|
||||
pub use memory_graph::apply_repo_model_patch;
|
||||
pub use memory_graph::RepoModelPatchPurpose;
|
||||
pub use memory_graph::compose_memory_graph_snapshots;
|
||||
pub use memory_graph::derive_memory_graph_freshness;
|
||||
pub use memory_graph::derive_repo_model_patch;
|
||||
pub use memory_graph::lifecycle_allowed_for_profile;
|
||||
pub use memory_graph::load_memory_graph_entry;
|
||||
pub use memory_graph::load_memory_graph_snapshot;
|
||||
|
|
@ -743,6 +745,7 @@ pub use prompt_context::render_epiphany_prompt_context;
|
|||
pub use proposal::EpiphanyMapProposal;
|
||||
pub use proposal::EpiphanyMapProposalInput;
|
||||
pub use proposal::propose_map_update;
|
||||
pub use repo_model_gateway::*;
|
||||
pub use retrieval::EPIPHANY_RETRIEVAL_DEFAULT_LIMIT;
|
||||
pub use retrieval::EPIPHANY_RETRIEVAL_MAX_LIMIT;
|
||||
pub use retrieval::EpiphanyRetrieveQuery;
|
||||
|
|
@ -800,11 +803,15 @@ pub use runtime_spine::advance_repo_work_modeling_route;
|
|||
pub use runtime_spine::append_runtime_event;
|
||||
pub use runtime_spine::clear_epiphany_job_binding_backend;
|
||||
pub use runtime_spine::commit_initial_repo_work_modeling_route;
|
||||
pub use runtime_spine::commit_repo_frontier_modeling_request;
|
||||
pub use runtime_spine::commit_repo_frontier_verification_request_for_chain;
|
||||
pub use runtime_spine::commit_repo_model_admission;
|
||||
pub use runtime_spine::commit_repo_work_hands_grant;
|
||||
pub use runtime_spine::commit_repo_work_map_admission;
|
||||
pub use runtime_spine::complete_runtime_job;
|
||||
pub use runtime_spine::create_runtime_job;
|
||||
pub use runtime_spine::create_runtime_session;
|
||||
pub use runtime_spine::ensure_runtime_repo_model;
|
||||
pub use runtime_spine::ensure_runtime_session;
|
||||
pub use runtime_spine::initialize_runtime_spine;
|
||||
pub use runtime_spine::open_runtime_spine_heartbeat_job;
|
||||
|
|
@ -822,6 +829,8 @@ pub use runtime_spine::put_hands_patch_receipt;
|
|||
pub use runtime_spine::put_hands_pr_receipt;
|
||||
pub use runtime_spine::put_mind_gateway_review;
|
||||
pub use runtime_spine::put_mind_state_commit_receipt;
|
||||
pub use runtime_spine::put_repo_frontier_hands_authority;
|
||||
pub use runtime_spine::put_repo_frontier_verification_request;
|
||||
pub use runtime_spine::put_repo_work_modeling_finding;
|
||||
pub use runtime_spine::put_repo_work_modeling_request;
|
||||
pub use runtime_spine::put_repo_work_plan_adoption_review;
|
||||
|
|
@ -839,6 +848,7 @@ pub use runtime_spine::runtime_hands_commit_receipt;
|
|||
pub use runtime_spine::runtime_hands_patch_receipt;
|
||||
pub use runtime_spine::runtime_hands_pr_receipt;
|
||||
pub use runtime_spine::runtime_hands_receipt_chain_after;
|
||||
pub use runtime_spine::runtime_has_actionable_hands_frontier;
|
||||
pub use runtime_spine::runtime_hello_frame;
|
||||
pub use runtime_spine::runtime_job_snapshot;
|
||||
pub use runtime_spine::runtime_latest_hands_receipt_chain_after;
|
||||
|
|
@ -846,6 +856,8 @@ pub use runtime_spine::runtime_mind_gateway_review;
|
|||
pub use runtime_spine::runtime_mind_state_commit_receipt;
|
||||
pub use runtime_spine::runtime_registered_document_types;
|
||||
pub use runtime_spine::runtime_reorient_worker_result;
|
||||
pub use runtime_spine::runtime_repo_frontier_route;
|
||||
pub use runtime_spine::runtime_repo_frontier_verification_request;
|
||||
pub use runtime_spine::runtime_repo_work_hands_grant;
|
||||
pub use runtime_spine::runtime_repo_work_map_entry;
|
||||
pub use runtime_spine::runtime_repo_work_modeling_finding;
|
||||
|
|
@ -860,6 +872,7 @@ pub use runtime_spine::runtime_spine_status;
|
|||
pub use runtime_spine::runtime_substrate_gate_repo_access_grant_receipt;
|
||||
pub use runtime_spine::runtime_tool_invocation_statuses;
|
||||
pub use runtime_spine::runtime_worker_launch_request;
|
||||
pub use runtime_spine::select_and_commit_repo_frontier_route;
|
||||
pub use runtime_spine::validate_hands_action_authority;
|
||||
pub use runtime_spine::write_runtime_hello_frame;
|
||||
pub use runtime_spine::write_runtime_schema_catalog_json;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use super::RepoFrontierStatus;
|
|||
use super::derive_memory_graph_freshness;
|
||||
use super::ids::normalized_key;
|
||||
use super::ids::stable_memory_graph_id;
|
||||
use super::memory_graph_model_hash;
|
||||
use bm25::Document;
|
||||
use bm25::Language;
|
||||
use bm25::SearchEngineBuilder;
|
||||
|
|
@ -162,6 +163,8 @@ pub fn plan_memory_graph_context_cut(
|
|||
EpiphanyMemoryContextPacket {
|
||||
id: stable_memory_graph_id("memctx", [query.id.as_str(), snapshot.graph_id.as_str()]),
|
||||
query_id: query.id.clone(),
|
||||
repo_model_revision: snapshot.model_revision,
|
||||
repo_model_hash: memory_graph_model_hash(snapshot).unwrap_or_default(),
|
||||
nodes,
|
||||
edges,
|
||||
summaries,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ pub use epiphany_state_model::RepoFrontierItem;
|
|||
pub use epiphany_state_model::RepoFrontierStatus;
|
||||
pub use epiphany_state_model::RepoModelPatch;
|
||||
pub use epiphany_state_model::RepoModelPatchOperation;
|
||||
pub use epiphany_state_model::RepoModelPatchPurpose;
|
||||
|
||||
pub use compose::compose_memory_graph_snapshots;
|
||||
pub use context_cut::plan_memory_graph_context_cut;
|
||||
|
|
@ -44,7 +45,9 @@ pub use store::EpiphanyMemoryGraphEntry;
|
|||
pub use store::MEMORY_GRAPH_KEY;
|
||||
pub use store::MEMORY_GRAPH_SCHEMA_VERSION;
|
||||
pub use store::MEMORY_GRAPH_TYPE;
|
||||
pub use store::apply_repo_model_patch;
|
||||
#[cfg(test)]
|
||||
pub(crate) use store::apply_repo_model_patch;
|
||||
pub use store::derive_repo_model_patch;
|
||||
pub use store::load_memory_graph_entry;
|
||||
pub use store::load_memory_graph_snapshot;
|
||||
pub use store::memory_graph_cache;
|
||||
|
|
|
|||
|
|
@ -804,6 +804,7 @@ mod tests {
|
|||
});
|
||||
ready.wait();
|
||||
let patch = crate::memory_graph::RepoModelPatch {
|
||||
purpose: crate::memory_graph::RepoModelPatchPurpose::Evolution,
|
||||
patch_id: "canonical-wins-refresh-race".to_string(),
|
||||
base_revision: 0,
|
||||
base_hash: crate::memory_graph::memory_graph_model_hash(&legacy)?,
|
||||
|
|
|
|||
|
|
@ -158,7 +158,8 @@ pub fn memory_graph_model_hash(snapshot: &EpiphanyMemoryGraphSnapshot) -> Result
|
|||
|
||||
/// Applies a Modeling patch as one stale-checked replacement of the existing
|
||||
/// CultCache aggregate. Validation occurs before the store is touched.
|
||||
pub fn apply_repo_model_patch(
|
||||
#[cfg(test)]
|
||||
pub(crate) fn apply_repo_model_patch(
|
||||
store_path: impl AsRef<Path>,
|
||||
patch: &RepoModelPatch,
|
||||
) -> Result<EpiphanyMemoryGraphSnapshot> {
|
||||
|
|
@ -184,7 +185,34 @@ pub fn apply_repo_model_patch(
|
|||
let entry: EpiphanyMemoryGraphEntry = rmp_serde::from_slice(&envelope.payload)?;
|
||||
validate_memory_graph_entry(&entry)?;
|
||||
let current = entry.snapshot()?;
|
||||
let current_hash = memory_graph_model_hash(¤t)?;
|
||||
let next = derive_repo_model_patch(¤t, patch)?;
|
||||
let next_entry = EpiphanyMemoryGraphEntry::from_snapshot(&next)?;
|
||||
let next_envelope = memory_graph_envelope(&next_entry, &patch.applied_at)?;
|
||||
if !backing.compare_and_swap_entry(&envelope, next_envelope)? {
|
||||
return Err(anyhow!(
|
||||
"stale repo model patch: aggregate changed during compare-and-swap"
|
||||
));
|
||||
}
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
/// Purely derives and validates the next canonical aggregate. Persistence and
|
||||
/// admission authority remain the caller's responsibility.
|
||||
pub fn derive_repo_model_patch(
|
||||
current: &EpiphanyMemoryGraphSnapshot,
|
||||
patch: &RepoModelPatch,
|
||||
) -> Result<EpiphanyMemoryGraphSnapshot> {
|
||||
if patch.patch_id.trim().is_empty() || patch.applied_at.trim().is_empty() {
|
||||
return Err(anyhow!("repo model patch requires patch_id and applied_at"));
|
||||
}
|
||||
if patch.operations.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"repo model patch must contain at least one operation"
|
||||
));
|
||||
}
|
||||
chrono::DateTime::parse_from_rfc3339(&patch.applied_at)
|
||||
.map_err(|_| anyhow!("repo model patch applied_at must be RFC3339"))?;
|
||||
let current_hash = memory_graph_model_hash(current)?;
|
||||
if patch.base_revision != current.model_revision || patch.base_hash != current_hash {
|
||||
return Err(anyhow!(
|
||||
"stale repo model patch: base revision/hash {}/{} does not match current {}/{}",
|
||||
|
|
@ -194,7 +222,7 @@ pub fn apply_repo_model_patch(
|
|||
current_hash
|
||||
));
|
||||
}
|
||||
let mut next = current;
|
||||
let mut next = current.clone();
|
||||
if next
|
||||
.lifecycle_receipts
|
||||
.iter()
|
||||
|
|
@ -253,14 +281,7 @@ pub fn apply_repo_model_patch(
|
|||
));
|
||||
}
|
||||
next.model_hash = memory_graph_model_hash(&next)?;
|
||||
let next_entry = EpiphanyMemoryGraphEntry::from_snapshot(&next)?;
|
||||
validate_memory_graph_entry(&next_entry)?;
|
||||
let next_envelope = memory_graph_envelope(&next_entry, &patch.applied_at)?;
|
||||
if !backing.compare_and_swap_entry(&envelope, next_envelope)? {
|
||||
return Err(anyhow!(
|
||||
"stale repo model patch: aggregate changed during compare-and-swap"
|
||||
));
|
||||
}
|
||||
validate_memory_graph_entry(&EpiphanyMemoryGraphEntry::from_snapshot(&next)?)?;
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ fn patch_for(
|
|||
operations: Vec<RepoModelPatchOperation>,
|
||||
) -> RepoModelPatch {
|
||||
RepoModelPatch {
|
||||
purpose: RepoModelPatchPurpose::Evolution,
|
||||
patch_id: "patch-1".to_string(),
|
||||
base_revision: snapshot.model_revision,
|
||||
base_hash: memory_graph_model_hash(snapshot).expect("model hash"),
|
||||
|
|
@ -872,6 +873,7 @@ fn repo_model_patch_cross_process_worker() -> Result<()> {
|
|||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
let patch = RepoModelPatch {
|
||||
purpose: RepoModelPatchPurpose::Evolution,
|
||||
patch_id: format!("cross-process-{id}"),
|
||||
base_revision: 0,
|
||||
base_hash,
|
||||
|
|
|
|||
|
|
@ -604,10 +604,13 @@ mod tests {
|
|||
evidence_gaps: Vec::new(),
|
||||
risks: Vec::new(),
|
||||
state_patch: None,
|
||||
repo_model_patch: None,
|
||||
self_patch: None,
|
||||
self_persistence: None,
|
||||
job_error: None,
|
||||
item_error: None,
|
||||
verification_request_id: None,
|
||||
frontier_route_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -297,6 +297,10 @@ pub fn render_epiphany_prompt_context(input: &EpiphanyPromptContextInput) -> Str
|
|||
"- Packet `{}` from query `{}`",
|
||||
input.memory_context.id, input.memory_context.query_id
|
||||
));
|
||||
lines.push(format!(
|
||||
"- Canonical RepoModel: revision={}, hash=`{}`",
|
||||
input.memory_context.repo_model_revision, input.memory_context.repo_model_hash
|
||||
));
|
||||
|
||||
for item in input.memory_context.frontier.iter().take(6) {
|
||||
lines.push(format!(
|
||||
|
|
|
|||
|
|
@ -88,9 +88,9 @@ In the work loop, Modeling follows Soul: after Hands changes the body and Soul v
|
|||
Inspect source before you trust the model; when the map feels warm but the source feels cold, believe the source and mark the wound.
|
||||
Name the living seam, the open wound, and the next safe modeling move in plain language.
|
||||
Return a compact structured result that says whether the checkpoint or map should be updated, what files you inspected, what frontier nodes matter, and where the next agent can safely place its hands.
|
||||
`statePatch` is part of the modeling job. If you return `checkpoint-ready`, `checkpoint-update-needed`, or `regather-needed`, include a Mind-reviewable native state patch using only modeling-safe fields (`graphs`, `graphFrontier`, `graphCheckpoint`, `scratch`, `investigationCheckpoint`, plus optional valid `observations` and `evidence`). The patch must include at least one durable modeling field: `graphs`, `graphFrontier`, `graphCheckpoint`, `scratch`, or `investigationCheckpoint`. Do not return an observations-only patch. Every observation must include `id`, `summary`, `source_kind`, `status`, and at least one `evidence_ids` entry; every new evidence record must include `id`, `kind`, `status`, and `summary`. For graph growth, return the full replacement graph object with the new or revised nodes/edges already woven into the current anatomy. If you believe no durable state change is needed, still return a small `scratch`, `graphCheckpoint`, or `investigationCheckpoint` patch that banks why the existing model is source-grounded; otherwise the coordinator cannot advance the lane.
|
||||
Before returning a `statePatch`, validate it as if acceptance will reject you. `graphs.architecture.nodes[].id` defines the only valid `architecture_node_id` values. `graphs.dataflow.nodes[].id` defines the only valid `dataflow_node_id` values. Every `graphs.links[]` entry must point from one existing dataflow node to one existing architecture node; never put a dataflow id in `architecture_node_id` or an architecture id in `dataflow_node_id`. If a concept needs to exist in both layers, create or reuse one node in each layer and link them explicitly. Every frontier/checkpoint node id you name must exist in the graph you return.
|
||||
Do not confuse the modeling verdict with checkpoint enum values. `verdict` may be `checkpoint-ready`, but `statePatch.investigationCheckpoint.disposition` must be exactly `resume_ready` or `regather_required`; never write `checkpoint_ready`.
|
||||
`repoModelPatch` is required. Return the typed patch against the canonical repository model revision/hash supplied in context. Use explicit node, edge, and frontier operations; preserve exact source-grounded claim ids and dependency ids. This artifact is only a proposal for later Modeling review/admission: do not claim it has been applied.
|
||||
When `<repo_frontier_modeling_request>` is present, echo its exact `modelingRequestId` as `repoFrontierModelingRequestId`, use `purpose.kind=incorporate_frontier_verdict` with its exact `frontierRouteId` and `soulVerdictReceiptId`, and revise only its routed frontier item to its allowed disposition. The typed request is authority; nearby telemetry, timestamps, and prose are not substitutes. When that request is absent, use `purpose.kind=evolution` and do not invent verdict-incorporation authority.
|
||||
Generic `statePatch` is optional and has been stripped of repository-anatomy authority. If present, it may contain only coherent `observations` and `evidence`. Never put graphs, graph frontier, graph checkpoint, scratch, investigation checkpoint, planning, objectives, jobs, or other anatomy in `statePatch`; those old paths are demoted and will be rejected.
|
||||
Do not edit files, do not implement product code, do not verify as if you were the reviewer, do not change jobs/subgoals/objective/invariants/churn/mode, and do not promote or accept your own output.
|
||||
'''
|
||||
|
||||
|
|
@ -99,6 +99,7 @@ Act as the Epiphany verification/review specialist for one bounded pass.
|
|||
You are the Soul of the machine: the promise, invariants, evidence, and user-facing truth must survive contact with the actual code.
|
||||
The Soul improves itself by becoming harder to fool. Preserve the failure pattern, missing proof, stale assumption, invariant wound, or user-truth fracture that would make the next reviewer sharper and less seducible by a tidy story.
|
||||
In the work loop, Soul follows Hands and precedes Modeling: verify the concrete Hands receipts, source diff, command artifacts, and user-facing claim before Modeling updates the body map.
|
||||
Echo the exact `verificationRequestId` and `frontierRouteId` supplied in the launch context. They are required typed identity, not prose you may reinterpret or substitute.
|
||||
When tool access is available, use it before returning `needs-evidence` for missing source, artifact, diff, or receipt-body inspection. The Verification worker may call read-only source tools exposed as `mcp__epiphany_source__read_file`, `mcp__epiphany_source__git_show`, and `mcp__epiphany_source__read_hands_receipt`; use them to inspect the cited changed files, stdout/stderr artifacts, commit diff, and Hands receipt bodies. If a tool call fails, cite the failed tool receipt and the exact remaining blocker instead of asking for generic evidence.
|
||||
Review objective, invariants, recent evidence, observations, role findings, and active frontier with a cold red pen and a working memory of past Jenga pain.
|
||||
Try to falsify the claim before you bless it; passing tests, tidy prose, or a plausible story are not enough if the real objective is still unmeasured.
|
||||
|
|
|
|||
250
epiphany-core/src/repo_model_gateway.rs
Normal file
250
epiphany-core/src/repo_model_gateway.rs
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
use crate::MindGatewayDecision;
|
||||
use cultcache_rs::DatabaseEntry;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const REPO_MODEL_ADMISSION_REVIEW_TYPE: &str = "epiphany.mind.repo_model_admission_review";
|
||||
pub const REPO_MODEL_ADMISSION_REVIEW_SCHEMA_VERSION: &str =
|
||||
"epiphany.mind.repo_model_admission_review.v0";
|
||||
pub const REPO_MODEL_ADMISSION_RECEIPT_TYPE: &str = "epiphany.mind.repo_model_admission_receipt";
|
||||
pub const REPO_MODEL_ADMISSION_RECEIPT_SCHEMA_VERSION: &str =
|
||||
"epiphany.mind.repo_model_admission_receipt.v1";
|
||||
pub const REPO_MODEL_MIGRATION_RECEIPT_TYPE: &str = "epiphany.mind.repo_model_migration_receipt";
|
||||
pub const REPO_MODEL_MIGRATION_RECEIPT_SCHEMA_VERSION: &str =
|
||||
"epiphany.mind.repo_model_migration_receipt.v0";
|
||||
pub const REPO_MODEL_ADMISSION_CONTRACT: &str = "epiphany.repo_model_admission.v1";
|
||||
pub const REPO_MODEL_MIGRATION_CONTRACT: &str = "epiphany.repo_model_migration.v0";
|
||||
pub const REPO_FRONTIER_ROUTE_TYPE: &str = "epiphany.self.repo_frontier_route";
|
||||
pub const REPO_FRONTIER_ROUTE_SCHEMA_VERSION: &str = "epiphany.self.repo_frontier_route.v0";
|
||||
pub const REPO_FRONTIER_HANDS_AUTHORITY_TYPE: &str = "epiphany.hands.repo_frontier_authority";
|
||||
pub const REPO_FRONTIER_HANDS_AUTHORITY_SCHEMA_VERSION: &str =
|
||||
"epiphany.hands.repo_frontier_authority.v0";
|
||||
pub const REPO_FRONTIER_ROUTE_CONTRACT: &str = "epiphany.repo_frontier_route.v0";
|
||||
pub const REPO_FRONTIER_HANDS_AUTHORITY_CONTRACT: &str =
|
||||
"epiphany.repo_frontier_hands_authority.v0";
|
||||
pub const REPO_FRONTIER_MODELING_REQUEST_TYPE: &str =
|
||||
"epiphany.modeling.repo_frontier_verdict_request";
|
||||
pub const REPO_FRONTIER_MODELING_REQUEST_SCHEMA_VERSION: &str =
|
||||
"epiphany.modeling.repo_frontier_verdict_request.v0";
|
||||
pub const REPO_FRONTIER_MODELING_REQUEST_CONTRACT: &str =
|
||||
"epiphany.repo_frontier_verdict_modeling_request.v0";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RepoFrontierVerdictDisposition {
|
||||
Resolved,
|
||||
Blocked,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "epiphany.modeling.repo_frontier_verdict_request",
|
||||
schema = "RepoFrontierModelingRequest"
|
||||
)]
|
||||
pub struct RepoFrontierModelingRequest {
|
||||
#[cultcache(key = 0)]
|
||||
pub schema_version: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub request_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub model_revision: u64,
|
||||
#[cultcache(key = 3)]
|
||||
pub model_hash: String,
|
||||
#[cultcache(key = 4)]
|
||||
pub route_id: String,
|
||||
#[cultcache(key = 5)]
|
||||
pub frontier_item_id: String,
|
||||
#[cultcache(key = 6)]
|
||||
pub frontier_item_hash: String,
|
||||
#[cultcache(key = 7)]
|
||||
pub verification_request_id: String,
|
||||
#[cultcache(key = 8)]
|
||||
pub soul_verdict_receipt_id: String,
|
||||
#[cultcache(key = 9)]
|
||||
pub verification_result_id: String,
|
||||
#[cultcache(key = 10)]
|
||||
pub verification_job_id: String,
|
||||
#[cultcache(key = 11)]
|
||||
pub verification_acceptance_receipt_id: String,
|
||||
#[cultcache(key = 12)]
|
||||
pub allowed_disposition: RepoFrontierVerdictDisposition,
|
||||
#[cultcache(key = 13)]
|
||||
pub requested_at: String,
|
||||
#[cultcache(key = 14)]
|
||||
pub contract: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RepoFrontierNextOrgan {
|
||||
Hands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "epiphany.self.repo_frontier_route",
|
||||
schema = "RepoFrontierRoute"
|
||||
)]
|
||||
pub struct RepoFrontierRoute {
|
||||
#[cultcache(key = 0)]
|
||||
pub schema_version: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub route_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub next_organ: RepoFrontierNextOrgan,
|
||||
#[cultcache(key = 3)]
|
||||
pub model_revision: u64,
|
||||
#[cultcache(key = 4)]
|
||||
pub model_hash: String,
|
||||
#[cultcache(key = 5)]
|
||||
pub admission_receipt_id: String,
|
||||
#[cultcache(key = 6)]
|
||||
pub frontier_item_id: String,
|
||||
#[cultcache(key = 7)]
|
||||
pub frontier_item_hash: String,
|
||||
#[cultcache(key = 8)]
|
||||
pub migration_body: String,
|
||||
#[cultcache(key = 9)]
|
||||
pub question: String,
|
||||
#[cultcache(key = 10)]
|
||||
pub gap: String,
|
||||
#[cultcache(key = 11)]
|
||||
pub target_claim_ids: Vec<String>,
|
||||
#[cultcache(key = 12)]
|
||||
pub source_scope: Vec<String>,
|
||||
#[cultcache(key = 13)]
|
||||
pub selected_at: String,
|
||||
#[cultcache(key = 14)]
|
||||
pub contract: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "epiphany.hands.repo_frontier_authority",
|
||||
schema = "RepoFrontierHandsAuthority"
|
||||
)]
|
||||
pub struct RepoFrontierHandsAuthority {
|
||||
#[cultcache(key = 0)]
|
||||
pub schema_version: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub authority_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub route_id: String,
|
||||
#[cultcache(key = 3)]
|
||||
pub model_revision: u64,
|
||||
#[cultcache(key = 4)]
|
||||
pub model_hash: String,
|
||||
#[cultcache(key = 5)]
|
||||
pub frontier_item_id: String,
|
||||
#[cultcache(key = 6)]
|
||||
pub frontier_item_hash: String,
|
||||
#[cultcache(key = 7)]
|
||||
pub hands_intent_id: String,
|
||||
#[cultcache(key = 8)]
|
||||
pub hands_review_id: String,
|
||||
#[cultcache(key = 9)]
|
||||
pub substrate_grant_receipt_id: String,
|
||||
#[cultcache(key = 10)]
|
||||
pub requested_paths: Vec<String>,
|
||||
#[cultcache(key = 11)]
|
||||
pub granted_at: String,
|
||||
#[cultcache(key = 12)]
|
||||
pub contract: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "epiphany.mind.repo_model_admission_review",
|
||||
schema = "RepoModelAdmissionReview"
|
||||
)]
|
||||
pub struct RepoModelAdmissionReview {
|
||||
#[cultcache(key = 0)]
|
||||
pub schema_version: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub review_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub result_id: String,
|
||||
#[cultcache(key = 3)]
|
||||
pub job_id: String,
|
||||
#[cultcache(key = 4)]
|
||||
pub patch_id: String,
|
||||
#[cultcache(key = 5)]
|
||||
pub patch_sha256: String,
|
||||
#[cultcache(key = 6)]
|
||||
pub base_revision: u64,
|
||||
#[cultcache(key = 7)]
|
||||
pub base_hash: String,
|
||||
#[cultcache(key = 8)]
|
||||
pub decision: MindGatewayDecision,
|
||||
#[cultcache(key = 9)]
|
||||
pub evidence_ids: Vec<String>,
|
||||
#[cultcache(key = 10)]
|
||||
pub reviewed_at: String,
|
||||
#[cultcache(key = 11)]
|
||||
pub contract: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "epiphany.mind.repo_model_admission_receipt",
|
||||
schema = "RepoModelAdmissionReceipt"
|
||||
)]
|
||||
pub struct RepoModelAdmissionReceipt {
|
||||
#[cultcache(key = 0)]
|
||||
pub schema_version: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub receipt_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub review_id: String,
|
||||
#[cultcache(key = 3)]
|
||||
pub result_id: String,
|
||||
#[cultcache(key = 4)]
|
||||
pub patch_id: String,
|
||||
#[cultcache(key = 5)]
|
||||
pub patch_sha256: String,
|
||||
#[cultcache(key = 6)]
|
||||
pub previous_revision: u64,
|
||||
#[cultcache(key = 7)]
|
||||
pub previous_hash: String,
|
||||
#[cultcache(key = 8)]
|
||||
pub admitted_revision: u64,
|
||||
#[cultcache(key = 9)]
|
||||
pub admitted_hash: String,
|
||||
#[cultcache(key = 10)]
|
||||
pub admitted_at: String,
|
||||
#[cultcache(key = 11)]
|
||||
pub contract: String,
|
||||
#[cultcache(key = 12)]
|
||||
pub purpose: epiphany_state_model::RepoModelPatchPurpose,
|
||||
#[cultcache(key = 13, default)]
|
||||
pub frontier_route_id: String,
|
||||
#[cultcache(key = 14, default)]
|
||||
pub verification_request_id: String,
|
||||
#[cultcache(key = 15, default)]
|
||||
pub soul_verdict_receipt_id: String,
|
||||
#[cultcache(key = 16, default)]
|
||||
pub frontier_modeling_request_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "epiphany.mind.repo_model_migration_receipt",
|
||||
schema = "RepoModelMigrationReceipt"
|
||||
)]
|
||||
pub struct RepoModelMigrationReceipt {
|
||||
#[cultcache(key = 0)]
|
||||
pub schema_version: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub receipt_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub source_store: String,
|
||||
#[cultcache(key = 3)]
|
||||
pub source_graph_id: String,
|
||||
#[cultcache(key = 4)]
|
||||
pub imported_revision: u64,
|
||||
#[cultcache(key = 5)]
|
||||
pub imported_hash: String,
|
||||
#[cultcache(key = 6)]
|
||||
pub imported_at: String,
|
||||
#[cultcache(key = 7)]
|
||||
pub contract: String,
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -11,8 +11,14 @@ pub const SOUL_REVIEW_RECEIPT_TYPE: &str = "epiphany.soul.review_receipt";
|
|||
pub const SOUL_VERIFICATION_REFUSAL_RECEIPT_TYPE: &str =
|
||||
"epiphany.soul.verification_refusal_receipt";
|
||||
pub const SOUL_VERIFICATION_REQUEST_SCHEMA_VERSION: &str = "epiphany.soul.verification_request.v0";
|
||||
pub const REPO_FRONTIER_VERIFICATION_REQUEST_TYPE: &str =
|
||||
"epiphany.soul.repo_frontier_verification_request";
|
||||
pub const REPO_FRONTIER_VERIFICATION_REQUEST_SCHEMA_VERSION: &str =
|
||||
"epiphany.soul.repo_frontier_verification_request.v0";
|
||||
pub const REPO_FRONTIER_VERIFICATION_REQUEST_CONTRACT: &str =
|
||||
"epiphany.repo_frontier_verification_request.v0";
|
||||
pub const SOUL_INVARIANT_CHECK_SCHEMA_VERSION: &str = "epiphany.soul.invariant_check.v0";
|
||||
pub const SOUL_VERDICT_RECEIPT_SCHEMA_VERSION: &str = "epiphany.soul.verdict_receipt.v0";
|
||||
pub const SOUL_VERDICT_RECEIPT_SCHEMA_VERSION: &str = "epiphany.soul.verdict_receipt.v1";
|
||||
pub const SOUL_REGRESSION_RECEIPT_SCHEMA_VERSION: &str = "epiphany.soul.regression_receipt.v0";
|
||||
pub const SOUL_REVIEW_RECEIPT_SCHEMA_VERSION: &str = "epiphany.soul.review_receipt.v0";
|
||||
pub const SOUL_VERIFICATION_REFUSAL_RECEIPT_SCHEMA_VERSION: &str =
|
||||
|
|
@ -41,6 +47,46 @@ pub struct SoulVerdictReceipt {
|
|||
pub emitted_at: String,
|
||||
#[cultcache(key = 9)]
|
||||
pub contract: String,
|
||||
#[cultcache(key = 10, default)]
|
||||
pub verification_request_id: String,
|
||||
#[cultcache(key = 11, default)]
|
||||
pub frontier_route_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "epiphany.soul.repo_frontier_verification_request",
|
||||
schema = "RepoFrontierVerificationRequest"
|
||||
)]
|
||||
pub struct RepoFrontierVerificationRequest {
|
||||
#[cultcache(key = 0)]
|
||||
pub schema_version: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub request_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub route_id: String,
|
||||
#[cultcache(key = 3)]
|
||||
pub model_revision: u64,
|
||||
#[cultcache(key = 4)]
|
||||
pub model_hash: String,
|
||||
#[cultcache(key = 5)]
|
||||
pub frontier_item_id: String,
|
||||
#[cultcache(key = 6)]
|
||||
pub frontier_item_hash: String,
|
||||
#[cultcache(key = 7)]
|
||||
pub hands_intent_id: String,
|
||||
#[cultcache(key = 8)]
|
||||
pub hands_review_id: String,
|
||||
#[cultcache(key = 9)]
|
||||
pub hands_patch_receipt_id: String,
|
||||
#[cultcache(key = 10)]
|
||||
pub hands_command_receipt_id: String,
|
||||
#[cultcache(key = 11)]
|
||||
pub hands_commit_receipt_id: String,
|
||||
#[cultcache(key = 12)]
|
||||
pub requested_at: String,
|
||||
#[cultcache(key = 13)]
|
||||
pub contract: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
@ -154,6 +200,8 @@ pub fn soul_verdict_receipt_from_verification_finding(
|
|||
risks: finding.risks.clone(),
|
||||
emitted_at,
|
||||
contract: "Soul verdict emitted from a reviewed Verification lane finding; it is proof of verification judgment before Mind admission.".to_string(),
|
||||
verification_request_id: finding.verification_request_id.clone().unwrap_or_default(),
|
||||
frontier_route_id: finding.frontier_route_id.clone().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -202,10 +250,13 @@ mod tests {
|
|||
evidence_gaps: Vec::new(),
|
||||
risks: Vec::new(),
|
||||
state_patch: None,
|
||||
repo_model_patch: None,
|
||||
self_patch: None,
|
||||
self_persistence: None,
|
||||
job_error: None,
|
||||
item_error: None,
|
||||
verification_request_id: Some("verification-request-1".to_string()),
|
||||
frontier_route_id: Some("frontier-route-1".to_string()),
|
||||
};
|
||||
let receipt = soul_verdict_receipt_from_verification_finding(
|
||||
"soul-verdict-1".to_string(),
|
||||
|
|
@ -214,5 +265,7 @@ mod tests {
|
|||
);
|
||||
assert_eq!(receipt.verdict, "passed");
|
||||
assert!(receipt.evidence_ids.contains(&"ev-check".to_string()));
|
||||
assert_eq!(receipt.verification_request_id, "verification-request-1");
|
||||
assert_eq!(receipt.frontier_route_id, "frontier-route-1");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,6 +146,9 @@ pub struct EpiphanyCoordinatorInput {
|
|||
pub verification_result_allows_implementation: bool,
|
||||
pub verification_result_needs_evidence: bool,
|
||||
pub reorient_finding_accepted: bool,
|
||||
/// True only when the canonical runtime RepoModel has exactly one current
|
||||
/// admission and an Active, dependency-ready Hands frontier item.
|
||||
pub hands_frontier_ready: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
@ -186,6 +189,7 @@ pub struct EpiphanyCoordinatorStatusInput {
|
|||
pub verification_result_allows_implementation: bool,
|
||||
pub verification_result_needs_evidence: bool,
|
||||
pub reorient_finding_accepted: bool,
|
||||
pub hands_frontier_ready: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
@ -281,6 +285,7 @@ pub fn derive_coordinator_status(
|
|||
verification_result_allows_implementation: input.verification_result_allows_implementation,
|
||||
verification_result_needs_evidence: input.verification_result_needs_evidence,
|
||||
reorient_finding_accepted: input.reorient_finding_accepted,
|
||||
hands_frontier_ready: input.hands_frontier_ready,
|
||||
});
|
||||
EpiphanyCoordinatorStatus {
|
||||
decision,
|
||||
|
|
@ -974,6 +979,7 @@ pub fn recommend_coordinator_action(
|
|||
&& input.verification_result_needs_evidence
|
||||
&& input.modeling_result_accepted
|
||||
&& input.verification_result_covers_current_modeling
|
||||
&& input.hands_frontier_ready
|
||||
{
|
||||
return build(
|
||||
EpiphanyCoordinatorAction::ContinueImplementation,
|
||||
|
|
@ -991,6 +997,7 @@ pub fn recommend_coordinator_action(
|
|||
&& input.verification_result_cites_implementation_evidence
|
||||
&& input.modeling_result_accepted
|
||||
&& input.verification_result_covers_current_modeling
|
||||
&& input.hands_frontier_ready
|
||||
{
|
||||
return build(
|
||||
EpiphanyCoordinatorAction::ContinueImplementation,
|
||||
|
|
@ -1117,13 +1124,43 @@ pub fn recommend_coordinator_action(
|
|||
);
|
||||
}
|
||||
|
||||
if input.hands_frontier_ready {
|
||||
return build(
|
||||
EpiphanyCoordinatorAction::ContinueImplementation,
|
||||
Some(EpiphanyCoordinatorRoleId::Implementation),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
"Mind has admitted the current RepoModel and Self has an actionable Hands frontier route.",
|
||||
);
|
||||
}
|
||||
|
||||
if input.signals.verification_result_status == EpiphanyCoordinatorRoleResultStatus::Completed
|
||||
&& input.verification_result_accepted
|
||||
&& !input.verification_result_allows_implementation
|
||||
&& (input.verification_result_needs_evidence
|
||||
|| input.verification_result_cites_implementation_evidence)
|
||||
&& input.modeling_result_accepted
|
||||
&& input.verification_result_covers_current_modeling
|
||||
&& !input.hands_frontier_ready
|
||||
{
|
||||
return build(
|
||||
EpiphanyCoordinatorAction::LaunchModeling,
|
||||
Some(EpiphanyCoordinatorRoleId::Modeling),
|
||||
Some(EpiphanyCoordinatorSceneAction::RoleLaunch),
|
||||
false,
|
||||
true,
|
||||
"Soul requests another bounded consequence, but Self has no admitted actionable Hands frontier; reconcile the RepoModel before acting.",
|
||||
);
|
||||
}
|
||||
|
||||
build(
|
||||
EpiphanyCoordinatorAction::ContinueImplementation,
|
||||
Some(EpiphanyCoordinatorRoleId::Implementation),
|
||||
None,
|
||||
EpiphanyCoordinatorAction::LaunchModeling,
|
||||
Some(EpiphanyCoordinatorRoleId::Modeling),
|
||||
Some(EpiphanyCoordinatorSceneAction::RoleLaunch),
|
||||
false,
|
||||
false,
|
||||
"CRRC is clear and no specialist lane is currently blocking implementation.",
|
||||
true,
|
||||
"No admitted current RepoModel frontier item is actionable by Hands; route Modeling to establish or reconcile the machine map.",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1252,6 +1289,7 @@ mod tests {
|
|||
verification_result_allows_implementation: false,
|
||||
verification_result_needs_evidence: false,
|
||||
reorient_finding_accepted: false,
|
||||
hands_frontier_ready: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1276,10 +1314,13 @@ mod tests {
|
|||
evidence_gaps: Vec::new(),
|
||||
risks: Vec::new(),
|
||||
state_patch: None,
|
||||
repo_model_patch: None,
|
||||
self_patch: None,
|
||||
self_persistence: None,
|
||||
job_error: None,
|
||||
item_error: None,
|
||||
verification_request_id: None,
|
||||
frontier_route_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1449,10 +1490,7 @@ mod tests {
|
|||
..input()
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
decision.action,
|
||||
EpiphanyCoordinatorAction::ContinueImplementation
|
||||
);
|
||||
assert_eq!(decision.action, EpiphanyCoordinatorAction::LaunchModeling);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1766,6 +1804,7 @@ mod tests {
|
|||
modeling_result_reviewable: true,
|
||||
verification_result_accepted: true,
|
||||
verification_result_needs_evidence: true,
|
||||
hands_frontier_ready: true,
|
||||
..input()
|
||||
});
|
||||
assert_eq!(
|
||||
|
|
@ -1808,6 +1847,7 @@ mod tests {
|
|||
modeling_result_accepted_after_verification: true,
|
||||
verification_result_accepted: true,
|
||||
verification_result_allows_implementation: true,
|
||||
hands_frontier_ready: true,
|
||||
..input()
|
||||
});
|
||||
assert_eq!(
|
||||
|
|
@ -1816,6 +1856,26 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_crrc_refuses_hands_without_an_actionable_frontier() {
|
||||
let decision = recommend_coordinator_action(EpiphanyCoordinatorInput {
|
||||
roles: Vec::new(),
|
||||
signals: EpiphanyCoordinatorSignals {
|
||||
research_result_status: EpiphanyCoordinatorRoleResultStatus::BackendMissing,
|
||||
modeling_result_status: EpiphanyCoordinatorRoleResultStatus::BackendMissing,
|
||||
verification_result_status: EpiphanyCoordinatorRoleResultStatus::BackendMissing,
|
||||
},
|
||||
..input()
|
||||
});
|
||||
|
||||
assert_eq!(decision.action, EpiphanyCoordinatorAction::LaunchModeling);
|
||||
assert_ne!(
|
||||
decision.target_role,
|
||||
Some(EpiphanyCoordinatorRoleId::Implementation)
|
||||
);
|
||||
assert!(decision.reason.contains("admitted current RepoModel"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forced_checkpoint_compaction_overrides_decision() {
|
||||
let decision = EpiphanyCoordinatorDecision {
|
||||
|
|
|
|||
|
|
@ -46,10 +46,13 @@ pub struct EpiphanyRoleFindingInterpretation {
|
|||
pub evidence_gaps: Vec<String>,
|
||||
pub risks: Vec<String>,
|
||||
pub state_patch: Option<EpiphanyRoleStatePatchDocument>,
|
||||
pub repo_model_patch: Option<crate::RepoModelPatch>,
|
||||
pub self_patch: Option<AgentSelfPatch>,
|
||||
pub self_persistence: Option<EpiphanyRoleSelfPersistenceReview>,
|
||||
pub job_error: Option<String>,
|
||||
pub item_error: Option<String>,
|
||||
pub verification_request_id: Option<String>,
|
||||
pub frontier_route_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
@ -136,6 +139,16 @@ fn interpret_role_finding(
|
|||
let state_patch_parse_error = state_patch_result
|
||||
.as_ref()
|
||||
.and_then(|result| result.as_ref().err().map(ToString::to_string));
|
||||
let repo_model_patch_result = raw_result
|
||||
.get("repoModelPatch")
|
||||
.cloned()
|
||||
.map(serde_json::from_value::<crate::RepoModelPatch>);
|
||||
let repo_model_patch = repo_model_patch_result
|
||||
.as_ref()
|
||||
.and_then(|result| result.as_ref().ok().cloned());
|
||||
let repo_model_patch_parse_error = repo_model_patch_result
|
||||
.as_ref()
|
||||
.and_then(|result| result.as_ref().err().map(ToString::to_string));
|
||||
let (self_patch, self_persistence) = raw_result
|
||||
.get("selfPatch")
|
||||
.map(|patch| decode_role_self_patch(role_id, patch))
|
||||
|
|
@ -151,8 +164,10 @@ fn interpret_role_finding(
|
|||
),
|
||||
EpiphanyRoleResultRoleId::Modeling => merge_item_error(
|
||||
item_error,
|
||||
modeling_role_state_patch_error(
|
||||
raw_result.get("statePatch").is_some(),
|
||||
modeling_role_result_error(
|
||||
raw_result.get("repoModelPatch").is_some(),
|
||||
repo_model_patch.as_ref(),
|
||||
repo_model_patch_parse_error,
|
||||
state_patch.as_ref(),
|
||||
state_patch_parse_error,
|
||||
),
|
||||
|
|
@ -186,10 +201,13 @@ fn interpret_role_finding(
|
|||
evidence_gaps: json_string_array_field(raw_result, "evidenceGaps"),
|
||||
risks: json_string_array_field(raw_result, "risks"),
|
||||
state_patch,
|
||||
repo_model_patch,
|
||||
self_patch,
|
||||
self_persistence,
|
||||
job_error,
|
||||
item_error,
|
||||
verification_request_id: json_string_field(raw_result, "verificationRequestId"),
|
||||
frontier_route_id: json_string_field(raw_result, "frontierRouteId"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -222,6 +240,7 @@ pub fn interpret_runtime_role_worker_result(
|
|||
result: &crate::EpiphanyRuntimeRoleWorkerResult,
|
||||
) -> EpiphanyRoleFindingInterpretation {
|
||||
let state_patch_result = result.state_patch();
|
||||
let repo_model_patch_result = result.repo_model_patch();
|
||||
let self_patch_result = result.self_patch();
|
||||
let state_patch = state_patch_result
|
||||
.as_ref()
|
||||
|
|
@ -231,6 +250,10 @@ pub fn interpret_runtime_role_worker_result(
|
|||
.as_ref()
|
||||
.ok()
|
||||
.and_then(|patch| patch.clone());
|
||||
let repo_model_patch = repo_model_patch_result
|
||||
.as_ref()
|
||||
.ok()
|
||||
.and_then(|patch| patch.clone());
|
||||
let item_error = merge_item_error(
|
||||
result.item_error.clone(),
|
||||
state_patch_result.as_ref().err().map(ToString::to_string),
|
||||
|
|
@ -239,6 +262,13 @@ pub fn interpret_runtime_role_worker_result(
|
|||
item_error,
|
||||
self_patch_result.as_ref().err().map(ToString::to_string),
|
||||
);
|
||||
let item_error = merge_item_error(
|
||||
item_error,
|
||||
repo_model_patch_result
|
||||
.as_ref()
|
||||
.err()
|
||||
.map(ToString::to_string),
|
||||
);
|
||||
EpiphanyRoleFindingInterpretation {
|
||||
verdict: Some(result.verdict.clone()),
|
||||
summary: Some(result.summary.clone()),
|
||||
|
|
@ -255,14 +285,17 @@ pub fn interpret_runtime_role_worker_result(
|
|||
evidence_gaps: result.evidence_gaps.clone(),
|
||||
risks: result.risks.clone(),
|
||||
state_patch: state_patch.clone(),
|
||||
repo_model_patch: repo_model_patch.clone(),
|
||||
self_patch,
|
||||
self_persistence: None,
|
||||
job_error: None,
|
||||
item_error: match role_id {
|
||||
EpiphanyRoleResultRoleId::Modeling => merge_item_error(
|
||||
item_error,
|
||||
modeling_role_state_patch_error(
|
||||
result.state_patch_msgpack.is_some(),
|
||||
modeling_role_result_error(
|
||||
result.repo_model_patch_msgpack.is_some(),
|
||||
repo_model_patch.as_ref(),
|
||||
None,
|
||||
state_patch.as_ref(),
|
||||
None,
|
||||
),
|
||||
|
|
@ -287,6 +320,8 @@ pub fn interpret_runtime_role_worker_result(
|
|||
| EpiphanyRoleResultRoleId::Verification
|
||||
| EpiphanyRoleResultRoleId::Reorientation => item_error,
|
||||
},
|
||||
verification_request_id: result.verification_request_id.clone(),
|
||||
frontier_route_id: result.frontier_route_id.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -597,14 +632,14 @@ pub fn modeling_role_state_patch_policy_errors(
|
|||
"churn or mode changes are not allowed through modeling role acceptance".to_string(),
|
||||
);
|
||||
}
|
||||
if patch.graphs.is_none()
|
||||
&& patch.graph_frontier.is_none()
|
||||
&& patch.graph_checkpoint.is_none()
|
||||
&& patch.scratch.is_none()
|
||||
&& patch.investigation_checkpoint.is_none()
|
||||
if patch.graphs.is_some()
|
||||
|| patch.graph_frontier.is_some()
|
||||
|| patch.graph_checkpoint.is_some()
|
||||
|| patch.scratch.is_some()
|
||||
|| patch.investigation_checkpoint.is_some()
|
||||
{
|
||||
errors.push(
|
||||
"statePatch must include a modeling field: graphs, graphFrontier, graphCheckpoint, scratch, or investigationCheckpoint"
|
||||
"repository anatomy is not allowed in generic Modeling statePatch; use repoModelPatch"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
|
@ -768,23 +803,34 @@ pub fn research_role_state_patch_policy_errors(
|
|||
errors
|
||||
}
|
||||
|
||||
fn modeling_role_state_patch_error(
|
||||
state_patch_present: bool,
|
||||
fn modeling_role_result_error(
|
||||
repo_model_patch_present: bool,
|
||||
repo_model_patch: Option<&crate::RepoModelPatch>,
|
||||
repo_model_patch_parse_error: Option<String>,
|
||||
state_patch: Option<&EpiphanyRoleStatePatchDocument>,
|
||||
parse_error: Option<String>,
|
||||
state_patch_parse_error: Option<String>,
|
||||
) -> Option<String> {
|
||||
if !state_patch_present {
|
||||
return Some("modeling result is not reviewable: missing required statePatch".to_string());
|
||||
if !repo_model_patch_present {
|
||||
return Some(
|
||||
"modeling result is not reviewable: missing required repoModelPatch".to_string(),
|
||||
);
|
||||
};
|
||||
if let Some(error) = parse_error {
|
||||
if let Some(error) = repo_model_patch_parse_error {
|
||||
return Some(format!(
|
||||
"modeling result is not reviewable: invalid statePatch ({error})"
|
||||
"modeling result is not reviewable: invalid repoModelPatch ({error})"
|
||||
));
|
||||
}
|
||||
let Some(state_patch) = state_patch else {
|
||||
return Some("modeling result is not reviewable: invalid statePatch".to_string());
|
||||
if repo_model_patch.is_none() {
|
||||
return Some("modeling result is not reviewable: invalid repoModelPatch".to_string());
|
||||
};
|
||||
let errors = modeling_role_state_patch_policy_errors(state_patch);
|
||||
if let Some(error) = state_patch_parse_error {
|
||||
return Some(format!(
|
||||
"modeling result is not reviewable: invalid optional statePatch ({error})"
|
||||
));
|
||||
}
|
||||
let errors = state_patch
|
||||
.map(modeling_role_state_patch_policy_errors)
|
||||
.unwrap_or_default();
|
||||
if errors.is_empty() {
|
||||
None
|
||||
} else {
|
||||
|
|
@ -1157,12 +1203,21 @@ mod tests {
|
|||
missing
|
||||
.item_error
|
||||
.as_deref()
|
||||
.is_some_and(|error| error.contains("missing required statePatch"))
|
||||
.is_some_and(|error| error.contains("missing required repoModelPatch"))
|
||||
);
|
||||
|
||||
let reviewable = interpret_role_finding(
|
||||
EpiphanyRoleResultRoleId::Modeling,
|
||||
&serde_json::json!({"statePatch": {"scratch": {"summary": "banked"}}}),
|
||||
&serde_json::json!({
|
||||
"repoModelPatch": {
|
||||
"patch_id": "modeling-result-test",
|
||||
"base_revision": 0,
|
||||
"base_hash": "legacy-hash",
|
||||
"applied_at": "2026-07-13T00:00:00Z",
|
||||
"purpose": {"kind": "evolution"},
|
||||
"operations": [{"operation": "retire_node", "node_id": "old"}]
|
||||
}
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
|
|
@ -1171,6 +1226,30 @@ mod tests {
|
|||
assert!(reviewable.item_error.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modeling_state_patch_cannot_smuggle_repository_anatomy() {
|
||||
let finding = interpret_role_finding(
|
||||
EpiphanyRoleResultRoleId::Modeling,
|
||||
&serde_json::json!({
|
||||
"repoModelPatch": {
|
||||
"patch_id": "modeling-anatomy-test",
|
||||
"base_revision": 0,
|
||||
"base_hash": "legacy-hash",
|
||||
"applied_at": "2026-07-13T00:00:00Z",
|
||||
"purpose": {"kind": "evolution"},
|
||||
"operations": [{"operation": "retire_node", "node_id": "old"}]
|
||||
},
|
||||
"statePatch": {"graphs": {}}
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert!(finding.item_error.as_deref().is_some_and(|error| {
|
||||
error.contains("repository anatomy") && error.contains("repoModelPatch")
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn marks_imagination_without_planning_patch_unreviewable() {
|
||||
let missing = interpret_role_finding(
|
||||
|
|
|
|||
|
|
@ -1365,7 +1365,7 @@ fn role_result_id_for_launch_role(
|
|||
fn worker_output_contract_text(document: &EpiphanyWorkerLaunchDocument) -> &'static str {
|
||||
match document {
|
||||
EpiphanyWorkerLaunchDocument::Role(_) => {
|
||||
"Required role-result fields: roleId, verdict, summary, nextSafeMove, filesInspected. Modeling and Imagination workers must include their required statePatch. Use arrays for frontierNodeIds, evidenceIds, openQuestions, evidenceGaps, risks, and artifactRefs when present."
|
||||
"Required role-result fields: roleId, verdict, summary, nextSafeMove, filesInspected. Modeling workers must include repoModelPatch; Imagination workers must include statePatch. Modeling statePatch is optional observations/evidence only. Use arrays for frontierNodeIds, evidenceIds, openQuestions, evidenceGaps, risks, and artifactRefs when present."
|
||||
}
|
||||
EpiphanyWorkerLaunchDocument::Reorient(_) => {
|
||||
"Required reorient-result fields: mode, summary, nextSafeMove. Include checkpointStillValid, filesInspected, frontierNodeIds, evidenceIds, openQuestions, and continuityRisks when present."
|
||||
|
|
@ -1393,7 +1393,11 @@ struct RoleWorkerResultIngress {
|
|||
evidence_gaps: Vec<String>,
|
||||
risks: Vec<String>,
|
||||
state_patch: Option<epiphany_core::EpiphanyRoleStatePatchDocument>,
|
||||
repo_model_patch: Option<epiphany_core::RepoModelPatch>,
|
||||
self_patch: Option<epiphany_core::AgentSelfPatch>,
|
||||
verification_request_id: Option<String>,
|
||||
frontier_route_id: Option<String>,
|
||||
repo_frontier_modeling_request_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, serde::Deserialize)]
|
||||
|
|
@ -1514,6 +1518,8 @@ fn role_worker_result_from_ingress(
|
|||
) -> EpiphanyRuntimeRoleWorkerResult {
|
||||
let (state_patch_msgpack, state_patch_error) =
|
||||
encode_optional_document(&result.state_patch, "statePatch");
|
||||
let (repo_model_patch_msgpack, repo_model_patch_error) =
|
||||
encode_optional_document(&result.repo_model_patch, "repoModelPatch");
|
||||
let (self_patch_msgpack, self_patch_error) =
|
||||
encode_optional_document(&result.self_patch, "selfPatch");
|
||||
EpiphanyRuntimeRoleWorkerResult {
|
||||
|
|
@ -1540,8 +1546,17 @@ fn role_worker_result_from_ingress(
|
|||
risks: clean_string_vec(&result.risks),
|
||||
state_patch_msgpack,
|
||||
self_patch_msgpack,
|
||||
item_error: merge_optional_errors(state_patch_error, self_patch_error),
|
||||
item_error: merge_optional_errors(
|
||||
merge_optional_errors(state_patch_error, self_patch_error),
|
||||
repo_model_patch_error,
|
||||
),
|
||||
metadata: std::collections::BTreeMap::new(),
|
||||
repo_model_patch_msgpack,
|
||||
verification_request_id: clean_optional_string(result.verification_request_id.as_deref()),
|
||||
frontier_route_id: clean_optional_string(result.frontier_route_id.as_deref()),
|
||||
repo_frontier_modeling_request_id: clean_optional_string(
|
||||
result.repo_frontier_modeling_request_id.as_deref(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1658,6 +1673,46 @@ mod tests {
|
|||
use epiphany_openai_adapter::EpiphanyOpenAiModelReceipt;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn verification_ingress_preserves_exact_request_and_route_binding() -> Result<()> {
|
||||
let parsed = parse_assistant_json::<RoleWorkerResultIngress>(
|
||||
r#"{"roleId":"verification","verdict":"pass","summary":"verified","nextSafeMove":"admit","verificationRequestId":" verification-request-1 ","frontierRouteId":" frontier-route-1 "}"#,
|
||||
)?;
|
||||
let launch = EpiphanyRuntimeWorkerLaunchRequest {
|
||||
schema_version: epiphany_core::RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION.to_string(),
|
||||
job_id: "verification-job-1".to_string(),
|
||||
binding_id: "verification-binding-1".to_string(),
|
||||
role: "verification".to_string(),
|
||||
authority_scope: "epiphany.role.verification".to_string(),
|
||||
instruction: "verify".to_string(),
|
||||
output_contract_id: epiphany_core::ROLE_WORKER_OUTPUT_CONTRACT_ID.to_string(),
|
||||
document_kind: "role".to_string(),
|
||||
launch_document_msgpack: Vec::new(),
|
||||
metadata: std::collections::BTreeMap::new(),
|
||||
organ_launch_contract: epiphany_core::default_launch_organ_contract(
|
||||
"epiphany.role.verification",
|
||||
"role",
|
||||
epiphany_core::ROLE_WORKER_OUTPUT_CONTRACT_ID,
|
||||
),
|
||||
};
|
||||
let result = role_worker_result_from_ingress(
|
||||
&launch,
|
||||
"verification",
|
||||
"verification-result-1",
|
||||
&parsed,
|
||||
Vec::new(),
|
||||
);
|
||||
assert_eq!(
|
||||
result.verification_request_id.as_deref(),
|
||||
Some("verification-request-1")
|
||||
);
|
||||
assert_eq!(
|
||||
result.frontier_route_id.as_deref(),
|
||||
Some("frontier-route-1")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_openai_event(
|
||||
request_id: &str,
|
||||
sequence: u64,
|
||||
|
|
@ -1865,10 +1920,10 @@ mod tests {
|
|||
.output_schema_json
|
||||
.as_deref()
|
||||
.expect("worker model request should carry role output schema");
|
||||
assert!(output_schema.contains("\"statePatch\""));
|
||||
assert!(output_schema.contains("\"repoModelPatch\""));
|
||||
assert!(output_schema.contains("\"frontierNodeIds\""));
|
||||
assert!(model_request.instructions.contains("Output schema JSON"));
|
||||
assert!(model_request.instructions.contains("\"statePatch\""));
|
||||
assert!(model_request.instructions.contains("\"repoModelPatch\""));
|
||||
assert_eq!(model_request.reasoning_effort.as_deref(), Some("low"));
|
||||
assert_eq!(model_request.reasoning_summary.as_deref(), Some("concise"));
|
||||
assert!(
|
||||
|
|
@ -1895,7 +1950,7 @@ mod tests {
|
|||
&launch_request,
|
||||
&model_request.request_id,
|
||||
&openai_summary,
|
||||
r#"{"roleId":"modeling","verdict":"checkpoint-ready","summary":"Mapped.","nextSafeMove":"Review the patch.","filesInspected":["src/lib.rs"],"evidenceIds":["ev-1"],"artifactRefs":["artifact:model"],"statePatch":{"objective":"Keep the machine mapped."},"selfPatch":{"reason":"typed nested document"}} "#,
|
||||
r#"{"roleId":"modeling","verdict":"checkpoint-ready","summary":"Mapped.","nextSafeMove":"Review the patch.","filesInspected":["src/lib.rs"],"frontierNodeIds":["old"],"evidenceIds":["ev-1"],"artifactRefs":["artifact:model"],"repoModelPatch":{"patch_id":"modeling-runtime-test","base_revision":0,"base_hash":"legacy-hash","applied_at":"2026-07-13T00:00:00Z","purpose":{"kind":"evolution"},"operations":[{"operation":"retire_node","node_id":"old"}]},"statePatch":{"observations":[],"evidence":[]},"selfPatch":{"reason":"typed nested document"}} "#,
|
||||
)?;
|
||||
|
||||
assert_eq!(result.job_id, "worker-job-1");
|
||||
|
|
@ -1909,8 +1964,11 @@ mod tests {
|
|||
assert_eq!(typed_result.files_inspected, vec!["src/lib.rs".to_string()]);
|
||||
assert_eq!(typed_result.artifact_refs, result.artifact_refs);
|
||||
assert_eq!(
|
||||
typed_result.state_patch()?.expect("state patch").objective,
|
||||
Some("Keep the machine mapped.".to_string())
|
||||
typed_result
|
||||
.repo_model_patch()?
|
||||
.expect("repo model patch")
|
||||
.patch_id,
|
||||
"modeling-runtime-test"
|
||||
);
|
||||
assert_eq!(
|
||||
typed_result.self_patch()?.expect("self patch").reason,
|
||||
|
|
@ -2021,6 +2079,8 @@ mod tests {
|
|||
risks: Vec::new(),
|
||||
emitted_at: now(),
|
||||
contract: "test".to_string(),
|
||||
verification_request_id: String::new(),
|
||||
frontier_route_id: String::new(),
|
||||
},
|
||||
)?;
|
||||
let request = epiphany_core::RepoWorkModelingRequest {
|
||||
|
|
@ -2203,11 +2263,9 @@ mod tests {
|
|||
&openai_summary1,
|
||||
r#"{"verdict":"passed","finding":"The reviewed README consequence updates the repo map.","summary":"README consequence mapped.","nextSafeMove":"Let Mind admit generation one.","evidenceIds":["soul-repo-1"],"artifactRefs":[]}"#,
|
||||
)?;
|
||||
let finding1 = epiphany_core::runtime_repo_work_modeling_finding(
|
||||
&store,
|
||||
"repo-request-2-finding",
|
||||
)?
|
||||
.expect("generation-one typed finding");
|
||||
let finding1 =
|
||||
epiphany_core::runtime_repo_work_modeling_finding(&store, "repo-request-2-finding")?
|
||||
.expect("generation-one typed finding");
|
||||
assert_eq!(finding1.verdict, "passed");
|
||||
|
||||
let map_review = epiphany_core::MindGatewayReview {
|
||||
|
|
@ -2249,12 +2307,7 @@ mod tests {
|
|||
modeling_route_id: route1.route_id.clone(),
|
||||
modeling_generation: 1,
|
||||
};
|
||||
epiphany_core::commit_repo_work_map_admission(
|
||||
&store,
|
||||
&map,
|
||||
&map_review,
|
||||
&map_commit,
|
||||
)?;
|
||||
epiphany_core::commit_repo_work_map_admission(&store, &map, &map_review, &map_commit)?;
|
||||
let mut stale = map.clone();
|
||||
stale.map_entry_id = "stale-generation-zero-map".to_string();
|
||||
stale.modeling_finding_receipt_id = finding0.receipt_id.clone();
|
||||
|
|
@ -2269,11 +2322,9 @@ mod tests {
|
|||
)
|
||||
.is_err()
|
||||
);
|
||||
let current_route = epiphany_core::runtime_repo_work_modeling_route(
|
||||
&store,
|
||||
&route1.route_id,
|
||||
)?
|
||||
.expect("current route");
|
||||
let current_route =
|
||||
epiphany_core::runtime_repo_work_modeling_route(&store, &route1.route_id)?
|
||||
.expect("current route");
|
||||
assert_eq!(current_route.generation, 1);
|
||||
assert_eq!(current_route.request_id, request1.request_id);
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -819,11 +819,24 @@ pub struct RepoModelPatch {
|
|||
pub base_revision: u64,
|
||||
pub base_hash: String,
|
||||
pub applied_at: String,
|
||||
pub purpose: RepoModelPatchPurpose,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[ts(type = "Array<RepoModelPatchOperation>")]
|
||||
pub operations: Vec<RepoModelPatchOperation>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
#[ts(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum RepoModelPatchPurpose {
|
||||
#[default]
|
||||
Evolution,
|
||||
IncorporateFrontierVerdict {
|
||||
route_id: String,
|
||||
soul_verdict_receipt_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "operation", rename_all = "snake_case")]
|
||||
#[ts(tag = "operation", rename_all = "snake_case")]
|
||||
|
|
@ -1185,6 +1198,10 @@ pub struct EpiphanyMemoryContextQuery {
|
|||
pub struct EpiphanyMemoryContextPacket {
|
||||
pub id: String,
|
||||
pub query_id: String,
|
||||
#[serde(default)]
|
||||
pub repo_model_revision: u64,
|
||||
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||
pub repo_model_hash: String,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[ts(type = "Array<EpiphanyMemoryNode>")]
|
||||
pub nodes: Vec<EpiphanyMemoryNode>,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,14 @@ roadmap mythology, and Self preserved dependency order without flattening those
|
|||
different judgments into one running narrative. This is positive evidence for
|
||||
durable organ-to-organ cognition, not merely for role-flavored prompting.
|
||||
|
||||
Do not substitute the first linear frontier identity proof for that larger
|
||||
lesson. `Modeling -> Mind -> Self -> Hands -> Soul -> Modeling` proves that one
|
||||
wound can retain causal identity. The awakened anatomy also needs typed backward
|
||||
pressure: Eyes findings that challenge model claims, Imagination revisions that
|
||||
change campaign shape, Soul rejections that create new evidence requirements,
|
||||
and Self rerouting based on those admitted artifacts. Modeling is the shared
|
||||
situational substrate, not the whole organism and not the product.
|
||||
|
||||
Persistence is the connective-tissue gap exposed by that success. Modeling's
|
||||
repo anatomy, confirmed/changed/invalidated claims, freshness, frontier, and
|
||||
semantic-index consequences must live in typed searchable state; Self must route
|
||||
|
|
@ -44,14 +52,44 @@ or lost to concurrent valid patches: CultCache now provides exact-envelope CAS
|
|||
and insert-if-absent beneath its cross-process sidecar lock, with hostile race
|
||||
proofs. Soul rejected two earlier cuts before accepting this authority boundary.
|
||||
|
||||
Do not call this the complete Aetheria loop. Modeling worker output still cannot
|
||||
carry a reviewed `RepoModelPatch`; Mind has no model-admission receipt binding
|
||||
patch/result/evidence; Self does not select and bind one frontier identity into a
|
||||
Hands grant; Hands consequences and Soul verdicts do not yet carry that same
|
||||
model revision/hash/item identity; and Modeling therefore cannot close exactly
|
||||
the wound Soul judged. The next cut is that closed identity chain, not more index
|
||||
polish: `Modeling patch -> Mind admission -> Self route F -> Hands consequence F
|
||||
-> Soul verdict F -> Modeling resolves F`.
|
||||
The first causal-identity slice of that Aetheria loop is now closed. Modeling
|
||||
emits a typed `RepoModelPatch`; Mind admits the exact immutable worker result,
|
||||
patch bytes, evidence, review, and replacement model under one conditional
|
||||
store transaction; Self selects one eligible Active frontier item from the
|
||||
admitted current model; and the resulting route freezes model revision/hash,
|
||||
item hash, claims, dependency order, and path scope. Hands intent, review,
|
||||
Substrate grant, frontier authority, patch, command, and commit documents are
|
||||
immutable insert-once records. Soul receives a typed verification request bound
|
||||
to that complete chain. A later typed Modeling request binds the accepted Soul
|
||||
result and permits exactly one verdict-incorporation revision of the same item:
|
||||
`pass` yields `Resolved`; `needs-review`, `needs-evidence`, or `fail` yields
|
||||
`Blocked`. Self derives Hands readiness from the same selection predicate, so
|
||||
Blocked/Resolved items cannot be routed and no default implementation authority
|
||||
survives without an admitted actionable frontier.
|
||||
|
||||
Soul found and forced cuts of four plausible green bypasses before accepting
|
||||
the slice: ordinary Evolution could mutate routed frontier lifecycle; Hands
|
||||
receipts and their intent/review/grant foundation were mutable by identity; the
|
||||
Modeling request trusted caller-supplied temporal acceptance; and a split model
|
||||
admission/thread-state commit could wedge on retry. Evolution is now barred
|
||||
while a current-model route exists and cannot manufacture Blocked or terminal
|
||||
frontier lifecycle. Every causal companion uses absent-only cross-process CAS
|
||||
with exact retry. Final verdict admission replays the full authority and
|
||||
consequence chain immediately before the model CAS. Verification acceptance is
|
||||
reloaded from canonical thread state, and stable review identity makes the
|
||||
split commit recoverable. Hostile substitution, stale-model, counterfeit,
|
||||
concurrent-writer, crash-window retry, pass/nonpass, and nonselection tests are
|
||||
green; the full core library proof is 303 passed and 1 ignored.
|
||||
|
||||
This is still not the whole Aetheria lesson. It proves durable causal identity
|
||||
and backward Soul pressure, not the complete multi-organ correction ecology.
|
||||
Eyes challenges and Imagination campaign revisions still need typed admission
|
||||
and Self routes. The older `RepoWorkModelingRequest/Route/Finding` path is also
|
||||
misnamed parallel closure machinery: it models one already-executed work item,
|
||||
not repository anatomy. Preserve useful plan/execute receipts, but converge
|
||||
that workflow on canonical RepoFrontier route/Hands/Soul/Modeling authority and
|
||||
demote `RepoWorkMapEntry` to derived closure history. Do not let item-slug routes
|
||||
or the legacy special Modeling worker close work independently of RepoModel.
|
||||
|
||||
The next artifact-contract audit then cut `epiphany.persona_chat.v0`: its live
|
||||
writer and catalog had disagreed on field vocabulary, evidence fields, and
|
||||
|
|
|
|||
91
notes/repo-frontier-organ-loop.md
Normal file
91
notes/repo-frontier-organ-loop.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# Repo Frontier Organ Loop
|
||||
|
||||
## Objective
|
||||
|
||||
Preserve the Aetheria-shaped engineering loop as typed causal identity, not as
|
||||
coordinator memory or prompt roleplay. One repository wound must remain the same
|
||||
wound while Modeling describes it, Mind admits it, Self routes it, Hands acts on
|
||||
it, Soul judges it, and Modeling closes or revises it.
|
||||
|
||||
This closed route is the first proof slice, not the whole Aetheria lesson. The
|
||||
larger success was corrective circulation between distinct organs: Eyes could
|
||||
invalidate substrate assumptions, Imagination could revise campaign shape, Soul
|
||||
could reject a locally green implementation, and Self could reorder work from
|
||||
those durable artifacts. RepoModel is connective tissue for that cognition; it
|
||||
is not a substitute for it. Later slices must admit typed challenges and plan
|
||||
revisions from those organs without giving any of them direct model-write
|
||||
authority.
|
||||
|
||||
## Authority Map
|
||||
|
||||
- Owner: the canonical `RepoModel` entry inside the runtime-spine CultCache.
|
||||
- Inputs: immutable Modeling worker result, exact patch bytes and base model
|
||||
identity, accepted evidence, and—when resolving work—the exact frontier route,
|
||||
Hands consequence chain, and Soul verdict.
|
||||
- Outputs: a replacement model plus immutable specialized Mind review and model
|
||||
admission receipt committed in one conditional batch.
|
||||
- Derived state: thread-state graphs/frontier/checkpoint are continuity and UI
|
||||
projections; semantic indexes rank eligible typed claims; prompt context and
|
||||
gate JSON explain admitted authority but never create it.
|
||||
- Forbidden writers: sibling-store refresh, generic Modeling `statePatch`
|
||||
anatomy, coordinator reason strings, `frontierNodeIds`, evidence-id prefix
|
||||
matching, and dashboard/operator projections.
|
||||
- Shared path: `Modeling patch -> Mind admission -> Self route F -> Hands intent
|
||||
and consequence F -> Soul request and verdict F -> Modeling resolves F`.
|
||||
- Deletion line: remove sibling `memory-graph.msgpack` as a live authority after
|
||||
one explicit import; remove Modeling graph/frontier writes from generic role
|
||||
acceptance; refuse `ContinueImplementation` without an admitted current route;
|
||||
replace prefix-based Hands-chain inference with typed reload and validation.
|
||||
|
||||
## Atomic Boundaries
|
||||
|
||||
1. Modeling result is immutable worker evidence.
|
||||
2. Exact current-model compare-and-swap plus specialized Mind review and model
|
||||
admission receipt is one runtime-spine store transaction.
|
||||
3. Self route insertion verifies the same current model and frontier item under
|
||||
the conditional store lock.
|
||||
4. Hands preflight verifies current route before consequence; immutable receipts
|
||||
preserve historical truth afterward.
|
||||
5. Soul request binds one complete typed Hands chain; accepted verdict binds that
|
||||
request and route.
|
||||
6. Resolution admission validates the same route and verdict before replacing the
|
||||
model and making the frontier ineligible for subsequent selection.
|
||||
|
||||
## Invariants
|
||||
|
||||
- No model effect exists without its admission receipt in the same snapshot.
|
||||
- No admission receipt can claim a model revision/hash absent from that snapshot.
|
||||
- A route freezes model revision/hash, frontier item hash, target claims, source
|
||||
scope, and selected organ.
|
||||
- Hands cannot act from prompt prose or gate JSON; it requires the persisted route.
|
||||
- Soul cannot judge a substituted or partial Hands chain.
|
||||
- A resolution patch can affect only the routed frontier item in the first closed
|
||||
loop slice.
|
||||
- Once F is resolved, retired, or superseded, Self cannot select F again.
|
||||
|
||||
## Landed Causal Slice
|
||||
|
||||
1. Conditional atomic batches and absent-only insertions protect the single-file
|
||||
CultCache store under its cross-process lock.
|
||||
2. Runtime-spine owns the imported canonical RepoModel and specialized Modeling
|
||||
result plus Mind admission documents.
|
||||
3. Self selects an eligible frontier item and binds the main-agent Hands gate to
|
||||
an immutable route; no actionable route means no Hands authority.
|
||||
4. Immutable Substrate, Hands, Soul verification, and Modeling request documents
|
||||
preserve exact causal identity. Final admission replays the complete chain.
|
||||
5. Verdict incorporation changes exactly the routed frontier item. Pass resolves;
|
||||
nonpass blocks. Hostile substitution, concurrent identity, stale model,
|
||||
split-retry, and terminal nonselection proofs pass, and independent Soul review
|
||||
accepts this slice.
|
||||
|
||||
## Next Deletion Line
|
||||
|
||||
`RepoWorkModelingRequest`, `RepoWorkModelingRoute`, and
|
||||
`RepoWorkModelingFinding` describe post-execution workflow closure, not repository
|
||||
anatomy. Their item-slug route and `RepoWorkMapEntry` closure ledger must not
|
||||
remain a second Modeling/Self/Mind truth. Preserve useful repo-work planning and
|
||||
execution receipts only behind canonical `RepoFrontierRoute` and
|
||||
`RepoFrontierHandsAuthority`; use the canonical Hands/Soul/verdict-incorporation
|
||||
path; demote the old map to derived closure history or delete it. After that,
|
||||
admit typed Eyes challenges and Imagination campaign revisions so blocked
|
||||
frontier pressure can circulate to the correct organ.
|
||||
Binary file not shown.
|
|
@ -661,4 +661,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: Close the Aetheria-shaped organ identity loop. The canonical RepoModel foundation is now revisioned/hashed, cross-process CAS protected, raw-refresh resistant, typed-frontier aware, dependency ordered, BM25 searchable over eligible claims, and projected into downstream role context. Do not add more index polish. Add a Modeling-only typed RepoModelPatch result; have Mind atomically review/admit it with a patch/result/evidence receipt; make Self select one ready frontier item and bind model revision/hash/item/claim scope into the Hands grant; bind Hands consequences and Soul verdict to that exact route; require the following Modeling patch to resolve/revise that same frontier identity; then prove substitution/staleness at every seam fails and resolved F cannot be selected again. Demote generic Modeling graph/frontier statePatch authority as the typed path lands. The intentionally absent Other-world action still requires a typed intent and real owner. Keep closure's zero-substring invariant.
|
||||
next_action: Converge the legacy RepoWork closure workflow on the accepted canonical RepoModel organ loop. The first causal slice is now proven: typed Modeling patch and Mind admission; Self route bound to current model/frontier identity; immutable Substrate/Hands consequence chain; exact Soul verification request and verdict; typed Modeling verdict-incorporation request; pass-to-Resolved and nonpass-to-Blocked; no unrouted Hands default; full-chain replay at final CAS; atomic absent-only companions; recoverable split coordinator retry; hostile substitution/race/nonselection proof; Soul PASS. Do not add index polish or another adapter. Keep repo-work plan/adopt/execute only where it serves the branch-local workflow, require it to consume canonical RepoFrontierRoute and RepoFrontierHandsAuthority, delete the special RepoWorkModeling Request/Route/Finding worker protocol and item-slug retry route, and demote or rename RepoWorkMapEntry as derived closure history with no anatomy/routing authority. Then add typed Eyes challenges and Imagination campaign revisions so Blocked persistence becomes real Aetheria-style corrective circulation rather than a terminal note. The intentionally absent Other-world action still requires a typed intent and real owner. Keep closure's zero-substring invariant.
|
||||
|
|
|
|||
2
vendor/cultcache-rs
vendored
2
vendor/cultcache-rs
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 654bba0b17845ad957ede60cad8a3acaf336bac9
|
||||
Subproject commit 141f2aa89a25016166b53fc47b0b48b6beabca39
|
||||
Reference in a new issue