Repair typed worker retry contracts

This commit is contained in:
Meta Vens 2026-08-07 05:21:32 +01:00
commit 1cb3e7648e
3 changed files with 170 additions and 45 deletions

View file

@ -204,6 +204,53 @@ fn evidence_output_schema() -> serde_json::Value {
})
}
fn self_patch_memory_output_schema() -> serde_json::Value {
serde_json::json!({
"type": "object",
"required": ["memoryId", "summary", "salience", "confidence"],
"properties": {
"memoryId": {"type": "string"},
"summary": {"type": "string", "minLength": 1, "maxLength": 600},
"salience": {"type": "number", "minimum": 0, "maximum": 1},
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
"linkedEventIds": {"type": "array", "items": {"type": "string"}},
"linkedRelationshipId": {"type": "string"}
},
"additionalProperties": false
})
}
fn self_patch_goal_output_schema() -> serde_json::Value {
serde_json::json!({
"type": "object",
"required": ["goalId", "description", "scope", "priority", "emotionalStake", "status"],
"properties": {
"goalId": {"type": "string"},
"description": {"type": "string", "minLength": 1, "maxLength": 700},
"scope": {"type": "string", "enum": ["immediate", "scene", "case", "arc", "life"]},
"priority": {"type": "number", "minimum": 0, "maximum": 1},
"emotionalStake": {"type": "string", "minLength": 1, "maxLength": 400},
"blockers": {"type": "array", "items": {"type": "string"}},
"status": {"type": "string", "enum": ["active", "blocked", "dormant", "resolved", "abandoned"]}
},
"additionalProperties": false
})
}
fn self_patch_value_output_schema() -> serde_json::Value {
serde_json::json!({
"type": "object",
"required": ["valueId", "label", "priority", "unforgivableIfBetrayed"],
"properties": {
"valueId": {"type": "string"},
"label": {"type": "string", "minLength": 1, "maxLength": 240},
"priority": {"type": "number", "minimum": 0, "maximum": 1},
"unforgivableIfBetrayed": {"type": "boolean"}
},
"additionalProperties": false
})
}
pub fn epiphany_role_launch_output_schema(role_id: EpiphanyRoleResultRoleId) -> serde_json::Value {
let verdict_enum = match role_id {
EpiphanyRoleResultRoleId::Imagination => {
@ -283,47 +330,33 @@ pub fn epiphany_role_launch_output_schema(role_id: EpiphanyRoleResultRoleId) ->
},
"semanticMemories": {
"type": "array",
"items": {
"type": "object",
"required": ["memoryId", "summary", "salience", "confidence"],
"additionalProperties": true
}
"maxItems": 8,
"items": self_patch_memory_output_schema()
},
"episodicMemories": {
"type": "array",
"items": {
"type": "object",
"required": ["memoryId", "summary", "salience", "confidence"],
"additionalProperties": true
}
"maxItems": 8,
"items": self_patch_memory_output_schema()
},
"relationshipMemories": {
"type": "array",
"items": {
"type": "object",
"required": ["memoryId", "summary", "salience", "confidence"],
"additionalProperties": true
}
"maxItems": 8,
"items": self_patch_memory_output_schema()
},
"goals": {
"type": "array",
"items": {
"type": "object",
"required": ["goalId", "description", "scope", "priority", "emotionalStake", "status"],
"additionalProperties": true
}
"maxItems": 6,
"items": self_patch_goal_output_schema()
},
"values": {
"type": "array",
"items": {
"type": "object",
"required": ["valueId", "label", "priority", "unforgivableIfBetrayed"],
"additionalProperties": true
}
"maxItems": 6,
"items": self_patch_value_output_schema()
},
"privateNotes": {
"type": "array",
"items": {"type": "string"}
"maxItems": 6,
"items": {"type": "string", "minLength": 1, "maxLength": 600}
}
},
"additionalProperties": false
@ -1529,8 +1562,8 @@ mod tests {
schema["properties"]["repositoryBodyObservationBasis"]["additionalProperties"],
false
);
let frontier_item = &schema["properties"]["repoModelPatch"]["properties"]["operations"]
["items"]["anyOf"][6]["properties"]["item"];
let frontier_item = &schema["properties"]["repoModelPatch"]["properties"]["operations"]["items"]
["anyOf"][6]["properties"]["item"];
assert!(
frontier_item["required"]
.as_array()
@ -1538,7 +1571,10 @@ mod tests {
.iter()
.any(|value| value == "target_claim_ids")
);
assert_eq!(frontier_item["properties"]["target_claim_ids"]["minItems"], 1);
assert_eq!(
frontier_item["properties"]["target_claim_ids"]["minItems"],
1
);
let purposes = schema["properties"]["repoModelPatch"]["properties"]["purpose"]["oneOf"]
.as_array()
.expect("typed Modeling purpose alternatives");
@ -1600,6 +1636,30 @@ mod tests {
);
}
#[test]
fn role_self_patch_schema_matches_canonical_numeric_memory_contract() {
let schema = epiphany_role_launch_output_schema(EpiphanyRoleResultRoleId::Verification);
let self_patch = &schema["properties"]["selfPatch"];
for bundle in [
"semanticMemories",
"episodicMemories",
"relationshipMemories",
] {
let item = &self_patch["properties"][bundle]["items"];
assert_eq!(item["properties"]["salience"]["type"], "number");
assert_eq!(item["properties"]["confidence"]["type"], "number");
assert_eq!(item["additionalProperties"], false);
}
assert_eq!(
self_patch["properties"]["goals"]["items"]["properties"]["priority"]["type"],
"number"
);
assert_eq!(
self_patch["properties"]["values"]["items"]["properties"]["priority"]["type"],
"number"
);
}
#[test]
fn frontier_planning_schema_exposes_candidate_without_generic_patch_mouths() {
let schema = epiphany_frontier_planning_output_schema();

View file

@ -971,10 +971,11 @@ fn validate_proposal_modeling_launch(
if cache
.get_all::<RepoFrontierProposalModelingLaunchBinding>()?
.iter()
.any(|binding| binding.proposal_modeling_request_id == request_id)
.filter(|binding| binding.proposal_modeling_request_id == request_id)
.any(|binding| !proposal_modeling_launch_was_superseded(state, binding))
{
return Err(anyhow!(
"proposal Modeling selection request is already bound to a launch"
"proposal Modeling selection request already has an unsuperseded launch"
));
}
let identity = cache
@ -1012,6 +1013,20 @@ fn validate_proposal_modeling_launch(
Ok((selection, proposal, identity))
}
fn proposal_modeling_launch_was_superseded(
state: &EpiphanyThreadState,
binding: &RepoFrontierProposalModelingLaunchBinding,
) -> bool {
state.acceptance_receipts.iter().any(|receipt| {
receipt.job_id == binding.job_id
&& receipt.binding_id == binding.binding_id
&& receipt.surface == "roleFailureReview"
&& receipt.role_id == "modeling"
&& receipt.status == "superseded"
&& !receipt.result_id.trim().is_empty()
})
}
fn terminal_runtime_link_for_binding(
state: &EpiphanyThreadState,
binding_id: &str,
@ -1806,6 +1821,44 @@ pub(crate) mod tests {
Ok(())
}
#[test]
fn proposal_launch_binding_is_reusable_only_after_exact_failure_supersession() {
let binding = RepoFrontierProposalModelingLaunchBinding {
schema_version: REPO_FRONTIER_PROPOSAL_MODELING_LAUNCH_BINDING_SCHEMA_VERSION.into(),
binding_record_id: "proposal-launch-1".into(),
proposal_modeling_request_id: "proposal-request-1".into(),
proposal_id: "proposal-1".into(),
proposal_payload_sha256: "a".repeat(64),
job_id: "job-1".into(),
binding_id: EPIPHANY_MODELING_ROLE_BINDING_ID.into(),
runtime_id: "runtime-1".into(),
thread_id: "thread-1".into(),
launched_at: "2026-07-13T05:00:03Z".into(),
worker_launch_document_sha256: "b".repeat(64),
contract: REPO_FRONTIER_PROPOSAL_MODELING_LAUNCH_BINDING_CONTRACT.into(),
};
let mut state = EpiphanyThreadState::default();
assert!(!proposal_modeling_launch_was_superseded(&state, &binding));
state
.acceptance_receipts
.push(epiphany_state_model::EpiphanyAcceptanceReceipt {
id: "failure-review-1".into(),
result_id: "failed-result-1".into(),
job_id: binding.job_id.clone(),
binding_id: binding.binding_id.clone(),
surface: "roleFailureReview".into(),
role_id: "modeling".into(),
status: "superseded".into(),
accepted_at: "2026-07-13T05:01:00Z".into(),
..Default::default()
});
assert!(proposal_modeling_launch_was_superseded(&state, &binding));
state.acceptance_receipts[0].job_id = "other-job".into();
assert!(!proposal_modeling_launch_was_superseded(&state, &binding));
}
#[test]
fn claim_repair_launch_is_coordinator_owned_bound_and_single_use() -> Result<()> {
let root = tempfile::tempdir()?;

View file

@ -3935,8 +3935,7 @@ pub fn promote_autonomous_direction_options_for_modeling(
else {
return Ok(Vec::new());
};
let Some(model) = opening
.get::<crate::EpiphanyMemoryGraphEntry>(crate::MEMORY_GRAPH_KEY)?
let Some(model) = opening.get::<crate::EpiphanyMemoryGraphEntry>(crate::MEMORY_GRAPH_KEY)?
else {
return Ok(Vec::new());
};
@ -6900,12 +6899,17 @@ pub fn commit_repo_frontier_verification_request_for_chain(
));
}
let authority = &authorities[0];
let request_id = format!(
"frontier-verification-{}-{}",
authority.route_id, chain.commit_receipt_id
);
let requested_at = cache
.get::<RepoFrontierVerificationRequest>(&request_id)?
.map(|existing| existing.requested_at)
.unwrap_or_else(|| requested_at.to_string());
let request = RepoFrontierVerificationRequest {
schema_version: REPO_FRONTIER_VERIFICATION_REQUEST_SCHEMA_VERSION.to_string(),
request_id: format!(
"frontier-verification-{}-{}",
authority.route_id, chain.commit_receipt_id
),
request_id,
route_id: authority.route_id.clone(),
model_revision: authority.model_revision,
model_hash: authority.model_hash.clone(),
@ -6916,7 +6920,7 @@ pub fn commit_repo_frontier_verification_request_for_chain(
hands_patch_receipt_id: chain.patch_receipt_id.clone(),
hands_command_receipt_id: chain.command_receipt_id.clone(),
hands_commit_receipt_id: chain.commit_receipt_id.clone(),
requested_at: requested_at.to_string(),
requested_at,
contract: REPO_FRONTIER_VERIFICATION_REQUEST_CONTRACT.to_string(),
};
put_repo_frontier_verification_request(store_path, &request)?;
@ -9780,13 +9784,11 @@ pub(crate) mod tests {
)?;
cache.put(
crate::MEMORY_GRAPH_KEY,
&crate::EpiphanyMemoryGraphEntry::from_snapshot(
&crate::EpiphanyMemoryGraphSnapshot {
schema_version: Some(crate::MEMORY_GRAPH_SCHEMA_VERSION.into()),
graph_id: "bootstrap-model".into(),
..Default::default()
},
)?,
&crate::EpiphanyMemoryGraphEntry::from_snapshot(&crate::EpiphanyMemoryGraphSnapshot {
schema_version: Some(crate::MEMORY_GRAPH_SCHEMA_VERSION.into()),
graph_id: "bootstrap-model".into(),
..Default::default()
})?,
)?;
let before = std::fs::read(&store)?;
assert!(
@ -12511,6 +12513,16 @@ pub(crate) mod tests {
&hands_chain,
"2026-05-06T00:07:11Z",
)?;
let retried_verification_request = commit_repo_frontier_verification_request_for_chain(
&store,
&hands_chain,
"2026-05-06T00:08:11Z",
)?;
assert_eq!(retried_verification_request, verification_request);
assert_eq!(
retried_verification_request.requested_at,
"2026-05-06T00:07:11Z"
);
assert_eq!(verification_request.route_id, frontier_route.route_id);
assert_eq!(verification_request.hands_patch_receipt_id, "hands-patch-1");
assert_eq!(