Seal work-loop receipt failures

This commit is contained in:
Meta Vens 2026-06-13 06:17:24 +01:00
commit b83c2fb5bc
6 changed files with 117 additions and 4 deletions

View file

@ -75,6 +75,7 @@ struct RecordCommitArgs {
commit_sha: String,
branch: String,
changed_paths: Vec<String>,
validate_commit_sha: bool,
}
#[derive(Debug)]
@ -87,6 +88,7 @@ struct RecordPassArgs {
commit_sha: String,
branch: String,
changed_paths: Vec<String>,
validate_commit_sha: bool,
}
impl Args {
@ -157,6 +159,7 @@ fn parse_record_commit(tokens: impl Iterator<Item = String>) -> Result<RecordCom
commit_sha,
branch,
changed_paths,
validate_commit_sha: true,
})
}
@ -183,6 +186,7 @@ fn parse_record_pass(tokens: impl Iterator<Item = String>) -> Result<RecordPassA
commit_sha,
branch,
changed_paths,
validate_commit_sha: true,
})
}
@ -325,6 +329,11 @@ fn record_command(store: &PathBuf, args: RecordCommandArgs) -> Result<serde_json
fn record_commit(store: &PathBuf, args: RecordCommitArgs) -> Result<serde_json::Value> {
let (intent, review) = load_gate(store, &args.gate, "commit")?;
validate_paths_within_gate(&intent, &args.changed_paths)?;
let commit_sha = if args.validate_commit_sha {
resolve_git_commit_sha(&args.commit_sha)?
} else {
args.commit_sha
};
let receipt_id = args
.gate
.receipt_id
@ -333,7 +342,7 @@ fn record_commit(store: &PathBuf, args: RecordCommitArgs) -> Result<serde_json::
receipt_id,
&intent,
&review,
args.commit_sha,
commit_sha,
args.branch,
normalize_paths(args.changed_paths),
args.gate.summary,
@ -378,6 +387,7 @@ fn record_pass(store: &PathBuf, args: RecordPassArgs) -> Result<serde_json::Valu
commit_sha: args.commit_sha,
branch: args.branch,
changed_paths: args.changed_paths,
validate_commit_sha: args.validate_commit_sha,
},
)?;
Ok(json!({
@ -520,6 +530,31 @@ fn normalize_path(path: &str) -> String {
path.trim().replace('\\', "/")
}
fn resolve_git_commit_sha(commit_sha: &str) -> Result<String> {
let output = std::process::Command::new("git")
.arg("rev-parse")
.arg("--verify")
.arg(format!("{commit_sha}^{{commit}}"))
.output()
.map_err(|err| anyhow!("failed to start git rev-parse for Hands commit receipt: {err}"))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
return Err(anyhow!(
"Hands commit receipt references non-existent commit {commit_sha:?}: {stderr}"
));
}
let resolved = String::from_utf8(output.stdout)
.map_err(|err| anyhow!("git rev-parse returned non-UTF8 commit sha: {err}"))?
.trim()
.to_string();
if resolved.is_empty() {
return Err(anyhow!(
"git rev-parse returned an empty commit sha for {commit_sha:?}"
));
}
Ok(resolved)
}
fn generated_receipt_id(prefix: &str) -> String {
format!("{prefix}-{}", uuid::Uuid::new_v4())
}
@ -582,6 +617,7 @@ mod tests {
commit_sha: "abc123".to_string(),
branch: "codex/test".to_string(),
changed_paths: vec!["src/lib.rs".to_string()],
validate_commit_sha: false,
},
)?;
@ -626,6 +662,7 @@ mod tests {
commit_sha: "def456".to_string(),
branch: "codex/test".to_string(),
changed_paths: vec!["src/lib.rs".to_string()],
validate_commit_sha: false,
},
)?;
@ -663,6 +700,28 @@ mod tests {
Ok(())
}
#[test]
fn refuses_commit_receipt_for_unknown_git_sha() -> Result<()> {
let temp = tempfile::tempdir()?;
let store = temp.path().join("runtime-spine.msgpack");
seed_gate(&store, vec!["src".to_string()])?;
let result = record_commit(
&store,
RecordCommitArgs {
gate: gate_args("hands-commit-test"),
commit_sha: "0000000000000000000000000000000000000000".to_string(),
branch: "codex/test".to_string(),
changed_paths: vec!["src/lib.rs".to_string()],
validate_commit_sha: true,
},
);
assert!(result.is_err());
assert!(runtime_hands_commit_receipt(&store, "hands-commit-test")?.is_none());
Ok(())
}
#[test]
fn refuses_patch_receipt_outside_requested_paths() -> Result<()> {
let temp = tempfile::tempdir()?;

View file

@ -815,6 +815,19 @@ pub fn recommend_coordinator_action(
);
}
if input.signals.verification_result_status == EpiphanyCoordinatorRoleResultStatus::Failed
&& !input.verification_result_accepted
{
return build(
EpiphanyCoordinatorAction::ReviewVerificationResult,
Some(EpiphanyCoordinatorRoleId::Verification),
Some(EpiphanyCoordinatorSceneAction::RoleResult),
false,
false,
"The verification/review worker failed; inspect the failed Soul result before relaunching verification or continuing implementation.",
);
}
if input.verification_result_accepted && input.implementation_evidence_after_verification {
return build(
EpiphanyCoordinatorAction::LaunchVerification,
@ -1471,6 +1484,22 @@ mod tests {
EpiphanyCoordinatorAction::LaunchVerification
);
let review_failed_verification = recommend_coordinator_action(EpiphanyCoordinatorInput {
signals: EpiphanyCoordinatorSignals {
research_result_status: EpiphanyCoordinatorRoleResultStatus::MissingBinding,
modeling_result_status: EpiphanyCoordinatorRoleResultStatus::Completed,
verification_result_status: EpiphanyCoordinatorRoleResultStatus::Failed,
},
modeling_result_accepted: true,
modeling_result_reviewable: true,
..input()
});
assert_eq!(
review_failed_verification.action,
EpiphanyCoordinatorAction::ReviewVerificationResult
);
assert!(!review_failed_verification.can_auto_run);
let verification_done = EpiphanyCoordinatorSignals {
research_result_status: EpiphanyCoordinatorRoleResultStatus::MissingBinding,
modeling_result_status: EpiphanyCoordinatorRoleResultStatus::Completed,

View file

@ -596,11 +596,35 @@ async fn run_worker_launch_with_tool_continuation(
}
if !openai_summary.tool_intent_ids.is_empty() {
return Err(anyhow!(
let summary = format!(
"worker {} still requested tools after {} automatic tool rounds",
launch_request.job_id,
options.max_tool_rounds
));
);
let result = fail_worker_job(
&options.store_path,
&launch_request.job_id,
summary.clone(),
"Inspect the worker request, tool receipts, and model/tool loop before relaunching."
.to_string(),
)?;
return Ok(json!({
"status": "tool-round-limit",
"store": options.store_path,
"jobId": launch_request.job_id,
"bindingId": launch_request.binding_id,
"role": launch_request.role,
"requestId": current_request_id,
"openaiResultId": openai_summary.result_id,
"openaiVerdict": openai_summary.verdict,
"openaiSummary": openai_summary.summary,
"workerResultId": result.result_id,
"verdict": result.verdict,
"summary": summary,
"nextSafeMove": result.next_safe_move,
"pendingToolIntentIds": openai_summary.tool_intent_ids,
"toolRounds": tool_rounds,
}));
}
let assistant_text =

View file

@ -36,6 +36,7 @@ remembers doctrine; the branch remembers the blade.
- Canonical anatomy now lives in `notes/epiphany-anatomy.md`. Embodied sub-agents are Self, Face, Imagination, Eyes, Proprioception, Hands, and Soul. Body, Mind, Continuity, and Substrate Gate are substrate/state/protocol surfaces, not sub-agent identities. State is split by layer: `work_organ` for resident Epiphany sub-agents, `persona` for portable public/person-shaped state shared by Epiphany Face, VoidBot repo Faces, and Ghostlight characters, and heartbeat/swarm state for scheduling and coordination physiology. Persona state now carries provenance, required public presentation, `date-time` timestamps, typed `candidateActions`, strict anchored thoughts with non-authoritative extensions, custom enum companion labels, and typed affect records for bonds/status reads/doctrine stances.
- The repo now has a one-command local operator path: `.\tools\epiphany_local_run.ps1`. Default mode is `smoke`; it builds the retained Codex app-server compatibility edge, builds native Epiphany operator binaries, runs the coordinator smoke, and writes launcher artifacts under `.epiphany-run/` plus coordinator artifacts under `.epiphany-dogfood/`. `-Mode status` is now Epiphany-native: it builds only Epiphany operator binaries, reads `state/thread-state.msgpack` when present, derives status through `epiphany-core`, and does not start or require `codex-app-server`. Use `-Mode plan` or `-Mode run -MaxSteps 4` for the raw bridge-equipped coordinator entrypoints; use `-Mode mvp -FaceInput "<operator request>" -MaxSteps 4` for the local product cycle that projects Face's character turn, writes a Face/Aquarium bubble, bootstraps a minimal local checkpoint for fresh threads, runs the coordinator with auto-tools, then runs heartbeat sleep/dream maintenance. Live plan/run/mvp modes use the workspace `state/runtime-spine.msgpack`, because CodexThread launch writes worker requests there; the run bundle remains artifact evidence, not runtime lifecycle authority. Add `-ThreadId <id>` and `-Workspace <path>` to inspect or continue an existing thread. `status`, `plan`, and `smoke` do not spend model calls; `run` and `mvp` build and use `epiphany-openai-runtime`. Coordinator workers now launch detached and are polled through runtime-spine instead of blocking the coordinator. MVP runs are non-ephemeral by default so `-ThreadId <id>` can resume and poll later. `-MaxRuntimeSeconds <n>` is passed into worker runtime; timeout writes a failed worker receipt and exits. Failed modeling now blocks downstream verification until the failed result is reviewed.
- Current Mom-ready status: the normal MVP pass reaches Face -> coordinator -> detached runtime -> heartbeat sleep, and modeling workers now complete through the ChatGPT subscription transport. The first failure was a stack of violated observability/runtime invariants: worker prompts prepended the whole shared persistent-memory rite, Responses frame liveness was invisible until completion, and the runtime tried to persist hundreds of text deltas before completing the worker job. Role/reorient workers now get a compact Epiphany worker boundary instead of full persistent memory; runtime-spine records sampled raw Responses frame kinds with small previews; contiguous reasoning/text deltas compact before durable storage; and live proofs completed `2fe38dcd-d99e-4c73-9a58-afcc81f851c7`, `241e3851-e3ea-4877-a63b-0d2bbd96a7e2`, and `85149a26-6975-403f-ac93-092b717c5825` instead of timing out. The second failure was stale lifecycle authority: completed runtime jobs remained as unresolved `runtime_links`, replacement launches treated historical active links as current, and one bad validation path opened an orphan job before rejecting the state patch. Latest-link-wins is now the runtime-link invariant, terminal links project completed jobs, stale active history cannot veto replacement, launch validation happens before opening runtime jobs, and completed-but-unreviewable modeling findings stop for review instead of relaunching the same lane. The Eyes/source-gather fork is now landed: Research is a fixed Eyes lane with launch/result/accept/status/coordinator/protocol support, and `regather-needed` modeling findings route to `LaunchResearch` instead of relaunching blind Proprioception. Research statePatch admission is Eyes-shaped only: observations, evidence, scratch, and optional investigationCheckpoint; no graph, planning, objective, or checkpoint authority. Mind admission now leaves typed runtime-spine proof: role/reorient acceptance persists `epiphany.mind.gateway_review` before state admission and `epiphany.mind.state_commit_receipt` after admission with the resulting durable revision. Launch contracts now split the broad receipt catalogue from effect-specific proof profiles for state admission, evidence promotion, repo action, verification, and continuity recovery. Research evidence promotion enforces Substrate Gate read/snapshot grant, Eyes evidence packet, and Mind review before state admission; Verification acceptance enforces Soul verdict and Mind review; Reorient acceptance enforces Continuity recovery receipt and Mind review. `epiphany-mvp-coordinator` now records a typed Substrate Gate grant plus Hands intent/review gate when the coordinator selects `continueImplementation`, including a structured `recordPassCommand` template; `epiphany-hands-action` records patch/command/commit receipts against that gate and refuses mismatched, unapproved, or out-of-scope consequences. `epiphany-mvp-coordinator-smoke` now proves the deterministic path: forced `continueImplementation`, gate emission, and `record-pass` receipts in the smoke runtime-spine store. Live run `local-20260612-133920` proved a receipt-only Verification `needs-evidence` finding can be accepted through `roleAccept` and advance to a Hands gate without granting Soul statePatch authority. Runtime-spine now detects and summarizes a complete post-verification Hands patch/command/commit receipt chain so the coordinator can route Soul after Hands. Verification launches now write that chain into the internal CultMesh Verse as `epiphany.cultmesh.work_loop_telemetry.v0` and render a Soul packet from it; Modeling launches read the latest packet, attach accepted Soul receipt ids, and render the Proprioception packet from the same typed state. The telemetry now carries resolved receipt payload previews, stdout/stderr artifact previews, commit diff preview, source proof notes, and verification assertions. The corrected work loop is Hands -> Soul -> Proprioception -> Hands: Soul verifies concrete consequence receipts and changed source, then Proprioception updates the machine model from verified consequence before another implementation turn.
- Fresh scar from the receipt proof dogfood: `epiphany-model-runtime run-worker --auto-tools` must seal the outer runtime job when the model still requests tools after the configured round limit; a failed Soul/verification result is a real lane result and must stop for review instead of being treated as no current finding; and `epiphany-hands-action` must resolve commit receipts through `git rev-parse --verify` before storing them, because an almost-correct SHA poisoned Soul's `git show` proof and made the receipt chain untrustworthy.
- The investment dossier readiness target is now explicit: `E:\Projects\gamecult-site\docs\gamecult_integrated_dossier.tex` expects Epiphany to be part of a Bifrost-first labor loop, not only a local Mom demo. The external proof bar is: Bifrost owns topics/work items/dispatch packets/receipts/credit/reward pressure; Epiphany executes bounded work; maintainers review artifacts; public/operator evidence records scope, consent, roles, model spend, review load, artifacts, accepted/rejected outcome, memory lessons, and case-study permission; and private worker/operator/agent context stays sealed. Diligence also asks for a live fresh-repo Epiphany demo, Bifrost work item/topic/agent transport/receipt/ledger demo, CultMesh status, VoidBot/Discord swarm demo, compute/output metrics, and security model for secrets/write permissions/public-private exports.
- This Codex operator's own roadmap/todo posture has been retuned for this scale target, with the Epiphany Imagination specialist as an optional sub-agent rail rather than the whole point. `notes/epiphany-investor-readiness-roadmap.md` now says: make the local Mom MVP a repeatable proof bundle; build the Bifrost-first external-work bridge; add compute/review/accepted-artifact accounting; prove fresh-repo operation without supervisor contamination; keep public/operator-safe exports sealed; and treat angel capital as fuel for capacity, not command authority. The Imagination specialist prompt was also updated so investor-scale Objective Drafts must name Bifrost provenance, bounded Epiphany proof, maintainer review, receipts/ledger evidence, compute/output metrics, public/private export safety, consent, and legal/security gates.
- Local operator runs now write typed CultMesh intent/receipt documents to `.epiphany-run/cultmesh/operator-runs.ccmp` through `epiphany-operator-run`. The wrapper writes intent before acting and a completion receipt after success; this is the local consent/trace rail, not a hidden scheduler queue.

Binary file not shown.

View file

@ -592,4 +592,4 @@ current_status:
cargo and rejects modeling/planning/graph/checkpoint authority. MVP status
and coordinator smokes pass with six lanes, and the smoke harness now
rebuilds stale coordinator/app-server binaries before using them.
next_action: Exercise the corrected Hands -> Soul -> Proprioception loop: record the current bounded edit/check/commit against the latest Hands gate, rerun the coordinator so Soul verifies the Hands receipts, accept the Soul result when reviewable, then confirm the next routed stage is Proprioception/modeling before another Hands turn. If the run exposes split-brained state admission instead, collapse Mind review/state mutation/commit into one storage-owner primitive.
next_action: Commit the runtime/Soul/Hands receipt integrity fixes, record the resulting pass with `epiphany-hands-action` so the commit SHA is git-validated, rerun the coordinator so Soul reviews the clean Hands receipts, accept the Soul result when reviewable, then confirm the next routed stage is Proprioception/modeling before another Hands turn.