Seal work-loop receipt failures
This commit is contained in:
parent
c772d852e0
commit
b83c2fb5bc
6 changed files with 117 additions and 4 deletions
|
|
@ -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()?;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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.
|
|
@ -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.
|
||||
|
|
|
|||
Reference in a new issue