Move Modeling process lifecycle under Idunn
This commit is contained in:
parent
04d44d7923
commit
e7d7f2961b
7 changed files with 150 additions and 18 deletions
|
|
@ -230,7 +230,22 @@ fn service_launch(args: Args) -> Result<()> {
|
|||
let service_args = service_serve_args(&args);
|
||||
let mut command = Command::new(&command_path);
|
||||
command.args(&service_args);
|
||||
command.stdout(Stdio::null()).stderr(Stdio::null());
|
||||
if let Some(stdout_path) = &args.stdout_artifact {
|
||||
if let Some(parent) = stdout_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
command.stdout(Stdio::from(fs::File::create(stdout_path)?));
|
||||
} else {
|
||||
command.stdout(Stdio::null());
|
||||
}
|
||||
if let Some(stderr_path) = &args.stderr_artifact {
|
||||
if let Some(parent) = stderr_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
command.stderr(Stdio::from(fs::File::create(stderr_path)?));
|
||||
} else {
|
||||
command.stderr(Stdio::null());
|
||||
}
|
||||
if let Some(cwd) = &args.cwd {
|
||||
command.current_dir(cwd);
|
||||
}
|
||||
|
|
@ -267,7 +282,9 @@ fn service_launch(args: Args) -> Result<()> {
|
|||
exit_code,
|
||||
started_at,
|
||||
completed_at,
|
||||
None,
|
||||
args.stdout_artifact
|
||||
.as_ref()
|
||||
.map(|path| path.display().to_string()),
|
||||
);
|
||||
let written = write_epiphany_cultmesh_daemon_service_lifecycle_receipt(
|
||||
&args.store,
|
||||
|
|
@ -284,6 +301,8 @@ fn service_launch(args: Args) -> Result<()> {
|
|||
"receiptId": written.receipt_id,
|
||||
"processId": written.process_id,
|
||||
"exitCode": written.exit_code,
|
||||
"stdoutArtifact": args.stdout_artifact,
|
||||
"stderrArtifact": args.stderr_artifact,
|
||||
"privateStateExposed": written.private_state_exposed,
|
||||
}))?
|
||||
);
|
||||
|
|
@ -3170,6 +3189,8 @@ struct Args {
|
|||
intent_id: Option<String>,
|
||||
receipt_id: Option<String>,
|
||||
artifact_ref: Option<String>,
|
||||
stdout_artifact: Option<PathBuf>,
|
||||
stderr_artifact: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
|
|
@ -3210,6 +3231,8 @@ impl Args {
|
|||
let mut intent_id = None;
|
||||
let mut receipt_id = None;
|
||||
let mut artifact_ref = None;
|
||||
let mut stdout_artifact = None;
|
||||
let mut stderr_artifact = None;
|
||||
|
||||
while let Some(arg) = values.next() {
|
||||
match arg.as_str() {
|
||||
|
|
@ -3339,6 +3362,16 @@ impl Args {
|
|||
"--artifact-ref" => {
|
||||
artifact_ref = Some(values.next().context("missing --artifact-ref value")?);
|
||||
}
|
||||
"--stdout-artifact" => {
|
||||
stdout_artifact = Some(PathBuf::from(
|
||||
values.next().context("missing --stdout-artifact value")?,
|
||||
));
|
||||
}
|
||||
"--stderr-artifact" => {
|
||||
stderr_artifact = Some(PathBuf::from(
|
||||
values.next().context("missing --stderr-artifact value")?,
|
||||
));
|
||||
}
|
||||
other => anyhow::bail!("unknown argument {other:?}"),
|
||||
}
|
||||
}
|
||||
|
|
@ -3464,6 +3497,8 @@ impl Args {
|
|||
intent_id,
|
||||
receipt_id,
|
||||
artifact_ref,
|
||||
stdout_artifact,
|
||||
stderr_artifact,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ use std::fs;
|
|||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::process::Stdio;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut args = env::args().skip(1);
|
||||
|
|
@ -14208,11 +14207,36 @@ fn resolve_repo_work_model_runtime(epiphany_root: &Path) -> Result<PathBuf> {
|
|||
.ok_or_else(|| anyhow!("epiphany-openai-runtime binary is not built"))
|
||||
}
|
||||
|
||||
fn resolve_daemon_supervisor(epiphany_root: &Path) -> Result<PathBuf> {
|
||||
let exe_name = if cfg!(windows) {
|
||||
"epiphany-daemon-supervisor.exe"
|
||||
} else {
|
||||
"epiphany-daemon-supervisor"
|
||||
};
|
||||
let mut candidates = Vec::new();
|
||||
if let Some(target) = env::var_os("CARGO_TARGET_DIR") {
|
||||
candidates.push(PathBuf::from(target).join("debug").join(exe_name));
|
||||
}
|
||||
candidates.push(
|
||||
epiphany_root
|
||||
.join("epiphany-core")
|
||||
.join("target")
|
||||
.join("debug")
|
||||
.join(exe_name),
|
||||
);
|
||||
candidates.push(epiphany_root.join("target").join("debug").join(exe_name));
|
||||
candidates
|
||||
.into_iter()
|
||||
.find(|candidate| candidate.is_file())
|
||||
.ok_or_else(|| anyhow!("epiphany-daemon-supervisor binary is not built"))
|
||||
}
|
||||
|
||||
fn launch_repo_work_modeling_worker(
|
||||
epiphany_root: &Path,
|
||||
workspace: &Path,
|
||||
artifact_dir: &Path,
|
||||
runtime_store: &Path,
|
||||
local_verse_store: &Path,
|
||||
request: &RepoWorkModelingRequest,
|
||||
item_slug: &str,
|
||||
) -> Result<Value> {
|
||||
|
|
@ -14272,28 +14296,73 @@ fn launch_repo_work_modeling_worker(
|
|||
},
|
||||
)?;
|
||||
let runtime_bin = resolve_repo_work_model_runtime(epiphany_root)?;
|
||||
let supervisor_bin = resolve_daemon_supervisor(epiphany_root)?;
|
||||
let stdout_path = artifact_dir.join(format!("{job_id}.stdout.log"));
|
||||
let stderr_path = artifact_dir.join(format!("{job_id}.stderr.log"));
|
||||
let stdout = fs::File::create(&stdout_path)?;
|
||||
let stderr = fs::File::create(&stderr_path)?;
|
||||
let child = Command::new(&runtime_bin)
|
||||
.arg("run-worker")
|
||||
let service_id = format!("idunn-{job_id}");
|
||||
let output = Command::new(&supervisor_bin)
|
||||
.arg("service-launch")
|
||||
.arg("--store")
|
||||
.arg(local_verse_store)
|
||||
.arg("--runtime-id")
|
||||
.arg("repo-work-local")
|
||||
.arg("--daemon-id")
|
||||
.arg("epiphany-daemon-modeling")
|
||||
.arg("--service-id")
|
||||
.arg(&service_id)
|
||||
.arg("--service-command")
|
||||
.arg(&runtime_bin)
|
||||
.arg("--service-arg")
|
||||
.arg("run-worker")
|
||||
.arg("--service-arg")
|
||||
.arg("--store")
|
||||
.arg("--service-arg")
|
||||
.arg(runtime_store)
|
||||
.arg("--service-arg")
|
||||
.arg("--job-id")
|
||||
.arg("--service-arg")
|
||||
.arg(&job_id)
|
||||
.arg("--service-arg")
|
||||
.arg("--cwd")
|
||||
.arg("--service-arg")
|
||||
.arg(workspace)
|
||||
.arg("--service-arg")
|
||||
.arg("--max-runtime-seconds")
|
||||
.arg("--service-arg")
|
||||
.arg("300")
|
||||
.arg("--cwd")
|
||||
.arg(workspace)
|
||||
.arg("--max-runtime-seconds")
|
||||
.arg("300")
|
||||
.stdout(Stdio::from(stdout))
|
||||
.stderr(Stdio::from(stderr))
|
||||
.spawn()
|
||||
.with_context(|| format!("failed to launch {}", runtime_bin.display()))?;
|
||||
.arg("--stdout-artifact")
|
||||
.arg(&stdout_path)
|
||||
.arg("--stderr-artifact")
|
||||
.arg(&stderr_path)
|
||||
.arg("--reason")
|
||||
.arg(format!(
|
||||
"Idunn launches typed repo-work Modeling job {job_id}."
|
||||
))
|
||||
.output()
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to invoke Idunn supervisor {}",
|
||||
supervisor_bin.display()
|
||||
)
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Idunn supervisor refused repo-work Modeling launch: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
let lifecycle: Value = serde_json::from_slice(&output.stdout)
|
||||
.context("Idunn supervisor did not emit its lifecycle receipt JSON")?;
|
||||
Ok(json!({
|
||||
"jobId": job_id,
|
||||
"processId": child.id(),
|
||||
"processId": lifecycle["processId"],
|
||||
"runtimeBin": runtime_bin,
|
||||
"supervisorBin": supervisor_bin,
|
||||
"lifecycleOwner": "Idunn",
|
||||
"lifecycleReceiptId": lifecycle["receiptId"],
|
||||
"serviceId": lifecycle["serviceId"],
|
||||
"stdoutArtifact": stdout_path,
|
||||
"stderrArtifact": stderr_path,
|
||||
"requestId": request.request_id,
|
||||
|
|
@ -14749,11 +14818,15 @@ fn run_tick(args: TickArgs) -> Result<Value> {
|
|||
}
|
||||
None => {
|
||||
action = "launch-modeling".to_string();
|
||||
let lifecycle_store = local_verse_store
|
||||
.clone()
|
||||
.unwrap_or_else(|| workspace.join(".epiphany").join("local-verse.ccmp"));
|
||||
advanced_result = launch_repo_work_modeling_worker(
|
||||
&epiphany_root,
|
||||
&workspace,
|
||||
&artifact_dir,
|
||||
&runtime_store,
|
||||
&lifecycle_store,
|
||||
&request,
|
||||
&item_slug,
|
||||
)?;
|
||||
|
|
@ -16604,6 +16677,9 @@ mod authority_tests {
|
|||
.next()
|
||||
.expect("production source before authority tests");
|
||||
assert!(!production.contains("put_repo_work_modeling_finding("));
|
||||
assert!(!production.contains(".spawn()"));
|
||||
assert!(production.contains("epiphany-daemon-supervisor"));
|
||||
assert!(production.contains("\"lifecycleOwner\": \"Idunn\""));
|
||||
assert!(!source.contains(&["RepoWork", "MapStore"].concat()));
|
||||
assert!(!source.contains(&["repo-work-map", ".msgpack"].concat()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,6 +143,15 @@ runtime document. The obsolete direct-CLI closure smoke was deleted. A live
|
|||
negative probe returned exit 1 for forged passing CLI cargo while leaving the
|
||||
typed request awaiting its real Modeling worker.
|
||||
|
||||
Detached worker physiology is now Idunn-owned. `epiphany-work` opens the typed
|
||||
runtime job and invokes `epiphany-daemon-supervisor service-launch`; it contains
|
||||
no child-process `spawn`. The supervisor launches the existing model runtime,
|
||||
owns Windows hidden-process behavior, redirects stdout/stderr to explicit
|
||||
artifacts, and publishes an
|
||||
`epiphany.cultmesh.daemon_service_lifecycle_receipt.v0` containing service and
|
||||
process identity. Self receives that receipt ID as routing evidence only. Live
|
||||
item `idunn-owned-modeling` crossed this boundary and reached the Bifrost gate.
|
||||
|
||||
The
|
||||
accepted interpretation is persisted as
|
||||
`epiphany.modeling.repo_work_finding.v0`; it references the passing Soul verdict,
|
||||
|
|
|
|||
|
|
@ -204,9 +204,9 @@ Delete or demote:
|
|||
|
||||
## Immediate Next Action
|
||||
|
||||
Move repo-work Modeling process stewardship under Idunn and add a reviewed
|
||||
request generation for non-passing immutable findings. The scheduler now routes
|
||||
passing requests through the existing model runtime and reaches Bifrost without
|
||||
Self-authored Modeling evidence; preserve that authority path. Do not overwrite
|
||||
a blocked finding, launch duplicate jobs, or grow a scheduler-owned process
|
||||
manager.
|
||||
Design and implement a typed Mind-reviewed request-generation pointer for
|
||||
non-passing immutable repo-work Modeling findings. Idunn now owns detached
|
||||
worker launch through its existing lifecycle receipt mouth. Preserve the old
|
||||
request/finding as immutable evidence, make one typed owner select the current
|
||||
generation, and keep filesystem closure artifacts and Self from becoming
|
||||
mutable routing authorities.
|
||||
|
|
|
|||
|
|
@ -2085,6 +2085,18 @@ Live negative item `cli-modeling-authority-refusal` returned exit 1 when
|
|||
`close --model-authored` supplied a forged passing finding; the error names the
|
||||
required Modeling runtime route. The old CLI path is structurally unable to
|
||||
publish the canonical finding.
|
||||
Idunn now owns the detached worker launch itself. The repo-work scheduler no
|
||||
longer calls `Command::spawn`; it invokes the existing daemon supervisor's
|
||||
generic `service-launch` mouth. That mouth now accepts explicit stdout/stderr
|
||||
artifact paths, owns hidden child creation, and writes its standard typed
|
||||
CultMesh lifecycle receipt. Authenticated live item `idunn-owned-modeling`
|
||||
returned lifecycle receipt
|
||||
`daemon-service-lifecycle-receipt-idunn-repo-work-modeling-idunn-owned-modeling-launch`,
|
||||
process id `45012`, a passing runtime-authored finding, closed Mind/map
|
||||
admission, and overview gate `awaiting-publication` with
|
||||
`bifrost-publication-missing`. Next design the typed Mind-reviewed generation
|
||||
pointer for a non-passing immutable finding; do not mutate the old request or
|
||||
let a filesystem close artifact become the new routing authority.
|
||||
|
||||
## Immediate Re-entry Instruction
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -659,4 +659,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`. Gjallar 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: Move detached repo-work Modeling worker stewardship under Idunn and add a reviewed request generation for non-passing immutable findings. Preserve the now-live typed scheduler path from Soul request through the existing model runtime, canonical Modeling finding, immutable Mind/map admission, and Bifrost gate; Self must remain unable to author or overwrite Modeling evidence.
|
||||
next_action: Design and implement a typed Mind-reviewed request-generation pointer for non-passing immutable repo-work Modeling findings. Idunn now owns detached worker launch through the existing daemon service lifecycle receipt mouth. Preserve each prior request/finding as immutable evidence; filesystem closure artifacts and Self must not become mutable routing authorities.
|
||||
|
|
|
|||
Reference in a new issue