Repair typed worker retry contracts
This commit is contained in:
parent
48c086ad85
commit
1cb3e7648e
3 changed files with 170 additions and 45 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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()?;
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
Reference in a new issue