diff --git a/epiphany-core/Cargo.toml b/epiphany-core/Cargo.toml index 63f78fe6..53557844 100644 --- a/epiphany-core/Cargo.toml +++ b/epiphany-core/Cargo.toml @@ -20,27 +20,11 @@ name = "epiphany-semantic-recovery-smoke" path = "src/bin/epiphany-semantic-recovery-smoke.rs" required-features = ["semantic-recovery-smoke"] -[[bin]] -name = "epiphany-atlas-publisher" -path = "src/bin/epiphany-atlas-publisher.rs" - -[[bin]] -name = "epiphany-model-entanglement-projector" -path = "src/bin/epiphany-model-entanglement-projector.rs" - -[[bin]] -name = "epiphany-atlas-impact-ingress" -path = "src/bin/epiphany-atlas-impact-ingress.rs" - [[bin]] name = "epiphany-workspace-coverage-recovery-smoke" path = "src/bin/epiphany-workspace-coverage-recovery-smoke.rs" required-features = ["workspace-coverage-recovery-smoke"] -[[bin]] -name = "epiphany-release" -path = "src/bin/epiphany-release.rs" - [[bin]] name = "epiphany-agent-memory-store" path = "src/bin/epiphany-agent-memory-store.rs" @@ -61,42 +45,14 @@ path = "src/bin/epiphany-cluster-daemon.rs" name = "epiphany-cultmesh-smoke" path = "src/bin/epiphany-cultmesh-smoke.rs" -[[bin]] -name = "epiphany-daemon-supervisor" -path = "src/bin/epiphany-daemon-supervisor.rs" - [[bin]] name = "epiphany-daemon-survival-rehearsal-smoke" path = "src/bin/epiphany-daemon-survival-rehearsal-smoke.rs" -[[bin]] -name = "epiphany-frontier-proposal" -path = "src/bin/epiphany-frontier-proposal.rs" - -[[bin]] -name = "epiphany-hands-action" -path = "src/bin/epiphany-hands-action.rs" - [[bin]] name = "epiphany-hands-action-smoke" path = "src/bin/epiphany-hands-action-smoke.rs" -[[bin]] -name = "epiphany-heartbeat-store" -path = "src/bin/epiphany-heartbeat-store.rs" - -[[bin]] -name = "epiphany-host-identity" -path = "src/bin/epiphany-host-identity.rs" - -[[bin]] -name = "epiphany-memory-semantic" -path = "src/bin/epiphany-memory-semantic.rs" - -[[bin]] -name = "epiphany-memory-semantic-projector" -path = "src/bin/epiphany-memory-semantic-projector.rs" - [[bin]] name = "epiphany-mvp-coordinator-smoke" path = "src/bin/epiphany-mvp-coordinator-smoke.rs" @@ -121,30 +77,14 @@ path = "src/bin/epiphany-persona-discord-crossing-fixture.rs" name = "epiphany-persona-discord-rudp-client-fixture" path = "src/bin/epiphany-persona-discord-rudp-client-fixture.rs" -[[bin]] -name = "epiphany-persona-discord-permit" -path = "src/bin/epiphany-persona-discord-permit.rs" - -[[bin]] -name = "epiphany-persona-discord-permit-identity" -path = "src/bin/epiphany-persona-discord-permit-identity.rs" - [[bin]] name = "epiphany-persona-feedback-causal-smoke" path = "src/bin/epiphany-persona-feedback-causal-smoke.rs" -[[bin]] -name = "epiphany-persona-feedback-ingress" -path = "src/bin/epiphany-persona-feedback-ingress.rs" - [[bin]] name = "epiphany-persona-memory-recall-smoke" path = "src/bin/epiphany-persona-memory-recall-smoke.rs" -[[bin]] -name = "epiphany-persona-mouth-identity" -path = "src/bin/epiphany-persona-mouth-identity.rs" - [[bin]] name = "epiphany-public-source-gate-probe" path = "src/bin/epiphany-public-source-gate-probe.rs" @@ -162,102 +102,6 @@ path = "src/bin/epiphany-persona-reddit.rs" name = "epiphany-prepare-compaction" path = "src/bin/epiphany-prepare-compaction.rs" -[[bin]] -name = "epiphany-repo" -path = "src/bin/epiphany-repo.rs" - -[[bin]] -name = "epiphany-repo-adoption-request-family-smoke" -path = "src/bin/epiphany-repo-adoption-request-family-smoke.rs" - -[[bin]] -name = "epiphany-repo-birth-runner" -path = "src/bin/epiphany-repo-birth-runner.rs" - -[[bin]] -name = "epiphany-repo-closure-source-grounding-smoke" -path = "src/bin/epiphany-repo-closure-source-grounding-smoke.rs" - -[[bin]] -name = "epiphany-repo-credit-request-family-smoke" -path = "src/bin/epiphany-repo-credit-request-family-smoke.rs" - -[[bin]] -name = "epiphany-repo-dependency-policy-family-smoke" -path = "src/bin/epiphany-repo-dependency-policy-family-smoke.rs" - -[[bin]] -name = "epiphany-repo-deployment-request-family-smoke" -path = "src/bin/epiphany-repo-deployment-request-family-smoke.rs" - -[[bin]] -name = "epiphany-repo-interpreter-brief-family-smoke" -path = "src/bin/epiphany-repo-interpreter-brief-family-smoke.rs" - -[[bin]] -name = "epiphany-repo-livefire-closure-smoke" -path = "src/bin/epiphany-repo-livefire-closure-smoke.rs" - -[[bin]] -name = "epiphany-repo-maintainer-review-request-family-smoke" -path = "src/bin/epiphany-repo-maintainer-review-request-family-smoke.rs" - -[[bin]] -name = "epiphany-repo-objective-draft-family-smoke" -path = "src/bin/epiphany-repo-objective-draft-family-smoke.rs" - -[[bin]] -name = "epiphany-repo-personality" -path = "src/bin/epiphany-repo-personality.rs" - -[[bin]] -name = "epiphany-repo-personality-smoke" -path = "src/bin/epiphany-repo-personality-smoke.rs" - -[[bin]] -name = "epiphany-repo-pr-request-family-smoke" -path = "src/bin/epiphany-repo-pr-request-family-smoke.rs" - -[[bin]] -name = "epiphany-repo-publication-request-family-smoke" -path = "src/bin/epiphany-repo-publication-request-family-smoke.rs" - -[[bin]] -name = "epiphany-repo-readiness-review-request-family-smoke" -path = "src/bin/epiphany-repo-readiness-review-request-family-smoke.rs" - -[[bin]] -name = "epiphany-repo-scheduling-request-family-smoke" -path = "src/bin/epiphany-repo-scheduling-request-family-smoke.rs" - -[[bin]] -name = "epiphany-repo-secret-policy-family-smoke" -path = "src/bin/epiphany-repo-secret-policy-family-smoke.rs" - -[[bin]] -name = "epiphany-repository-body" -path = "src/bin/epiphany-repository-body.rs" - -[[bin]] -name = "epiphany-repo-sync-request-family-smoke" -path = "src/bin/epiphany-repo-sync-request-family-smoke.rs" - -[[bin]] -name = "epiphany-repo-tool-request-family-smoke" -path = "src/bin/epiphany-repo-tool-request-family-smoke.rs" - -[[bin]] -name = "epiphany-repo-verification-request-family-smoke" -path = "src/bin/epiphany-repo-verification-request-family-smoke.rs" - -[[bin]] -name = "epiphany-repo-work-order-family-smoke" -path = "src/bin/epiphany-repo-work-order-family-smoke.rs" - -[[bin]] -name = "epiphany-repo-work-service-lifecycle-smoke" -path = "src/bin/epiphany-repo-work-service-lifecycle-smoke.rs" - [[bin]] name = "epiphany-rider-bridge" path = "src/bin/epiphany-rider-bridge.rs" @@ -266,30 +110,14 @@ path = "src/bin/epiphany-rider-bridge.rs" name = "epiphany-rider-bridge-smoke" path = "src/bin/epiphany-rider-bridge-smoke.rs" -[[bin]] -name = "epiphany-runtime-spine" -path = "src/bin/epiphany-runtime-spine.rs" - [[bin]] name = "epiphany-runtime-store-benchmark" path = "src/bin/epiphany-runtime-store-benchmark.rs" -[[bin]] -name = "epiphany-runtime-store-migrate" -path = "src/bin/epiphany-runtime-store-migrate.rs" - -[[bin]] -name = "epiphany-state" -path = "src/bin/epiphany-state.rs" - [[bin]] name = "epiphany-state-ledger-store" path = "src/bin/epiphany-state-ledger-store.rs" -[[bin]] -name = "epiphany-swarm" -path = "src/bin/epiphany-swarm.rs" - [[bin]] name = "epiphany-unity-bridge" path = "src/bin/epiphany-unity-bridge.rs" @@ -298,10 +126,6 @@ path = "src/bin/epiphany-unity-bridge.rs" name = "epiphany-unity-bridge-smoke" path = "src/bin/epiphany-unity-bridge-smoke.rs" -[[bin]] -name = "epiphany-verse-query" -path = "src/bin/epiphany-verse-query.rs" - [[bin]] name = "epiphany-void-memory" path = "src/bin/epiphany-void-memory.rs" @@ -314,11 +138,6 @@ path = "src/bin/epiphany-weksa-interlingua-smoke.rs" name = "epiphany-work" path = "src/bin/epiphany-work.rs" -[[bin]] -name = "epiphany-workspace-coverage-projector" -path = "src/bin/epiphany-workspace-coverage-projector.rs" - - [dependencies] anyhow = "1" bm25 = "2.3.2" diff --git a/epiphany-core/src/agent_launch.rs b/epiphany-core/src/agent_launch.rs index af584059..03ac9a9b 100644 --- a/epiphany-core/src/agent_launch.rs +++ b/epiphany-core/src/agent_launch.rs @@ -916,10 +916,6 @@ pub struct EpiphanyRolePromptConfig { #[allow(dead_code)] pub research: String, #[allow(dead_code)] - pub repo_personality: String, - #[allow(dead_code)] - pub repo_memory: String, - #[allow(dead_code)] pub persona: String, } @@ -1123,11 +1119,6 @@ mod tests { ("roles.modeling", prompts.roles.modeling.as_str()), ("roles.verification", prompts.roles.verification.as_str()), ("roles.research", prompts.roles.research.as_str()), - ( - "roles.repo_personality", - prompts.roles.repo_personality.as_str(), - ), - ("roles.repo_memory", prompts.roles.repo_memory.as_str()), ("roles.persona", prompts.roles.persona.as_str()), ( "implementation.continue_template", diff --git a/epiphany-core/src/bin/epiphany-repo-adoption-request-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-adoption-request-family-smoke.rs deleted file mode 100644 index 5ce04239..00000000 --- a/epiphany-core/src/bin/epiphany-repo-adoption-request-family-smoke.rs +++ /dev/null @@ -1,398 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-adoption-request-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo Adoption Request Family Smoke\n\nThis repository proves branch-local Mind adoption requests.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - git(["add", "README.md"], &repo)?; - git( - ["commit", "-m", "Seed repo adoption request smoke body"], - &repo, - )?; - git( - ["switch", "-c", "epiphany/repo-adoption-request-family"], - &repo, - )?; - - let item = "repo-adoption-request-family"; - let target_path = ".epiphany/adoption-requests/repo-adoption-request-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-adoption-request-family-smoke", - "--topic", - "repo-adoption-request-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-adoption-request-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Ask Mind to adopt or refuse a reviewable Objective Draft before Self schedules branch-local work.", - "--candidate-action-ref", - "candidate-action://repo-adoption-request-family/mind-review", - "--candidate-action-ref", - "candidate-action://repo-adoption-request-family/refuse-or-adopt", - "--public-discussion-ref", - "epiphany-global/persona-collaboration/repo-adoption-request-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-adoption-request", - "--model-ref", - "repo-adoption-request-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - let close_path = repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")); - let close = read_json(&close_path)?; - let request_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.adoption_request", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - require_text( - &request_text, - "schema_version = \"epiphany.repo_adoption_request.v0\"", - )?; - require_text( - &request_text, - "safe_action_family = \"repo.adoption_request\"", - )?; - require_text(&request_text, "[request]")?; - require_text(&request_text, "status = \"awaiting-mind-review\"")?; - require_text( - &request_text, - "requested_decision = \"adopt-or-refuse-objective-draft\"", - )?; - require_text(&request_text, "mind_review_required = true")?; - require_text(&request_text, "mind_state_commit_required = true")?; - require_text(&request_text, "self_scheduling_after_mind_only = true")?; - require_text(&request_text, "[decision_contract]")?; - require_text( - &request_text, - "allowed_verdicts = [\"adopted\", \"refused\", \"needs-more-consensus\"]", - )?; - require_text( - &request_text, - "requires_receipt = \"epiphany.mind.gateway_review\"", - )?; - require_text( - &request_text, - "requires_commit_receipt_if_adopted = \"epiphany.mind.state_commit_receipt\"", - )?; - require_text(&request_text, "does_not_modify_state = true")?; - require_text(&request_text, "objective_draft_required = true")?; - require_text(&request_text, "consensus_brief_required = true")?; - require_text(&request_text, "objective_adoption_authorized = false")?; - require_text(&request_text, "state_commit_authorized = false")?; - require_text(&request_text, "self_scheduling_authorized = false")?; - require_text(&request_text, "hands_action_authorized = false")?; - require_text(&request_text, "cross_body_mutation_authorized = false")?; - require_text(&request_text, "private_state_exposed = false")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_work_adoption_request_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "awaitingMindReview": true, - "doesNotModifyState": true, - "stateCommitAuthorized": false, - "selfSchedulingAuthorized": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!( - "expected adoption request text to contain {needle:?}" - )) - } -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .with_context(|| format!("missing value for {flag}")) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} diff --git a/epiphany-core/src/bin/epiphany-repo-birth-runner.rs b/epiphany-core/src/bin/epiphany-repo-birth-runner.rs deleted file mode 100644 index 94ebcf76..00000000 --- a/epiphany-core/src/bin/epiphany-repo-birth-runner.rs +++ /dev/null @@ -1,533 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::{Path, PathBuf}; -use std::process::Child; -use std::process::Command; -use std::process::Stdio; -use std::thread; -use std::time::{Duration, Instant}; - -const DEFAULT_MODEL_RUNTIME_BIN: &str = "epiphany-model-runtime"; -const DEFAULT_TIMEOUT_SECONDS: u64 = 900; - -fn main() -> Result<()> { - let args = Args::parse()?; - let summary = run(args)?; - println!("{}", serde_json::to_string_pretty(&summary)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - repo: PathBuf, - baseline: PathBuf, - artifact_dir: PathBuf, - init_store: PathBuf, - agent_store: PathBuf, - heartbeat_store: PathBuf, - runtime_store: PathBuf, - model_runtime_bin: String, - model_provider: String, - executor: String, - mode: String, - model: Option, - timeout_seconds: u64, - auto_accept: bool, -} - -impl Args { - fn parse() -> Result { - let root = env::current_dir().context("failed to resolve current dir")?; - let mut args = env::args().skip(1); - let mut parsed = Args { - repo: root.clone(), - baseline: root - .join(".epiphany-imports") - .join("repo-personality-terrain") - .join("baseline.msgpack"), - artifact_dir: root.join(".epiphany-birth-runner"), - init_store: root.join("state").join("repo-initialization.msgpack"), - agent_store: root.join("state").join("agents.msgpack"), - heartbeat_store: root.join("state").join("agent-heartbeats.msgpack"), - runtime_store: root.join("state").join("runtime-spine.msgpack"), - model_runtime_bin: DEFAULT_MODEL_RUNTIME_BIN.to_string(), - model_provider: "openai-codex".to_string(), - executor: "model-runtime".to_string(), - mode: "plan".to_string(), - model: None, - timeout_seconds: DEFAULT_TIMEOUT_SECONDS, - auto_accept: false, - }; - while let Some(arg) = args.next() { - match arg.as_str() { - "--repo" => parsed.repo = take_path(&mut args, "--repo")?, - "--baseline" => parsed.baseline = take_path(&mut args, "--baseline")?, - "--artifact-dir" => parsed.artifact_dir = take_path(&mut args, "--artifact-dir")?, - "--init-store" => parsed.init_store = take_path(&mut args, "--init-store")?, - "--agent-store" => parsed.agent_store = take_path(&mut args, "--agent-store")?, - "--heartbeat-store" => { - parsed.heartbeat_store = take_path(&mut args, "--heartbeat-store")?; - } - "--runtime-store" => { - parsed.runtime_store = take_path(&mut args, "--runtime-store")? - } - "--model-runtime-bin" => { - parsed.model_runtime_bin = take_string(&mut args, "--model-runtime-bin")?; - } - "--openai-runtime-bin" => { - parsed.model_runtime_bin = take_string(&mut args, "--openai-runtime-bin")?; - } - "--model-provider" => { - parsed.model_provider = take_string(&mut args, "--model-provider")?; - } - "--executor" => parsed.executor = take_string(&mut args, "--executor")?, - "--mode" => parsed.mode = take_string(&mut args, "--mode")?, - "--model" => parsed.model = Some(take_string(&mut args, "--model")?), - "--timeout-seconds" => { - parsed.timeout_seconds = - take_string(&mut args, "--timeout-seconds")?.parse()?; - } - "--auto-accept" => parsed.auto_accept = true, - other => return Err(anyhow!("unknown argument {other:?}")), - } - } - if !matches!(parsed.mode.as_str(), "plan" | "run") { - return Err(anyhow!("--mode must be plan or run")); - } - if parsed.executor == "openai-runtime" { - parsed.executor = "model-runtime".to_string(); - } - if parsed.executor != "model-runtime" { - return Err(anyhow!( - "--executor must be model-runtime; Codex CLI execution is not an Epiphany agent prompt path" - )); - } - Ok(parsed) - } -} - -fn run(args: Args) -> Result { - fs::create_dir_all(&args.artifact_dir) - .with_context(|| format!("failed to create {}", args.artifact_dir.display()))?; - let startup_dir = args.artifact_dir.join("startup"); - fs::create_dir_all(&startup_dir)?; - let startup = run_repo_personality_json(&[ - "startup".to_string(), - "--repo".to_string(), - args.repo.display().to_string(), - "--baseline".to_string(), - args.baseline.display().to_string(), - "--artifact-dir".to_string(), - startup_dir.display().to_string(), - "--init-store".to_string(), - args.init_store.display().to_string(), - ])?; - let packets = startup["generatedPackets"] - .as_array() - .cloned() - .unwrap_or_default(); - let mut executions = Vec::new(); - for packet in packets { - let kind = packet["kind"].as_str().unwrap_or("unknown"); - let packet_path = path_from_value(&packet["packetPath"])?; - let execution_dir = args.artifact_dir.join(sanitize(kind)); - fs::create_dir_all(&execution_dir)?; - let packet_value = read_json(&packet_path)?; - let prompt_path = execution_dir.join("prompt.md"); - let schema_path = execution_dir.join("output-schema.json"); - let result_path = execution_dir.join("result.json"); - let request_path = execution_dir.join("model-request.json"); - let runtime_summary_path = execution_dir.join("model-runtime-summary.json"); - let stdout_path = execution_dir.join("executor.stdout.log"); - let stderr_path = execution_dir.join("executor.stderr.log"); - let prompt = render_specialist_prompt(kind, &packet_value)?; - let schema = output_schema_for_kind(kind, &packet_value)?; - write_text(&prompt_path, &prompt)?; - write_json(&schema_path, &schema)?; - let mut execution = json!({ - "kind": kind, - "birthOnly": true, - "executionOwner": "repo-initialization-startup-runner", - "heartbeatParticipant": Value::Null, - "packetPath": packet_path, - "promptPath": prompt_path, - "schemaPath": schema_path, - "resultPath": result_path, - "requestPath": request_path, - "runtimeSummaryPath": runtime_summary_path, - "stdoutPath": stdout_path, - "stderrPath": stderr_path, - "executor": args.executor.clone(), - "modelRuntimeBin": args.model_runtime_bin.clone(), - "modelProvider": args.model_provider.clone(), - "status": "planned", - "acceptCommand": accept_command(kind, &args, &packet_path, &result_path), - }); - if args.mode == "run" { - let run_result = run_openai_runtime_specialist( - &args, - kind, - &prompt, - &schema_path, - &request_path, - &result_path, - &runtime_summary_path, - &stdout_path, - &stderr_path, - )?; - execution["status"] = run_result["status"].clone(); - execution["exitCode"] = run_result["exitCode"].clone(); - execution["timedOut"] = run_result["timedOut"].clone(); - if result_path.exists() { - execution["result"] = read_json(&result_path).unwrap_or_else(|err| { - json!({ - "status": "invalid-json", - "error": err.to_string(), - }) - }); - } - if args.auto_accept && execution["status"] == "completed" { - execution["acceptance"] = run_repo_personality_json(&accept_args( - kind, - &args, - &packet_path, - &result_path, - ))?; - } - } - executions.push(execution); - } - let summary = json!({ - "schemaVersion": "epiphany.repo_birth_runner.v0", - "createdAt": Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true), - "mode": args.mode, - "repo": args.repo, - "baseline": args.baseline, - "artifactDir": args.artifact_dir, - "initStore": args.init_store, - "agentStore": args.agent_store, - "heartbeatStore": args.heartbeat_store, - "runtimeStore": args.runtime_store, - "executor": args.executor, - "modelRuntimeBin": args.model_runtime_bin, - "modelProvider": args.model_provider, - "startup": startup, - "executions": executions, - "requiresReview": !args.auto_accept, - "nextSafeMove": if executions.is_empty() { - "Birth initialization records are present; continue startup." - } else if args.mode == "plan" { - "Review planned startup-only birth executions, then rerun with --mode run." - } else if args.auto_accept { - "Review auto-accepted smoke output and continue startup." - } else { - "Review result.json for each birth specialist, then run the shown accept-init command." - } - }); - write_json( - &args.artifact_dir.join("birth-runner-summary.json"), - &summary, - )?; - Ok(summary) -} - -#[allow(clippy::too_many_arguments)] -fn run_openai_runtime_specialist( - args: &Args, - kind: &str, - prompt: &str, - schema_path: &Path, - request_path: &Path, - result_path: &Path, - runtime_summary_path: &Path, - stdout_path: &Path, - stderr_path: &Path, -) -> Result { - let schema_text = fs::read_to_string(schema_path) - .with_context(|| format!("failed to read {}", schema_path.display()))?; - let model = args.model.clone().unwrap_or_else(|| "gpt-5.4".to_string()); - let request_id = format!( - "repo-birth-{}-{}", - sanitize(kind), - Utc::now().timestamp_millis() - ); - let request = json!({ - "schema_id": "epiphany.model_request.v0", - "request_id": request_id, - "conversation_id": format!("repo-birth-{}", sanitize(kind)), - "provider": args.model_provider.clone(), - "model": model, - "instructions": format!( - "{prompt}\n\nReturn only a single JSON object matching this output schema. No Markdown, no commentary.\n\n```json\n{schema_text}\n```" - ), - "input": [ - { - "UserText": { - "text": "Execute this startup-only Epiphany birth distiller packet and return the reviewable JSON result." - } - } - ], - "reasoning_effort": Value::Null, - "reasoning_summary": Value::Null, - "service_tier": Value::Null, - "output_contract_id": format!("repo-birth-{kind}") - }); - write_json(request_path, &request)?; - - let mut command = Command::new(&args.model_runtime_bin); - command - .arg("model-turn") - .arg("--provider") - .arg(&args.model_provider) - .arg("--store") - .arg(&args.runtime_store) - .arg("--request") - .arg(request_path) - .arg("--objective") - .arg(format!("Run startup-only repo birth specialist {kind}")) - .arg("--default-model") - .arg(&model) - .arg("--output-last-message") - .arg(result_path) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - let child = command - .spawn() - .context("failed to spawn model runtime model-turn")?; - wait_for_child( - child, - args.timeout_seconds, - stdout_path, - stderr_path, - Some(runtime_summary_path), - ) -} - -fn wait_for_child( - mut child: Child, - timeout_seconds: u64, - stdout_path: &Path, - stderr_path: &Path, - stdout_json_copy_path: Option<&Path>, -) -> Result { - let deadline = Instant::now() + Duration::from_secs(timeout_seconds); - loop { - if let Some(status) = child.try_wait()? { - let output = child.wait_with_output()?; - write_bytes(stdout_path, &output.stdout)?; - write_bytes(stderr_path, &output.stderr)?; - if let Some(path) = stdout_json_copy_path { - write_bytes(path, &output.stdout)?; - } - return Ok(json!({ - "status": if status.success() { "completed" } else { "failed" }, - "exitCode": status.code(), - "timedOut": false, - })); - } - if Instant::now() >= deadline { - let _ = child.kill(); - let output = child.wait_with_output()?; - write_bytes(stdout_path, &output.stdout)?; - write_bytes(stderr_path, &output.stderr)?; - if let Some(path) = stdout_json_copy_path { - write_bytes(path, &output.stdout)?; - } - return Ok(json!({ - "status": "timeout", - "exitCode": Value::Null, - "timedOut": true, - })); - } - thread::sleep(Duration::from_millis(250)); - } -} - -fn render_specialist_prompt(kind: &str, packet: &Value) -> Result { - Ok(format!( - "{}\n\n# Startup-Only Birth Packet\n\nYou are executing exactly one repo initialization birth specialist packet. Do not edit files. Do not mutate state. Return only JSON that matches the provided schema. The coordinator/Self will review and decide whether to accept the result.\n\n```json\n{}\n```\n", - packet["prompt"].as_str().unwrap_or(match kind { - "repo-trajectory" => "Act as the Epiphany Repo Trajectory Distiller.", - "repo-personality" => "Act as the Epiphany Repo Personality Distiller.", - "repo-memory" => "Act as the Epiphany Repo Memory Distiller.", - _ => "Act as a startup-only Epiphany birth specialist.", - }), - serde_json::to_string_pretty(packet)? - )) -} - -fn output_schema_for_kind(kind: &str, packet: &Value) -> Result { - let expected = packet.get("expectedOutput").cloned().unwrap_or(Value::Null); - let schema = match kind { - "repo-trajectory" => json!({ - "type": "object", - "required": ["verdict", "summary", "confidence", "selfImage", "trajectoryNarrative", "implicitGoals", "antiGoals", "roleBiases", "selfPatchCandidates", "initializationRecord", "doNotMutate", "nextSafeMove"], - "properties": { - "verdict": {"type": "string", "enum": ["ready-for-review", "needs-more-history", "reject"]}, - "summary": {"type": "string"}, - "confidence": {"type": "number"}, - "selfImage": {"type": "string"}, - "trajectoryNarrative": {"type": "string"}, - "implicitGoals": {"type": "array", "items": {"type": "string"}}, - "antiGoals": {"type": "array", "items": {"type": "string"}}, - "roleBiases": {"type": "array", "items": {"type": "object"}}, - "selfPatchCandidates": {"type": "array", "items": {"type": "object"}}, - "initializationRecord": {"type": "object"}, - "doNotMutate": {"type": "array", "items": {"type": "string"}}, - "nextSafeMove": {"type": "string"} - }, - "additionalProperties": true, - "xEpiphanyExpectedOutput": expected, - }), - "repo-personality" => json!({ - "type": "object", - "required": ["verdict", "summary", "confidence", "roleQuirks", "selfPatchCandidates", "initializationRecord", "doNotMutate", "nextSafeMove"], - "properties": { - "verdict": {"type": "string", "enum": ["ready-for-review", "needs-more-terrain", "reject"]}, - "summary": {"type": "string"}, - "confidence": {"type": "number"}, - "roleQuirks": {"type": "array", "items": {"type": "object"}}, - "selfPatchCandidates": {"type": "array", "items": {"type": "object"}}, - "initializationRecord": {"type": "object"}, - "doNotMutate": {"type": "array", "items": {"type": "string"}}, - "nextSafeMove": {"type": "string"} - }, - "additionalProperties": true, - "xEpiphanyExpectedOutput": expected, - }), - "repo-memory" => json!({ - "type": "object", - "required": ["verdict", "summary", "confidence", "roleMemoryPatches", "initializationRecord", "doNotMutate", "nextSafeMove"], - "properties": { - "verdict": {"type": "string", "enum": ["ready-for-review", "needs-more-terrain", "reject"]}, - "summary": {"type": "string"}, - "confidence": {"type": "number"}, - "roleMemoryPatches": {"type": "array", "items": {"type": "object"}}, - "globalMemoryCandidates": {"type": "array", "items": {"type": "object"}}, - "initializationRecord": {"type": "object"}, - "doNotMutate": {"type": "array", "items": {"type": "string"}}, - "nextSafeMove": {"type": "string"} - }, - "additionalProperties": true, - "xEpiphanyExpectedOutput": expected, - }), - other => return Err(anyhow!("unsupported birth specialist kind {other:?}")), - }; - Ok(schema) -} - -fn accept_command(kind: &str, args: &Args, packet_path: &Path, result_path: &Path) -> Vec { - accept_args(kind, args, packet_path, result_path) -} - -fn accept_args(kind: &str, args: &Args, packet_path: &Path, result_path: &Path) -> Vec { - let mut out = vec![ - "accept-init".to_string(), - "--init-store".to_string(), - args.init_store.display().to_string(), - "--packet".to_string(), - packet_path.display().to_string(), - "--kind".to_string(), - kind.to_string(), - "--accepted-by".to_string(), - "Self".to_string(), - "--summary".to_string(), - format!("Accepted {kind} birth specialist result after review."), - "--result".to_string(), - result_path.display().to_string(), - "--agent-store".to_string(), - args.agent_store.display().to_string(), - "--apply-self-patches".to_string(), - "true".to_string(), - ]; - if kind == "repo-personality" { - out.extend([ - "--heartbeat-store".to_string(), - args.heartbeat_store.display().to_string(), - "--apply-heartbeat-seeds".to_string(), - "true".to_string(), - ]); - } - out -} - -fn run_repo_personality_json(args: &[String]) -> Result { - let exe = native_exe("epiphany-repo-personality"); - let output = Command::new(&exe) - .args(args) - .output() - .with_context(|| format!("failed to run {}", exe.display()))?; - if !output.status.success() { - return Err(anyhow!( - "epiphany-repo-personality failed: {}", - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout) - .with_context(|| format!("epiphany-repo-personality returned invalid JSON")) -} - -fn native_exe(name: &str) -> PathBuf { - let target_dir = env::var_os("CARGO_TARGET_DIR") - .unwrap_or_else(|| r"C:\Users\Meta\.cargo-target-codex".into()); - PathBuf::from(target_dir) - .join("debug") - .join(format!("{name}{}", env::consts::EXE_SUFFIX)) -} - -fn read_json(path: &Path) -> Result { - let raw = - fs::read_to_string(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_str(&raw).with_context(|| format!("failed to decode {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn write_text(path: &Path, value: &str) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - fs::write(path, value).with_context(|| format!("failed to write {}", path.display())) -} - -fn write_bytes(path: &Path, value: &[u8]) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - fs::write(path, value).with_context(|| format!("failed to write {}", path.display())) -} - -fn path_from_value(value: &Value) -> Result { - value - .as_str() - .map(PathBuf::from) - .ok_or_else(|| anyhow!("expected path string, got {value}")) -} - -fn sanitize(value: &str) -> String { - value - .chars() - .map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '-' }) - .collect() -} - -fn take_string(args: &mut impl Iterator, name: &str) -> Result { - args.next() - .ok_or_else(|| anyhow!("{name} requires a value")) -} - -fn take_path(args: &mut impl Iterator, name: &str) -> Result { - Ok(PathBuf::from(take_string(args, name)?)) -} diff --git a/epiphany-core/src/bin/epiphany-repo-closure-source-grounding-smoke.rs b/epiphany-core/src/bin/epiphany-repo-closure-source-grounding-smoke.rs deleted file mode 100644 index 19798f62..00000000 --- a/epiphany-core/src/bin/epiphany-repo-closure-source-grounding-smoke.rs +++ /dev/null @@ -1,413 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-closure-source-grounding-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Closure Source Grounding Smoke\n\nThis repository proves Soul can require verification output to cite changed paths.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - git(["add", "README.md"], &repo)?; - git( - ["commit", "-m", "Seed closure source grounding smoke"], - &repo, - )?; - git(["switch", "-c", "epiphany/closure-source-grounding"], &repo)?; - - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "closure-source-grounding-smoke", - "--topic", - "closure-source-grounding", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "closure-source-grounding-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - - let good = drive_item( - &manifest, - &root, - &repo, - "closure-source-grounded", - "Prove default closure verification cites the changed path.", - None, - )?; - let bad = drive_item( - &manifest, - &root, - &repo, - "closure-proofless-output", - "Prove source grounding blocks proofless verification output.", - Some("Write-Output 'ok'"), - )?; - - require_eq(&good.close, &["status"], "closed")?; - require_eq(&good.close, &["soul", "verdict"], "passed")?; - require_bool( - &good.close, - &[ - "closureReview", - "verificationSourceGrounding", - "allDeclaredPathsMentionedByVerificationOutput", - ], - true, - )?; - require_bool( - &good.close, - &["closureReview", "verificationSourceGrounding", "required"], - true, - )?; - require_eq(&bad.close, &["status"], "verification-failed")?; - require_eq(&bad.close, &["soul", "verdict"], "failed")?; - require_eq( - &bad.close, - &["closureReview", "verificationSourceGrounding", "status"], - "failed", - )?; - require_bool( - &bad.close, - &[ - "closureReview", - "verificationSourceGrounding", - "allDeclaredPathsMentionedByVerificationOutput", - ], - false, - )?; - require_bool( - &bad.close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool( - &bad.close, - &[ - "closureReview", - "sourceGrounding", - "verificationOutputMentionsChangedPaths", - ], - false, - )?; - require_eq( - &bad.close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool(&good.close, &["privateStateExposed"], false)?; - require_bool(&bad.close, &["privateStateExposed"], false)?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_work_closure_source_grounding_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "passItem": good.item, - "passCloseStatus": good.close["status"], - "passSourceGroundingStatus": good.close["closureReview"]["verificationSourceGrounding"]["status"], - "passVerificationOutputMentionsChangedPaths": good.close["closureReview"]["sourceGrounding"]["verificationOutputMentionsChangedPaths"], - "blockItem": bad.item, - "blockCloseStatus": bad.close["status"], - "blockSoulVerdict": bad.close["soul"]["verdict"], - "blockSourceGroundingStatus": bad.close["closureReview"]["verificationSourceGrounding"]["status"], - "blockPathScopeMatched": bad.close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "blockFamilyAssertionsStatus": bad.close["closureReview"]["familyAssertions"]["status"], - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -struct ItemProof { - item: String, - close: Value, -} - -fn drive_item( - manifest: &Path, - root: &Path, - repo: &Path, - item: &str, - summary: &str, - verification_command: Option<&str>, -) -> Result { - cargo_json( - manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - summary, - ], - root, - )?; - cargo_json( - manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(repo)?, - "--item", - item, - "--action-family", - "repo-status-section", - "--model-ref", - "closure-source-grounding-smoke-imagination-v1", - "--model-authored", - ], - root, - )?; - for _ in 0..3 { - cargo_json( - manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(repo)?, - "--epiphany-root", - path_str(root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - root, - )?; - } - - let mut close_args = vec![ - "close".to_string(), - "--workspace".to_string(), - path_str(repo)?.to_string(), - "--item".to_string(), - item.to_string(), - "--require-source-grounding".to_string(), - ]; - if let Some(command) = verification_command { - close_args.push("--verification-command".to_string()); - close_args.push(command.to_string()); - } - let close_arg_refs = close_args.iter().map(String::as_str).collect::>(); - let close = cargo_json(manifest, "epiphany-work", &close_arg_refs, root)?; - Ok(ItemProof { - item: item.to_string(), - close, - }) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git output failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} - -fn take_path(args: &mut impl Iterator, name: &str) -> Result { - Ok(PathBuf::from( - args.next() - .ok_or_else(|| anyhow!("missing value for {name}"))?, - )) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = value_at(value, path) - .and_then(Value::as_str) - .ok_or_else(|| anyhow!("missing string at {}", path.join(".")))?; - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = value_at(value, path) - .and_then(Value::as_bool) - .ok_or_else(|| anyhow!("missing bool at {}", path.join(".")))?; - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {actual}", - path.join(".") - )) - } -} - -fn value_at<'a>(value: &'a Value, path: &[&str]) -> Option<&'a Value> { - let mut current = value; - for segment in path { - current = current.get(*segment)?; - } - Some(current) -} diff --git a/epiphany-core/src/bin/epiphany-repo-credit-request-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-credit-request-family-smoke.rs deleted file mode 100644 index b64a043a..00000000 --- a/epiphany-core/src/bin/epiphany-repo-credit-request-family-smoke.rs +++ /dev/null @@ -1,447 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-credit-request-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo Credit Request Family Smoke\n\nThis repository proves branch-local Bifrost credit request cargo without credit ledger authority.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - git(["add", "README.md"], &repo)?; - git( - ["commit", "-m", "Seed repo credit request smoke body"], - &repo, - )?; - git( - ["switch", "-c", "epiphany/repo-credit-request-family"], - &repo, - )?; - - let item = "repo-credit-request-family"; - let target_path = ".epiphany/credit-requests/repo-credit-request-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-credit-request-family-smoke", - "--topic", - "repo-credit-request-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-credit-request-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Ask Bifrost to prepare credit ledger review for an accepted artifact without granting credit authority.", - "--candidate-action-ref", - "candidate-action://repo-credit-request-family/credit-packet", - "--candidate-action-ref", - "candidate-action://repo-credit-request-family/bifrost-ledger-review", - "--public-discussion-ref", - "epiphany-global/persona-collaboration/repo-credit-request-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-credit-request", - "--model-ref", - "repo-credit-request-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - let close_path = repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")); - let close = read_json(&close_path)?; - let request_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.credit_request", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - require_text( - &request_text, - "schema_version = \"epiphany.repo_credit_request.v0\"", - )?; - require_text( - &request_text, - "safe_action_family = \"repo.credit_request\"", - )?; - require_text(&request_text, "[request]")?; - require_text(&request_text, "status = \"awaiting-bifrost-credit-review\"")?; - require_text(&request_text, "requested_owner = \"Bifrost\"")?; - require_text( - &request_text, - "requested_effect = \"record-credit-for-redacted-proof-and-accepted-artifact\"", - )?; - require_text(&request_text, "maintainer_review_request_ref = ")?; - require_text(&request_text, "publication_request_ref = ")?; - require_text(&request_text, "pr_request_ref = ")?; - require_text( - &request_text, - "credit_scope = \"authorship, maintainer review, accepted artifact, public proof, and Bifrost ledger readback\"", - )?; - require_text(&request_text, "[antecedents]")?; - require_text(&request_text, "closure_review_required = true")?; - require_text(&request_text, "soul_verdict_required = true")?; - require_text(&request_text, "mind_commit_required = true")?; - require_text(&request_text, "public_proof_required = true")?; - require_text(&request_text, "maintainer_review_required = true")?; - require_text(&request_text, "accepted_artifact_required = true")?; - require_text(&request_text, "authorship_context_required = true")?; - require_text(&request_text, "[required_receipts]")?; - require_text( - &request_text, - "closure_review = \"epiphany.repo_work_closure_review.v0\"", - )?; - require_text( - &request_text, - "soul_verdict = \"epiphany.soul.verification_verdict\"", - )?; - require_text( - &request_text, - "mind_commit = \"epiphany.mind.state_commit_receipt\"", - )?; - require_text( - &request_text, - "public_proof = \"epiphany.repo_work_public_proof_bundle.v0\"", - )?; - require_text( - &request_text, - "maintainer_review = \"gamecult.maintainer.review_receipt.v0\"", - )?; - require_text( - &request_text, - "accepted_artifact = \"gamecult.artifact.acceptance_receipt.v0\"", - )?; - require_text( - &request_text, - "credit_ledger = \"gamecult.bifrost.credit_receipt.v0\"", - )?; - require_text( - &request_text, - "credit_readback = \"gamecult.bifrost.credit_readback_receipt.v0\"", - )?; - require_text(&request_text, "[credit_packet]")?; - require_text(&request_text, "requires_author_identity = true")?; - require_text(&request_text, "requires_reviewer_identity = true")?; - require_text(&request_text, "requires_accepted_artifact_ref = true")?; - require_text(&request_text, "requires_changed_path_list = true")?; - require_text(&request_text, "requires_public_proof_ref = true")?; - require_text(&request_text, "requires_credit_ledger_target = true")?; - require_text( - &request_text, - "requires_private_state_redaction_check = true", - )?; - require_text(&request_text, "credit_ledger_authorized = false")?; - require_text(&request_text, "bifrost_publication_authorized = false")?; - require_text(&request_text, "github_pr_authorized = false")?; - require_text(&request_text, "merge_authorized = false")?; - require_text(&request_text, "publication_authorized = false")?; - require_text(&request_text, "upstream_sync_authorized = false")?; - require_text(&request_text, "hands_action_authorized = false")?; - require_text(&request_text, "cross_body_mutation_authorized = false")?; - require_text(&request_text, "bifrost_credit_authority_required = true")?; - require_text(&request_text, "private_state_exposed = false")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_credit_request_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "awaitingBifrostCreditReview": true, - "creditLedgerAuthorized": false, - "bifrostPublicationAuthorized": false, - "githubPrAuthorized": false, - "mergeAuthorized": false, - "publicationAuthorized": false, - "upstreamSyncAuthorized": false, - "handsActionAuthorized": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!( - "expected credit request text to contain {needle:?}" - )) - } -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .with_context(|| format!("missing value for {flag}")) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} diff --git a/epiphany-core/src/bin/epiphany-repo-dependency-policy-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-dependency-policy-family-smoke.rs deleted file mode 100644 index 71874dd4..00000000 --- a/epiphany-core/src/bin/epiphany-repo-dependency-policy-family-smoke.rs +++ /dev/null @@ -1,430 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-dependency-policy-request-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo Dependency Policy Request Family Smoke\n\nThis repository proves branch-local dependency policy request cargo without package install authority.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - fs::write( - repo.join("package.json"), - "{\n \"name\": \"epiphany-dependency-policy-smoke\",\n \"version\": \"0.0.0\",\n \"dependencies\": {}\n}\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("package.json").display()))?; - fs::write( - repo.join("Cargo.toml"), - "[package]\nname = \"epiphany-dependency-policy-smoke\"\nversion = \"0.0.0\"\nedition = \"2024\"\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("Cargo.toml").display()))?; - git(["add", "README.md", "package.json", "Cargo.toml"], &repo)?; - git( - ["commit", "-m", "Seed repo dependency policy smoke body"], - &repo, - )?; - git( - [ - "switch", - "-c", - "epiphany/repo-dependency-policy-request-family", - ], - &repo, - )?; - - let item = "repo-dependency-policy-request-family"; - let target_path = - ".epiphany/dependency-policy-requests/repo-dependency-policy-request-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-dependency-policy-request-family-smoke", - "--topic", - "repo-dependency-policy-request-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-dependency-policy-request-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Ask for a reviewed dependency and supply-chain policy before changing package manifests or lockfiles.", - "--candidate-action-ref", - "candidate-action://repo-dependency-policy-request-family/dependency-review", - "--public-discussion-ref", - "epiphany-global/persona-collaboration/repo-dependency-policy-request-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-dependency-policy-request", - "--model-ref", - "repo-dependency-policy-request-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - - let close = read_json( - &repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")), - )?; - let request_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - let package_text = fs::read_to_string(repo.join("package.json")) - .with_context(|| format!("failed to read {}", repo.join("package.json").display()))?; - let cargo_text = fs::read_to_string(repo.join("Cargo.toml")) - .with_context(|| format!("failed to read {}", repo.join("Cargo.toml").display()))?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.dependency_policy_request", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - require_text( - &request_text, - "schema_version = \"epiphany.repo_dependency_policy_request.v0\"", - )?; - require_text( - &request_text, - "safe_action_family = \"repo.dependency_policy_request\"", - )?; - require_text( - &request_text, - "status = \"awaiting-dependency-policy-review\"", - )?; - require_text(&request_text, "routing_owner = \"Self\"")?; - require_text(&request_text, "requires_manifest_inventory = true")?; - require_text(&request_text, "requires_lockfile_policy = true")?; - require_text( - &request_text, - "requires_package_manager_command_review = true", - )?; - require_text(&request_text, "requires_network_fetch_policy = true")?; - require_text(&request_text, "requires_vulnerability_review = true")?; - require_text(&request_text, "requires_license_review = true")?; - require_text(&request_text, "source_grounding_required = true")?; - require_text(&request_text, "eyes_evidence_required = true")?; - require_text( - &request_text, - "dependency_audit = \"gamecult.supply_chain.dependency_audit_receipt.v0\"", - )?; - require_text(&request_text, "requires_manifest_paths = true")?; - require_text(&request_text, "requires_lockfile_paths = true")?; - require_text(&request_text, "requires_package_manager_commands = true")?; - require_text(&request_text, "requires_vulnerability_sources = true")?; - require_text(&request_text, "requires_license_constraints = true")?; - require_text(&request_text, "requires_vendored_code_policy = true")?; - require_text(&request_text, "requires_update_cadence = true")?; - require_text(&request_text, "direct_dependency_update_authority = false")?; - require_text(&request_text, "direct_package_install_authority = false")?; - require_text(&request_text, "direct_lockfile_mutation_authority = false")?; - require_text(&request_text, "direct_network_fetch_authority = false")?; - require_text(&request_text, "direct_ci_mutation_authority = false")?; - require_text(&request_text, "direct_hands_authority = false")?; - require_text(&request_text, "publication_authorized = false")?; - require_text(&request_text, "deployment_authority = false")?; - require_text(&request_text, "service_lifecycle_authority = false")?; - require_text(&request_text, "cross_body_mutation_authorized = false")?; - require_text(&request_text, "private_verse_rummaging = false")?; - require_text(&request_text, "private_state_exposed = false")?; - require_text(&package_text, "\"dependencies\": {}")?; - require_text(&cargo_text, "edition = \"2024\"")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_dependency_policy_request_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "awaitingDependencyPolicyReview": true, - "dependencyUpdateAuthorized": false, - "packageInstallAuthorized": false, - "lockfileMutationAuthorized": false, - "networkFetchAuthorized": false, - "ciMutationAuthorized": false, - "deploymentAuthority": false, - "publicationAuthorized": false, - "serviceLifecycleAuthority": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to equal {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool) - .unwrap_or(false); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to equal {expected}, got {actual}", - path.join(".") - )) - } -} - -fn require_text(text: &str, needle: &str) -> Result<()> { - if text.contains(needle) { - Ok(()) - } else { - Err(anyhow!("expected text to contain {needle:?}")) - } -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .ok_or_else(|| anyhow!("missing value for {flag}")) -} diff --git a/epiphany-core/src/bin/epiphany-repo-deployment-request-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-deployment-request-family-smoke.rs deleted file mode 100644 index d9cc520d..00000000 --- a/epiphany-core/src/bin/epiphany-repo-deployment-request-family-smoke.rs +++ /dev/null @@ -1,437 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-deployment-request-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo Deployment Request Family Smoke\n\nThis repository proves branch-local deployment request cargo without deploy authority.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - fs::create_dir_all(repo.join("deploy")) - .with_context(|| format!("failed to create {}", repo.join("deploy").display()))?; - fs::write( - repo.join("deploy").join("idunn-deploy.ps1"), - "# reviewed by Idunn before use\nWrite-Output 'deployment script placeholder'\n", - ) - .with_context(|| { - format!( - "failed to seed {}", - repo.join("deploy").join("idunn-deploy.ps1").display() - ) - })?; - git(["add", "README.md", "deploy/idunn-deploy.ps1"], &repo)?; - git( - ["commit", "-m", "Seed repo deployment request smoke body"], - &repo, - )?; - git( - ["switch", "-c", "epiphany/repo-deployment-request-family"], - &repo, - )?; - - let item = "repo-deployment-request-family"; - let target_path = ".epiphany/deployment-requests/repo-deployment-request-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-deployment-request-family-smoke", - "--topic", - "repo-deployment-request-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-deployment-request-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Ask for a reviewed Idunn-owned git-push deployment request without granting deployment authority.", - "--candidate-action-ref", - "candidate-action://repo-deployment-request-family/idunn-review", - "--public-discussion-ref", - "epiphany-global/persona-collaboration/repo-deployment-request-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-deployment-request", - "--model-ref", - "repo-deployment-request-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - - let close = read_json( - &repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")), - )?; - let request_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - let deploy_script_text = fs::read_to_string(repo.join("deploy").join("idunn-deploy.ps1")) - .with_context(|| { - format!( - "failed to read {}", - repo.join("deploy").join("idunn-deploy.ps1").display() - ) - })?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.deployment_request", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - require_text( - &request_text, - "schema_version = \"epiphany.repo_deployment_request.v0\"", - )?; - require_text( - &request_text, - "safe_action_family = \"repo.deployment_request\"", - )?; - require_text(&request_text, "status = \"awaiting-idunn-review\"")?; - require_text(&request_text, "routing_owner = \"Self\"")?; - require_text( - &request_text, - "required_reviewers = [\"Maintainer\", \"Soul\", \"Mind\", \"Bifrost\"]", - )?; - require_text(&request_text, "execution_owner = \"Idunn\"")?; - require_text( - &request_text, - "deployment_trigger = \"git-push-observed-by-idunn\"", - )?; - require_text(&request_text, "requires_idunn_receipt = true")?; - require_text(&request_text, "requires_aftercare_audit = true")?; - require_text(&request_text, "secret_policy_review_required = true")?; - require_text( - &request_text, - "idunn_deployment = \"gamecult.idunn.deployment_receipt.v0\"", - )?; - require_text( - &request_text, - "aftercare_audit = \"gamecult.idunn.deployment_aftercare_audit.v0\"", - )?; - require_text(&request_text, "requires_deployment_script_ref = true")?; - require_text(&request_text, "requires_watched_ref = true")?; - require_text(&request_text, "requires_source_commit_sha = true")?; - require_text(&request_text, "requires_script_hash = true")?; - require_text(&request_text, "requires_script_review_ref = true")?; - require_text(&request_text, "requires_host_access_policy_ref = true")?; - require_text(&request_text, "requires_secret_policy_ref = true")?; - require_text(&request_text, "requires_rollback_plan = true")?; - require_text(&request_text, "requires_aftercare_checks = true")?; - require_text(&request_text, "direct_deployment_authority = false")?; - require_text(&request_text, "direct_ssh_authority = false")?; - require_text(&request_text, "direct_git_push_authority = false")?; - require_text(&request_text, "direct_service_lifecycle_authority = false")?; - require_text(&request_text, "direct_hands_authority = false")?; - require_text(&request_text, "publication_authorized = false")?; - require_text(&request_text, "cross_body_mutation_authorized = false")?; - require_text(&request_text, "private_verse_rummaging = false")?; - require_text(&request_text, "private_state_exposed = false")?; - require_text(&deploy_script_text, "deployment script placeholder")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_deployment_request_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "awaitingIdunnReview": true, - "deploymentTrigger": "git-push-observed-by-idunn", - "deploymentAuthority": false, - "sshAuthority": false, - "gitPushAuthority": false, - "serviceLifecycleAuthority": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = value_at_path(value, path) - .and_then(Value::as_str) - .ok_or_else(|| anyhow!("missing string at {}", path.join(".")))?; - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {:?}, got {:?}", - path.join("."), - expected, - actual - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = value_at_path(value, path) - .and_then(Value::as_bool) - .ok_or_else(|| anyhow!("missing bool at {}", path.join(".")))?; - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {}, got {}", - path.join("."), - expected, - actual - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!("expected text to contain {needle:?}")) - } -} - -fn value_at_path<'a>(value: &'a Value, path: &[&str]) -> Option<&'a Value> { - let mut cursor = value; - for segment in path { - if let Ok(index) = segment.parse::() { - cursor = cursor.as_array()?.get(index)?; - } else { - cursor = cursor.get(*segment)?; - } - } - Some(cursor) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} - -fn take_path(args: &mut impl Iterator, name: &str) -> Result { - Ok(PathBuf::from( - args.next() - .ok_or_else(|| anyhow!("missing value for {name}"))?, - )) -} diff --git a/epiphany-core/src/bin/epiphany-repo-interpreter-brief-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-interpreter-brief-family-smoke.rs deleted file mode 100644 index b6dcfd46..00000000 --- a/epiphany-core/src/bin/epiphany-repo-interpreter-brief-family-smoke.rs +++ /dev/null @@ -1,437 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-interpreter-brief-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo Interpreter Brief Family Smoke\n\nThis repository proves branch-local Mind-owned interpretation briefs.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - git(["add", "README.md"], &repo)?; - git( - ["commit", "-m", "Seed repo interpreter brief smoke body"], - &repo, - )?; - git( - ["switch", "-c", "epiphany/repo-interpreter-brief-family"], - &repo, - )?; - - let item = "repo-interpreter-brief-family"; - let target_path = ".epiphany/interpreter-briefs/repo-interpreter-brief-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-interpreter-brief-family-smoke", - "--topic", - "repo-interpreter-brief-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-interpreter-brief-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Turn rough Persona and Imagination consensus into a Mind-owned interpreter brief before any work item receives Hands authority.", - "--candidate-action-ref", - "candidate-action://repo-interpreter-brief-family/interpret-action-semantics", - "--candidate-action-ref", - "candidate-action://repo-interpreter-brief-family/request-soul-grounding", - "--public-discussion-ref", - "epiphany-global/persona-collaboration/repo-interpreter-brief-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-interpreter-brief", - "--model-ref", - "repo-interpreter-brief-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - let close_path = repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")); - let close = read_json(&close_path)?; - let brief_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.interpreter_brief", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - require_text( - &brief_text, - "schema_version = \"epiphany.repo_interpreter_brief.v0\"", - )?; - require_text( - &brief_text, - "safe_action_family = \"repo.interpreter_brief\"", - )?; - require_text(&brief_text, "[interpreter]")?; - require_text(&brief_text, "status = \"awaiting-mind-interpretation\"")?; - require_text(&brief_text, "authoring_owner = \"Imagination\"")?; - require_text(&brief_text, "requested_interpreter = \"Mind\"")?; - require_text(&brief_text, "interpretation_admitted = false")?; - require_text( - &brief_text, - "purpose = \"public-pressure-to-action-semantics\"", - )?; - require_text(&brief_text, "requires_consensus_readback = true")?; - require_text(&brief_text, "requires_safe_family_choice = true")?; - require_text(&brief_text, "requires_requested_paths = true")?; - require_text(&brief_text, "requires_verification_asks = true")?; - require_text(&brief_text, "requires_evidence_needs = true")?; - require_text(&brief_text, "candidate_actions_non_authoritative = true")?; - require_text(&brief_text, "[semantic_checks]")?; - require_text(&brief_text, "intent_summary_required = true")?; - require_text(&brief_text, "scope_boundary_required = true")?; - require_text(&brief_text, "requested_paths_required = true")?; - require_text(&brief_text, "verification_required = true")?; - require_text(&brief_text, "evidence_required = true")?; - require_text(&brief_text, "rollback_required = true")?; - require_text(&brief_text, "non_goals_required = true")?; - require_text(&brief_text, "open_questions_required = true")?; - require_text(&brief_text, "consensus_alignment_required = true")?; - require_text(&brief_text, "[allowed_outputs]")?; - require_text(&brief_text, "\"repo.consensus_brief\"")?; - require_text(&brief_text, "\"repo.objective_draft\"")?; - require_text(&brief_text, "\"repo.adoption_request\"")?; - require_text(&brief_text, "\"repo.work_order\"")?; - require_text(&brief_text, "\"repo.verification_request\"")?; - require_text(&brief_text, "\"repo.publication_request\"")?; - require_text(&brief_text, "may_request_replanning = true")?; - require_text(&brief_text, "may_request_more_consensus = true")?; - require_text(&brief_text, "may_adopt_objective = false")?; - require_text(&brief_text, "may_schedule_work = false")?; - require_text(&brief_text, "may_touch_substrate = false")?; - require_text(&brief_text, "may_publish = false")?; - require_text(&brief_text, "may_deploy = false")?; - require_text(&brief_text, "[required_gates]")?; - require_text(&brief_text, "imagination_consensus_required = true")?; - require_text(&brief_text, "mind_review_required = true")?; - require_text(&brief_text, "soul_source_grounding_required = true")?; - require_text(&brief_text, "bifrost_publication_review_required = true")?; - require_text( - &brief_text, - "hands_receipt_required_before_state_change = true", - )?; - require_text( - &brief_text, - "substrate_receipt_required_before_mutation = true", - )?; - require_text( - &brief_text, - "idunn_receipt_required_before_deployment = true", - )?; - require_text(&brief_text, "direct_state_commit_authorized = false")?; - require_text(&brief_text, "objective_adoption_authorized = false")?; - require_text(&brief_text, "self_scheduling_authorized = false")?; - require_text(&brief_text, "substrate_access_authorized = false")?; - require_text(&brief_text, "hands_action_authorized = false")?; - require_text(&brief_text, "shell_command_authorized = false")?; - require_text(&brief_text, "commit_authorized = false")?; - require_text(&brief_text, "publication_authorized = false")?; - require_text(&brief_text, "deployment_execution_authority = false")?; - require_text(&brief_text, "cross_body_mutation_authorized = false")?; - require_text(&brief_text, "private_worker_transcripts_allowed = false")?; - require_text(&brief_text, "raw_result_payloads_allowed = false")?; - require_text(&brief_text, "private_state_exposed = false")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_work_interpreter_brief_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "interpreterIsDraft": true, - "mindOwnsInterpretation": true, - "candidateActionsNonAuthoritative": true, - "directStateCommitAuthorized": false, - "handsActionAuthorized": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!( - "expected interpreter brief text to contain {needle:?}" - )) - } -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .with_context(|| format!("missing value for {flag}")) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} diff --git a/epiphany-core/src/bin/epiphany-repo-livefire-closure-smoke.rs b/epiphany-core/src/bin/epiphany-repo-livefire-closure-smoke.rs deleted file mode 100644 index af9be675..00000000 --- a/epiphany-core/src/bin/epiphany-repo-livefire-closure-smoke.rs +++ /dev/null @@ -1,299 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Debug)] -struct Args { - workspace: PathBuf, - item: String, - expected_path: String, - expected_family: String, - expected_gate: String, - expected_blocker: String, -} - -impl Args { - fn parse() -> Result { - let mut workspace = env::current_dir().context("failed to resolve current directory")?; - let mut item = None; - let mut expected_path = "README.md".to_string(); - let mut expected_family = "repo.status_section".to_string(); - let mut expected_gate = "awaiting-publication".to_string(); - let mut expected_blocker = "bifrost-publication-missing".to_string(); - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--workspace" => workspace = take_path(&mut args, "--workspace")?, - "--item" => item = Some(take_value(&mut args, "--item")?), - "--expected-path" => expected_path = take_value(&mut args, "--expected-path")?, - "--expected-family" => { - expected_family = take_value(&mut args, "--expected-family")? - } - "--expected-gate" => expected_gate = take_value(&mut args, "--expected-gate")?, - "--expected-blocker" => { - expected_blocker = take_value(&mut args, "--expected-blocker")? - } - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - let item = item.ok_or_else(|| anyhow!("--item is required"))?; - Ok(Self { - workspace, - item, - expected_path, - expected_family, - expected_gate, - expected_blocker, - }) - } -} - -fn run_smoke(args: Args) -> Result { - let workspace = args - .workspace - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.workspace.display()))?; - let work_dir = workspace.join(".epiphany").join("work"); - let close_path = work_dir.join(format!("work-close-{}.json", args.item)); - let overview_path = work_dir.join(format!("work-overview-{}.json", args.item)); - let close = read_json(&close_path)?; - let overview = read_json(&overview_path)?; - - require_str( - &close, - &["schemaVersion"], - "epiphany.repo_work_closure_receipt.v0", - )?; - require_str(&close, &["status"], "closed")?; - require_str(&close, &["item"], &args.item)?; - require_bool(&close, &["privateStateExposed"], false)?; - require_str(&close, &["soul", "verdict"], "passed")?; - require_bool(&close, &["authority", "branchLocalOnly"], true)?; - require_bool(&close, &["authority", "publicationAuthorized"], false)?; - require_bool(&close, &["authority", "mergeAuthorized"], false)?; - require_bool(&close, &["authority", "serviceLifecycleAuthorized"], false)?; - require_bool(&close, &["authority", "crossRepoMutationAuthorized"], false)?; - require_bool(&close, &["authority", "privateStateExposed"], false)?; - require_str( - &close, - &["mind", "repoMapEntry", "safeActionFamily"], - &args.expected_family, - )?; - require_str( - &close, - &["mind", "repoMapEntry", "publicationGate"], - "Bifrost", - )?; - let commit_sha = require_present_str(&close, &["handsReceipts", "commitSha"])?; - require_single_path( - &close, - &["mind", "repoMapEntry", "changedPaths"], - &args.expected_path, - )?; - require_single_path( - &close, - &["closureReview", "sourceGrounding", "actualChangedPaths"], - &args.expected_path, - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "familyAssertionsPassed"], - true, - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "mindAdoptionPassed"], - true, - )?; - - let overview_schema = require_one_of_str( - &overview, - &["schemaVersion"], - &[ - "epiphany.repo_work_overview.v0", - "epiphany.repo_work_overview_receipt.v0", - ], - )?; - if overview_schema == "epiphany.repo_work_overview.v0" { - require_str(&overview, &["status"], "overview-ready")?; - } - require_str(&overview, &["item"], &args.item)?; - require_str(&overview, &["currentGate"], &args.expected_gate)?; - require_str(&overview, &["blocker"], &args.expected_blocker)?; - require_bool(&overview, &["privateStateExposed"], false)?; - require_str(&overview, &["proofBundle", "soulVerdict"], "passed")?; - require_single_path( - &overview, - &["proofBundle", "changedPaths"], - &args.expected_path, - )?; - - let git_paths = git_changed_paths(&workspace, commit_sha)?; - if git_paths != vec![args.expected_path.clone()] { - return Err(anyhow!( - "expected git commit {commit_sha} to change only {:?}, got {:?}", - args.expected_path, - git_paths - )); - } - - Ok(json!({ - "schemaVersion": "epiphany.repo_livefire_closure_smoke.v0", - "status": "ok", - "workspace": workspace, - "item": args.item, - "branch": string_at(&close, &["mind", "repoMapEntry", "branch"]), - "commitSha": commit_sha, - "safeActionFamily": args.expected_family, - "changedPaths": git_paths, - "currentGate": args.expected_gate, - "blocker": args.expected_blocker, - "publicationGate": "Bifrost", - "soulVerdict": "passed", - "mindStateCommitReceiptId": string_at(&close, &["mind", "stateCommitReceiptId"]), - "closeReceipt": close_path, - "overviewReceipt": overview_path, - "verifiedAt": Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true), - "privateStateExposed": false, - })) -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn take_value( - args: &mut std::iter::Peekable>, - name: &str, -) -> Result { - args.next() - .ok_or_else(|| anyhow!("missing value for {name}")) -} - -fn take_path( - args: &mut std::iter::Peekable>, - name: &str, -) -> Result { - Ok(PathBuf::from(take_value(args, name)?)) -} - -fn value_at<'a>(value: &'a Value, path: &[&str]) -> Option<&'a Value> { - path.iter() - .try_fold(value, |current, key| current.get(*key)) -} - -fn string_at(value: &Value, path: &[&str]) -> String { - value_at(value, path) - .and_then(Value::as_str) - .unwrap_or("") - .to_string() -} - -fn require_present_str<'a>(value: &'a Value, path: &[&str]) -> Result<&'a str> { - value_at(value, path) - .and_then(Value::as_str) - .filter(|actual| !actual.is_empty()) - .ok_or_else(|| anyhow!("expected {} to be a non-empty string", path.join("."))) -} - -fn require_str(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = string_at(value, path); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_one_of_str<'a>(value: &Value, path: &[&str], expected: &'a [&str]) -> Result<&'a str> { - let actual = string_at(value, path); - if let Some(matched) = expected.iter().find(|candidate| **candidate == actual) { - Ok(*matched) - } else { - Err(anyhow!( - "expected {} to be one of {:?}, got {actual:?}", - path.join("."), - expected - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = value_at(value, path).and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_single_path(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let paths = value_at(value, path) - .and_then(Value::as_array) - .ok_or_else(|| anyhow!("expected {} to be an array", path.join(".")))? - .iter() - .map(|entry| entry.as_str().unwrap_or("").to_string()) - .collect::>(); - if paths == vec![expected.to_string()] { - Ok(()) - } else { - Err(anyhow!( - "expected {} to contain only {expected:?}, got {:?}", - path.join("."), - paths - )) - } -} - -fn git_changed_paths(workspace: &Path, commit_sha: &str) -> Result> { - let output = Command::new("git") - .arg("-C") - .arg(workspace) - .arg("show") - .arg("--format=") - .arg("--name-only") - .arg(commit_sha) - .output() - .with_context(|| format!("failed to run git show in {}", workspace.display()))?; - if !output.status.success() { - return Err(anyhow!( - "git show failed with exit {:?}: {}", - output.status.code(), - String::from_utf8_lossy(&output.stderr) - )); - } - Ok(String::from_utf8_lossy(&output.stdout) - .lines() - .map(str::trim) - .filter(|line| !line.is_empty()) - .map(|line| line.replace('\\', "/")) - .collect()) -} diff --git a/epiphany-core/src/bin/epiphany-repo-maintainer-review-request-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-maintainer-review-request-family-smoke.rs deleted file mode 100644 index ee9b97dd..00000000 --- a/epiphany-core/src/bin/epiphany-repo-maintainer-review-request-family-smoke.rs +++ /dev/null @@ -1,444 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-maintainer-review-request-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo Maintainer Review Request Family Smoke\n\nThis repository proves branch-local Hands maintainer-review-request requests.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - git(["add", "README.md"], &repo)?; - git( - [ - "commit", - "-m", - "Seed repo maintainer review request smoke body", - ], - &repo, - )?; - git( - [ - "switch", - "-c", - "epiphany/repo-maintainer-review-request-family", - ], - &repo, - )?; - - let item = "repo-maintainer-review-request-family"; - let target_path = ".epiphany/review-requests/repo-maintainer-review-request-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-maintainer-review-request-family-smoke", - "--topic", - "repo-maintainer-review-request-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-maintainer-review-request-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Ask a maintainer to review the redacted proof and branch diff without granting merge authority.", - "--candidate-action-ref", - "candidate-action://repo-maintainer-review-request-family/human-review", - "--candidate-action-ref", - "candidate-action://repo-maintainer-review-request-family/maintainer-verdict", - "--public-discussion-ref", - "epiphany-global/persona-collaboration/repo-maintainer-review-request-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-maintainer-review-request", - "--model-ref", - "repo-maintainer-review-request-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - let close_path = repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")); - let close = read_json(&close_path)?; - let request_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.maintainer_review_request", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - require_text( - &request_text, - "schema_version = \"epiphany.repo_maintainer_review_request.v0\"", - )?; - require_text( - &request_text, - "safe_action_family = \"repo.maintainer_review_request\"", - )?; - require_text(&request_text, "[request]")?; - require_text(&request_text, "status = \"awaiting-maintainer-review\"")?; - require_text(&request_text, "requested_owner = \"Maintainer\"")?; - require_text( - &request_text, - "requested_effect = \"review-redacted-proof-and-branch-diff\"", - )?; - require_text(&request_text, "verification_request_ref = ")?; - require_text(&request_text, "publication_request_ref = ")?; - require_text( - &request_text, - "review_scope = \"changed paths, closure proof, public proof bundle, and Bifrost credit context\"", - )?; - require_text(&request_text, "[antecedents]")?; - require_text(&request_text, "closure_review_required = true")?; - require_text(&request_text, "soul_verdict_required = true")?; - require_text(&request_text, "mind_commit_required = true")?; - require_text(&request_text, "public_proof_required = true")?; - require_text(&request_text, "bifrost_publication_request_required = true")?; - require_text(&request_text, "[required_receipts]")?; - require_text( - &request_text, - "closure_review = \"epiphany.repo_work_closure_review.v0\"", - )?; - require_text( - &request_text, - "soul_verdict = \"epiphany.soul.verification_verdict\"", - )?; - require_text( - &request_text, - "mind_commit = \"epiphany.mind.state_commit_receipt\"", - )?; - require_text( - &request_text, - "public_proof = \"epiphany.repo_work_public_proof_bundle.v0\"", - )?; - require_text( - &request_text, - "maintainer_review = \"gamecult.maintainer.review_receipt.v0\"", - )?; - require_text( - &request_text, - "bifrost_publication = \"gamecult.bifrost.public_proof_publication_receipt.v0\"", - )?; - require_text(&request_text, "[review_packet]")?; - require_text(&request_text, "requires_reviewer_identity = true")?; - require_text(&request_text, "requires_review_verdict = true")?; - require_text( - &request_text, - "allowed_verdicts = [\"approved\", \"changes-requested\", \"rejected\", \"needs-human-context\"]", - )?; - require_text(&request_text, "requires_changed_path_list = true")?; - require_text(&request_text, "requires_public_proof_ref = true")?; - require_text( - &request_text, - "requires_private_state_redaction_check = true", - )?; - require_text(&request_text, "maintainer_approval_authorized = false")?; - require_text(&request_text, "maintainer_response_required = true")?; - require_text(&request_text, "merge_authorized = false")?; - require_text(&request_text, "push_authorized = false")?; - require_text(&request_text, "publication_authorized = false")?; - require_text(&request_text, "upstream_sync_authorized = false")?; - require_text(&request_text, "hands_action_authorized = false")?; - require_text(&request_text, "cross_body_mutation_authorized = false")?; - require_text(&request_text, "private_state_exposed = false")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_maintainer_review_request_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "awaitingMaintainerReview": true, - "maintainerApprovalAuthorized": false, - "mergeAuthorized": false, - "pushAuthorized": false, - "publicationAuthorized": false, - "upstreamSyncAuthorized": false, - "handsActionAuthorized": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!( - "expected maintainer review request text to contain {needle:?}" - )) - } -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .with_context(|| format!("missing value for {flag}")) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} diff --git a/epiphany-core/src/bin/epiphany-repo-objective-draft-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-objective-draft-family-smoke.rs deleted file mode 100644 index 4a563d55..00000000 --- a/epiphany-core/src/bin/epiphany-repo-objective-draft-family-smoke.rs +++ /dev/null @@ -1,391 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-objective-draft-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo Objective Draft Family Smoke\n\nThis repository proves branch-local Objective Draft cargo.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - git(["add", "README.md"], &repo)?; - git( - ["commit", "-m", "Seed repo objective draft smoke body"], - &repo, - )?; - git( - ["switch", "-c", "epiphany/repo-objective-draft-family"], - &repo, - )?; - - let item = "repo-objective-draft-family"; - let target_path = ".epiphany/objective-drafts/repo-objective-draft-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-objective-draft-family-smoke", - "--topic", - "repo-objective-draft-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-objective-draft-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Shape public consensus into a reviewable Objective Draft before Self schedules any branch-local work.", - "--candidate-action-ref", - "candidate-action://repo-objective-draft-family/adopt-objective", - "--candidate-action-ref", - "candidate-action://repo-objective-draft-family/write-task-card", - "--public-discussion-ref", - "epiphany-global/persona-collaboration/repo-objective-draft-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-objective-draft", - "--model-ref", - "repo-objective-draft-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - let close_path = repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")); - let close = read_json(&close_path)?; - let draft_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.objective_draft", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - require_text( - &draft_text, - "schema_version = \"epiphany.repo_objective_draft.v0\"", - )?; - require_text(&draft_text, "safe_action_family = \"repo.objective_draft\"")?; - require_text(&draft_text, "[draft]")?; - require_text(&draft_text, "status = \"review-required\"")?; - require_text(&draft_text, "adoption_gate = \"Mind\"")?; - require_text(&draft_text, "scheduler_gate = \"Self\"")?; - require_text(&draft_text, "publication_gate = \"Bifrost\"")?; - require_text(&draft_text, "objective_adopted = false")?; - require_text(&draft_text, "[objective]")?; - require_text( - &draft_text, - "preferred_next_safe_family = \"repo.task_card\"", - )?; - require_text(&draft_text, "[acceptance]")?; - require_text(&draft_text, "Mind explicitly accepts or rejects")?; - require_text(&draft_text, "Self schedules only after Mind adoption")?; - require_text( - &draft_text, - "Hands acts only through a later receipt-backed plan", - )?; - require_text(&draft_text, "[inputs]")?; - require_text(&draft_text, "consensus_brief_required = true")?; - require_text(&draft_text, "objective_adoption_authorized = false")?; - require_text(&draft_text, "self_scheduling_authorized = false")?; - require_text(&draft_text, "hands_action_authorized = false")?; - require_text(&draft_text, "cross_body_mutation_authorized = false")?; - require_text(&draft_text, "mind_adoption_required = true")?; - require_text(&draft_text, "bifrost_publication_required = true")?; - require_text(&draft_text, "private_state_exposed = false")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_work_objective_draft_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "draftReviewRequired": true, - "objectiveAdopted": false, - "mindAdoptionRequired": true, - "selfSchedulingAuthorized": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!( - "expected Objective Draft text to contain {needle:?}" - )) - } -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .with_context(|| format!("missing value for {flag}")) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} diff --git a/epiphany-core/src/bin/epiphany-repo-personality-smoke.rs b/epiphany-core/src/bin/epiphany-repo-personality-smoke.rs deleted file mode 100644 index 64cebdb1..00000000 --- a/epiphany-core/src/bin/epiphany-repo-personality-smoke.rs +++ /dev/null @@ -1,788 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use epiphany_core::load_agent_memory_entry_for_role; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::{Path, PathBuf}; -use std::process::Command; - -fn main() -> Result<()> { - let result = run_smoke()?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -fn run_smoke() -> Result { - let root = env::current_dir().context("failed to resolve current dir")?; - let workspace = root - .join(".epiphany-smoke") - .join("repo-personality-workspace"); - let artifacts = root - .join(".epiphany-smoke") - .join("repo-personality-artifacts"); - reset_smoke_path(&root, &workspace)?; - reset_smoke_path(&root, &artifacts)?; - let cult_repo = create_repo( - &workspace.join("CultTiny"), - &[ - ( - "AGENTS.md", - "Prefer typed schemas and tiny public contracts.\n", - ), - ("src/lib.rs", "pub fn schema_contract() {}\n"), - ( - "docs/architecture.md", - "# Architecture\nCultTiny stores typed contract surfaces.\n", - ), - ( - "research/prior-art.md", - "# Prior Art\nPrefer known schema formats before invention.\n", - ), - ( - "tests/contract_smoke.rs", - "#[test] fn contract_smoke() {}\n", - ), - ("schemas/message.schema.json", "{\"type\":\"object\"}\n"), - ], - "Add schema contract smoke", - )?; - let lore_repo = create_repo( - &workspace.join("LoreTiny"), - &[ - ( - "AGENTS.md", - "Respect canon, source notes, and public voice.\n", - ), - ("state/map.yaml", "objective: preserve canon\n"), - ("notes/fresh-workspace-handoff.md", "Read the map first.\n"), - ("Chronicles/City.md", "# City\nCanon material.\n"), - ], - "Polish canon handoff", - )?; - - let scout = run_personality( - &root, - &[ - "scout", - "--root", - workspace.to_str().unwrap_or_default(), - "--artifact-dir", - artifacts.join("scout").to_str().unwrap_or_default(), - ], - )?; - require(scout["repoCount"] == 2, "scout should find two git repos")?; - let baseline = path_value(&scout, "store")?; - require(baseline.exists(), "scout should write typed baseline store")?; - - let projection = run_personality( - &root, - &[ - "project", - "--repo", - cult_repo.to_str().unwrap_or_default(), - "--baseline", - baseline.to_str().unwrap_or_default(), - "--artifact-dir", - artifacts.join("cult-project").to_str().unwrap_or_default(), - ], - )?; - require( - projection["profile"]["roleProjectionCount"] == 8, - "project should emit all standing role projections", - )?; - let store = path_value(&projection, "store")?; - require( - store.exists(), - "project should write typed projection store", - )?; - - let packet = run_personality( - &root, - &[ - "agent-packet", - "--store", - store.to_str().unwrap_or_default(), - "--artifact-dir", - artifacts.join("cult-packet").to_str().unwrap_or_default(), - ], - )?; - require( - packet["roleProjectionCount"] == 8, - "agent packet should carry eight role projections", - )?; - let packet_path = path_value(&packet, "packetPath")?; - let prompt_path = path_value(&packet, "promptPath")?; - let packet_summary_path = path_value(&packet, "summaryPath")?; - require( - packet_path.exists(), - "agent packet should write packet json", - )?; - require( - prompt_path.exists(), - "agent packet should write prompt markdown", - )?; - require( - packet_summary_path.exists(), - "agent packet should write summary markdown", - )?; - - let memory_packet = run_personality( - &root, - &[ - "memory-packet", - "--store", - store.to_str().unwrap_or_default(), - "--artifact-dir", - artifacts - .join("cult-memory-packet") - .to_str() - .unwrap_or_default(), - ], - )?; - require( - memory_packet["memorySourceCount"].as_u64().unwrap_or(0) > 0, - "memory packet should carry source excerpts", - )?; - require( - memory_packet["roleDistillerCount"] == 8, - "memory packet should carry all role distiller lanes", - )?; - let memory_packet_path = path_value(&memory_packet, "packetPath")?; - let memory_prompt_path = path_value(&memory_packet, "promptPath")?; - let memory_summary_path = path_value(&memory_packet, "summaryPath")?; - require( - memory_packet_path.exists(), - "memory packet should write packet json", - )?; - require( - memory_prompt_path.exists(), - "memory packet should write prompt markdown", - )?; - require( - memory_summary_path.exists(), - "memory packet should write summary markdown", - )?; - - let trajectory_packet = run_personality( - &root, - &[ - "trajectory-packet", - "--store", - store.to_str().unwrap_or_default(), - "--artifact-dir", - artifacts - .join("cult-trajectory-packet") - .to_str() - .unwrap_or_default(), - ], - )?; - require( - trajectory_packet["trajectoryThemeCount"] - .as_u64() - .unwrap_or(0) - > 0, - "trajectory packet should carry trajectory themes", - )?; - let trajectory_packet_path = path_value(&trajectory_packet, "packetPath")?; - let trajectory_prompt_path = path_value(&trajectory_packet, "promptPath")?; - let trajectory_summary_path = path_value(&trajectory_packet, "summaryPath")?; - require( - trajectory_packet_path.exists(), - "trajectory packet should write packet json", - )?; - require( - trajectory_prompt_path.exists(), - "trajectory packet should write prompt markdown", - )?; - require( - trajectory_summary_path.exists(), - "trajectory packet should write summary markdown", - )?; - - let init_store = artifacts.join("repo-initialization.msgpack"); - let startup = run_personality( - &root, - &[ - "startup", - "--repo", - cult_repo.to_str().unwrap_or_default(), - "--baseline", - baseline.to_str().unwrap_or_default(), - "--artifact-dir", - artifacts.join("startup-first").to_str().unwrap_or_default(), - "--init-store", - init_store.to_str().unwrap_or_default(), - ], - )?; - require( - startup["action"] == "reviewInitializationPackets", - "startup should request birth packet review before accepted records exist", - )?; - require( - startup["generatedPackets"].as_array().map_or(0, Vec::len) == 3, - "startup should generate trajectory, personality, and memory packets", - )?; - let startup_trajectory_packet = packet_for_kind(&startup, "repo-trajectory")?; - let startup_personality_packet = packet_for_kind(&startup, "repo-personality")?; - let startup_memory_packet = packet_for_kind(&startup, "repo-memory")?; - require( - startup_trajectory_packet.exists(), - "startup should write trajectory packet", - )?; - require( - startup_personality_packet.exists(), - "startup should write personality packet", - )?; - require( - startup_memory_packet.exists(), - "startup should write memory packet", - )?; - let birth_runner = run_birth_runner( - &root, - &[ - "--repo", - cult_repo.to_str().unwrap_or_default(), - "--baseline", - baseline.to_str().unwrap_or_default(), - "--artifact-dir", - artifacts - .join("birth-runner-plan") - .to_str() - .unwrap_or_default(), - "--init-store", - artifacts - .join("birth-runner-plan") - .join("repo-initialization.msgpack") - .to_str() - .unwrap_or_default(), - "--agent-store", - root.join("state") - .join("agents.msgpack") - .to_str() - .unwrap_or_default(), - "--heartbeat-store", - root.join("state") - .join("agent-heartbeats.msgpack") - .to_str() - .unwrap_or_default(), - "--mode", - "plan", - ], - )?; - require( - birth_runner["schemaVersion"] == "epiphany.repo_birth_runner.v0", - "birth runner should return its schema", - )?; - require( - birth_runner["executions"].as_array().map_or(0, Vec::len) == 3, - "birth runner plan should expose all startup-only specialist executions", - )?; - require( - birth_runner["executions"] - .as_array() - .into_iter() - .flatten() - .all(|execution| execution["heartbeatParticipant"].is_null()), - "birth runner executions should not be heartbeat participants", - )?; - let heartbeat_store = artifacts.join("startup-heartbeats.msgpack"); - run_heartbeat( - &root, - &[ - "init", - "--store", - heartbeat_store.to_str().unwrap_or_default(), - ], - )?; - let trajectory_result_path = artifacts.join("startup-trajectory-result.json"); - fs::write( - &trajectory_result_path, - serde_json::to_vec_pretty(&json!({ - "verdict": "ready-for-review", - "summary": "Smoke trajectory birth distillation.", - "confidence": 0.88, - "selfImage": "CultTiny is becoming a typed-contract workspace that prefers receipts to swagger.", - "trajectoryNarrative": "The tiny history and docs both lean toward explicit schema boundaries, known formats, and reviewable proof over improvising private protocol sludge.", - "implicitGoals": ["Keep future expansion grounded in explicit schemas and receipts."], - "antiGoals": ["Do not drift into ad hoc protocol glue just because the fixture is small."], - "roleBiases": [{ - "roleId": "coordinator", - "bias": "Self should treat typed-schema growth as part of the repo's grain.", - "trajectorySignals": ["systems_formalization", "protocol_intolerance"], - "behavioralEffect": "Challenge loose glue early.", - "risk": "Could become stiff if later work needs play.", - "evidenceRefs": ["AGENTS.md", "docs/architecture.md"] - }], - "selfPatchCandidates": [{ - "roleId": "coordinator", - "selfPatch": { - "agentId": "epiphany.self", - "reason": "Trajectory birth should teach Self that CultTiny has been moving toward typed receipts and explicit contracts.", - "semanticMemories": [{ - "memoryId": "mem-self-startup-trajectory-smoke", - "summary": "CultTiny's trajectory favors typed contract growth and evidence-backed explicitness over casual glue.", - "salience": 0.74, - "confidence": 0.87 - }] - } - }], - "initializationRecord": { - "repoId": "culttiny", - "distillerKind": "repo-trajectory", - "acceptedOnce": true - }, - "doNotMutate": [], - "nextSafeMove": "Self reviews the trajectory petition before it colors later memory drift." - }))?, - )?; - let accepted_trajectory = run_personality( - &root, - &[ - "accept-init", - "--init-store", - init_store.to_str().unwrap_or_default(), - "--packet", - startup_trajectory_packet.to_str().unwrap_or_default(), - "--kind", - "repo-trajectory", - "--accepted-by", - "smoke-self", - "--summary", - "Smoke accepted repo trajectory birth packet after review.", - "--result", - trajectory_result_path.to_str().unwrap_or_default(), - "--agent-store", - root.join("state") - .join("agents.msgpack") - .to_str() - .unwrap_or_default(), - "--apply-self-patches", - "false", - ], - )?; - require( - accepted_trajectory["record"]["kind"] == "repo-trajectory", - "accept-init should record trajectory birth", - )?; - let agent_store = artifacts.join("startup-agents.msgpack"); - fs::copy(root.join("state").join("agents.msgpack"), &agent_store) - .context("failed to copy role memory store for startup smoke")?; - let accepted_personality = run_personality( - &root, - &[ - "accept-init", - "--init-store", - init_store.to_str().unwrap_or_default(), - "--packet", - startup_personality_packet.to_str().unwrap_or_default(), - "--kind", - "repo-personality", - "--accepted-by", - "smoke-self", - "--summary", - "Smoke accepted repo personality birth packet after review.", - "--agent-store", - agent_store.to_str().unwrap_or_default(), - "--apply-trait-seeds", - "true", - "--heartbeat-store", - heartbeat_store.to_str().unwrap_or_default(), - "--apply-heartbeat-seeds", - "true", - ], - )?; - require( - accepted_personality["record"]["kind"] == "repo-personality", - "accept-init should record personality birth", - )?; - require( - accepted_personality["heartbeatSeeds"]["applied"] - .as_u64() - .unwrap_or_default() - > 0, - "accept-init should apply heartbeat seed mutations", - )?; - require( - accepted_personality["traitLatticePersistence"]["applied"] - .as_u64() - .unwrap_or_default() - > 0, - "accept-init should stamp newborn canonical trait lattice seeds", - )?; - let coordinator = load_agent_memory_entry_for_role(&agent_store, "coordinator")? - .ok_or_else(|| anyhow!("startup smoke lost coordinator role memory entry"))?; - require( - !coordinator - .agent - .canonical_state - .underlying_organization - .contains_key("baseline"), - "personality accept-init should replace baseline canonical traits for coordinator", - )?; - require( - coordinator - .agent - .canonical_state - .underlying_organization - .contains_key("routing_discipline"), - "personality accept-init should seed coordinator routing_discipline trait", - )?; - let memory_result_path = artifacts.join("startup-memory-result.json"); - fs::write( - &memory_result_path, - serde_json::to_vec_pretty(&json!({ - "verdict": "ready-for-review", - "summary": "Smoke newborn memory distillation.", - "confidence": 0.9, - "roleMemoryPatches": [{ - "roleId": "modeling", - "roleName": "Modeling", - "verdict": "ready-for-review", - "selfPatch": { - "agentId": "epiphany.modeling", - "reason": "Birth memory should teach Modeling that repo initialization memory is reviewed and typed.", - "semanticMemories": [{ - "memoryId": "mem-body-startup-birth-memory-smoke", - "summary": "Repo memory birth packets can produce role-specific selfPatch candidates that are applied only after Self review.", - "salience": 0.72, - "confidence": 0.88 - }] - }, - "sourceRefs": ["AGENTS.md"], - "whyThisBelongsInMemory": "Modeling needs the startup memory route to stay source-grounded.", - "stalenessRisk": "smoke fixture", - "doNotStore": [] - }] - }))?, - )?; - let accepted_memory = run_personality( - &root, - &[ - "accept-init", - "--init-store", - init_store.to_str().unwrap_or_default(), - "--packet", - startup_memory_packet.to_str().unwrap_or_default(), - "--kind", - "repo-memory", - "--accepted-by", - "smoke-self", - "--summary", - "Smoke accepted repo memory birth packet after review.", - "--result", - memory_result_path.to_str().unwrap_or_default(), - "--agent-store", - agent_store.to_str().unwrap_or_default(), - "--apply-self-patches", - "true", - ], - )?; - require( - accepted_memory["record"]["kind"] == "repo-memory", - "accept-init should record memory birth", - )?; - require( - accepted_memory["selfPersistence"]["applied"] == 1, - "accept-init should apply reviewed memory selfPatch candidates", - )?; - let startup_after_accept = run_personality( - &root, - &[ - "startup", - "--repo", - cult_repo.to_str().unwrap_or_default(), - "--baseline", - baseline.to_str().unwrap_or_default(), - "--artifact-dir", - artifacts - .join("startup-after-accept") - .to_str() - .unwrap_or_default(), - "--init-store", - init_store.to_str().unwrap_or_default(), - ], - )?; - require( - startup_after_accept["action"] == "continueStartup", - "startup should not rerun birth packets after accepted records exist", - )?; - require( - startup_after_accept["generatedPackets"] - .as_array() - .is_some_and(Vec::is_empty), - "startup should generate no packets after accepted records exist", - )?; - - let status = run_personality( - &root, - &["status", "--store", store.to_str().unwrap_or_default()], - )?; - require( - status["reports"] == 1, - "projection store should have one report", - )?; - require( - status["profiles"] == 1, - "projection store should have one profile", - )?; - require( - status["trajectoryReports"] == 1, - "projection store should have one trajectory report", - )?; - require( - status["roleProjections"] == 8, - "projection store should have eight role projections", - )?; - - Ok(json!({ - "workspace": workspace, - "artifactRoot": artifacts, - "cultRepo": cult_repo, - "loreRepo": lore_repo, - "baseline": baseline, - "projectionStore": store, - "packetPath": packet_path, - "promptPath": prompt_path, - "packetSummaryPath": packet_summary_path, - "trajectoryPacketPath": trajectory_packet_path, - "trajectoryPromptPath": trajectory_prompt_path, - "trajectorySummaryPath": trajectory_summary_path, - "memoryPacketPath": memory_packet_path, - "memoryPromptPath": memory_prompt_path, - "memorySummaryPath": memory_summary_path, - "initStore": init_store, - "agentStore": agent_store, - "heartbeatStore": heartbeat_store, - "birthRunnerSummary": artifacts.join("birth-runner-plan").join("birth-runner-summary.json"), - "startupTrajectoryPacket": startup_trajectory_packet, - "startupPersonalityPacket": startup_personality_packet, - "startupMemoryPacket": startup_memory_packet, - "startupTrajectoryResult": trajectory_result_path, - "startupMemoryResult": memory_result_path, - "startupAfterAcceptAction": startup_after_accept["action"], - "repoCount": scout["repoCount"], - "trajectoryReports": status["trajectoryReports"], - "roleProjections": status["roleProjections"], - })) -} - -fn create_repo(root: &Path, files: &[(&str, &str)], commit_message: &str) -> Result { - fs::create_dir_all(root)?; - run_git(root, &["init"])?; - run_git( - root, - &["config", "user.email", "epiphany-smoke@example.invalid"], - )?; - run_git(root, &["config", "user.name", "Epiphany Smoke"])?; - for (path, content) in files { - let path = root.join(path); - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - fs::write(path, content)?; - } - run_git(root, &["add", "."])?; - run_git(root, &["commit", "-m", commit_message])?; - Ok(root.to_path_buf()) -} - -fn run_personality(root: &Path, args: &[&str]) -> Result { - let exe = native_personality_exe()?; - ensure_built(root, "epiphany-repo-personality", &exe)?; - let output = Command::new(exe) - .current_dir(root) - .args(args) - .output() - .context("failed to run epiphany-repo-personality")?; - require( - output.status.success(), - &format!( - "epiphany-repo-personality failed: {}", - String::from_utf8_lossy(&output.stderr) - ), - )?; - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "epiphany-repo-personality did not return JSON: {}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn run_birth_runner(root: &Path, args: &[&str]) -> Result { - let exe = native_birth_runner_exe()?; - ensure_built(root, "epiphany-repo-birth-runner", &exe)?; - let output = Command::new(exe) - .current_dir(root) - .args(args) - .output() - .context("failed to run epiphany-repo-birth-runner")?; - require( - output.status.success(), - &format!( - "epiphany-repo-birth-runner failed: {}", - String::from_utf8_lossy(&output.stderr) - ), - )?; - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "epiphany-repo-birth-runner did not return JSON: {}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn run_heartbeat(root: &Path, args: &[&str]) -> Result { - let exe = native_heartbeat_exe()?; - ensure_built(root, "epiphany-heartbeat-store", &exe)?; - let output = Command::new(exe) - .current_dir(root) - .args(args) - .output() - .context("failed to run epiphany-heartbeat-store")?; - require( - output.status.success(), - &format!( - "epiphany-heartbeat-store failed: {}", - String::from_utf8_lossy(&output.stderr) - ), - )?; - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "epiphany-heartbeat-store did not return JSON: {}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn native_personality_exe() -> Result { - let target_dir = env::var_os("CARGO_TARGET_DIR") - .unwrap_or_else(|| r"C:\Users\Meta\.cargo-target-codex".into()); - Ok(PathBuf::from(target_dir).join("debug").join(format!( - "epiphany-repo-personality{}", - env::consts::EXE_SUFFIX - ))) -} - -fn native_birth_runner_exe() -> Result { - let target_dir = env::var_os("CARGO_TARGET_DIR") - .unwrap_or_else(|| r"C:\Users\Meta\.cargo-target-codex".into()); - Ok(PathBuf::from(target_dir).join("debug").join(format!( - "epiphany-repo-birth-runner{}", - env::consts::EXE_SUFFIX - ))) -} - -fn native_heartbeat_exe() -> Result { - let target_dir = env::var_os("CARGO_TARGET_DIR") - .unwrap_or_else(|| r"C:\Users\Meta\.cargo-target-codex".into()); - Ok(PathBuf::from(target_dir).join("debug").join(format!( - "epiphany-heartbeat-store{}", - env::consts::EXE_SUFFIX - ))) -} - -fn ensure_built(root: &Path, bin: &str, exe: &Path) -> Result<()> { - let status = Command::new("cargo") - .current_dir(root) - .arg("build") - .arg("--manifest-path") - .arg(root.join("epiphany-core").join("Cargo.toml")) - .arg("--bin") - .arg(bin) - .status() - .with_context(|| format!("failed to build {bin}"))?; - require( - status.success() && exe.exists(), - &format!("{bin} executable was not built at {}", exe.display()), - ) -} - -fn run_git(repo: &Path, args: &[&str]) -> Result<()> { - let output = Command::new("git") - .current_dir(repo) - .args(args) - .output() - .with_context(|| format!("failed to run git {}", args.join(" ")))?; - require( - output.status.success(), - &format!( - "git {} failed: {}", - args.join(" "), - String::from_utf8_lossy(&output.stderr) - ), - ) -} - -fn reset_smoke_path(root: &Path, path: &Path) -> Result<()> { - let quarantine = root.join(".epiphany-smoke"); - fs::create_dir_all(&quarantine)?; - let quarantine = quarantine.canonicalize()?; - let parent = path.parent().unwrap_or_else(|| Path::new(".")); - fs::create_dir_all(parent)?; - let resolved = if path.exists() { - path.canonicalize()? - } else { - parent.canonicalize()?.join( - path.file_name() - .ok_or_else(|| anyhow!("smoke path has no leaf"))?, - ) - }; - if resolved == quarantine || !resolved.starts_with(&quarantine) { - return Err(anyhow!( - "refusing to reset path outside smoke quarantine: {}", - path.display() - )); - } - if path.exists() { - fs::remove_dir_all(path).with_context(|| format!("failed to reset {}", path.display()))?; - } - fs::create_dir_all(path).with_context(|| format!("failed to create {}", path.display())) -} - -#[cfg(test)] -mod reset_tests { - use super::*; - - #[test] - fn reset_refuses_traversal_outside_smoke_quarantine() -> Result<()> { - let temp = tempfile::tempdir()?; - let escaped = temp.path().join(".epiphany-smoke").join(".."); - let error = reset_smoke_path(temp.path(), &escaped).expect_err("escape must fail"); - assert!(error.to_string().contains("outside smoke quarantine")); - assert!(temp.path().exists()); - Ok(()) - } -} - -fn path_value(value: &Value, key: &str) -> Result { - value - .get(key) - .and_then(Value::as_str) - .map(PathBuf::from) - .ok_or_else(|| anyhow!("missing path value {key}")) -} - -fn packet_for_kind(value: &Value, kind: &str) -> Result { - value["generatedPackets"] - .as_array() - .and_then(|packets| { - packets - .iter() - .find(|packet| packet["kind"] == kind) - .and_then(|packet| packet["packetPath"].as_str()) - }) - .map(PathBuf::from) - .ok_or_else(|| anyhow!("missing generated packet for {kind}")) -} - -fn require(condition: bool, message: &str) -> Result<()> { - if condition { - Ok(()) - } else { - Err(anyhow!(message.to_string())) - } -} diff --git a/epiphany-core/src/bin/epiphany-repo-personality.rs b/epiphany-core/src/bin/epiphany-repo-personality.rs deleted file mode 100644 index 58b92d18..00000000 --- a/epiphany-core/src/bin/epiphany-repo-personality.rs +++ /dev/null @@ -1,4514 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use cultcache_rs::CultCache; -use cultcache_rs::DatabaseEntry; -use cultcache_rs::SingleFileMessagePackBackingStore; -use epiphany_core::AgentCanonicalTraitSeed; -use epiphany_core::AgentSelfPatch; -use epiphany_core::HeartbeatBirthPersonalitySeed; -use epiphany_core::apply_agent_canonical_trait_seeds; -use epiphany_core::apply_agent_self_patch_document; -use epiphany_core::default_heartbeat_state; -use epiphany_core::load_heartbeat_state_entry; -use epiphany_core::review_agent_self_patch_document; -use epiphany_core::write_heartbeat_state_entry; -use ignore::WalkBuilder; -use serde::Deserialize; -use serde::Serialize; -use serde_json::Value; -use serde_json::json; -use std::collections::BTreeMap; -use std::collections::BTreeSet; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -const TERRAIN_SCHEMA_VERSION: &str = "epiphany.repo_terrain_report.v0"; -const PROFILE_SCHEMA_VERSION: &str = "epiphany.repo_personality_profile.v0"; -const ROLE_PROJECTION_SCHEMA_VERSION: &str = "epiphany.role_personality_projection.v0"; -const TRAJECTORY_SCHEMA_VERSION: &str = "epiphany.repo_trajectory_report.v0"; -const ARTIFACT_SCHEMA_VERSION: &str = "epiphany.repo_personality_artifacts.v0"; -const DISTILLER_PACKET_SCHEMA_VERSION: &str = "epiphany.repo_personality_distiller_packet.v0"; -const MEMORY_DISTILLER_PACKET_SCHEMA_VERSION: &str = "epiphany.repo_memory_distiller_packet.v0"; -const TRAJECTORY_DISTILLER_PACKET_SCHEMA_VERSION: &str = - "epiphany.repo_trajectory_distiller_packet.v0"; -const INITIALIZATION_RECORD_SCHEMA_VERSION: &str = "epiphany.repo_initialization_record.v0"; -const REPO_PERSONALITY_DISTILLER_PROMPT: &str = - include_str!("../prompts/repo_personality_distiller.md"); -const REPO_MEMORY_DISTILLER_PROMPT: &str = include_str!("../prompts/repo_memory_distiller.md"); -const REPO_TRAJECTORY_DISTILLER_PROMPT: &str = - include_str!("../prompts/repo_trajectory_distiller.md"); -const MEMORY_SOURCE_MAX_FILES: usize = 48; -const MEMORY_SOURCE_MAX_BYTES_PER_FILE: usize = 12_000; -const MEMORY_SOURCE_MAX_TOTAL_BYTES: usize = 120_000; -const TRAJECTORY_SOURCE_MAX_FILES: usize = 18; -const TRAJECTORY_SOURCE_MAX_BYTES_PER_FILE: usize = 9_000; -const TRAJECTORY_SOURCE_MAX_TOTAL_BYTES: usize = 72_000; - -const ROLES: &[&str] = &[ - "coordinator", - "Persona", - "imagination", - "research", - "modeling", - "implementation", - "verification", -]; - -const AXES: &[&str] = &[ - "contract_strictness", - "protocol_intolerance", - "boundary_severity", - "actuation_risk", - "runtime_proximity", - "state_hygiene", - "evidence_appetite", - "source_fidelity", - "content_canon_bias", - "verification_environment_need", - "burstiness", - "consolidation_drive", - "production_pressure", - "temporal_pressure", - "experimental_heat", - "churn_spiral_risk", - "interface_orientation", - "aesthetic_appetite", - "social_surface", - "sensory_salience", - "editorial_restraint", - "speech_pressure", - "novelty_hunger", - "guardedness", - "rumination_bias", - "initiative_drive", - "mood_lability", -]; - -#[derive(Clone, Debug, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.repo_terrain_report", - schema = "EpiphanyRepoTerrainReport" -)] -pub struct RepoTerrainReport { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub repo_id: String, - #[cultcache(key = 2)] - pub name: String, - #[cultcache(key = 3)] - pub path: String, - #[cultcache(key = 4)] - pub remote_urls: Vec, - #[cultcache(key = 5)] - pub source_families: Vec, - #[cultcache(key = 6)] - pub languages: Vec, - #[cultcache(key = 7)] - pub state_surfaces: Vec, - #[cultcache(key = 8)] - pub instruction_surfaces: Vec, - #[cultcache(key = 9)] - pub test_surfaces: Vec, - #[cultcache(key = 10)] - pub runtime_surfaces: Vec, - #[cultcache(key = 11)] - pub history_metrics: RepoHistoryMetrics, - #[cultcache(key = 12)] - pub axis_scores: BTreeMap, - #[cultcache(key = 13)] - pub axis_evidence: BTreeMap>, - #[cultcache(key = 14)] - pub confidence: f64, - #[cultcache(key = 15)] - pub warnings: Vec, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RepoSignalCount { - pub label: String, - pub count: usize, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RepoHistoryMetrics { - pub commit_count: usize, - pub sampled_commits: usize, - pub active_days: usize, - pub insertions: usize, - pub deletions: usize, - pub changed_files: usize, - pub state_doc_touches: usize, - pub test_receipt_touches: usize, - pub runtime_touches: usize, - pub protocol_touches: usize, - pub ui_touches: usize, - pub keyword_hits: BTreeMap, - pub recent_messages: Vec, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TrajectorySourceExcerpt { - pub path: String, - pub kind: String, - pub bytes: usize, - pub truncated: bool, - pub text: String, -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RepoTrajectoryThemeScore { - pub theme: String, - pub early_history: f64, - pub recent_history: f64, - pub current_sources: f64, - pub delta: f64, - pub evidence: Vec, -} - -#[derive(Clone, Debug, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.repo_trajectory_report", - schema = "EpiphanyRepoTrajectoryReport" -)] -pub struct RepoTrajectoryReport { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub repo_id: String, - #[cultcache(key = 2)] - pub trajectory_summary: String, - #[cultcache(key = 3)] - pub self_image: String, - #[cultcache(key = 4)] - pub early_commit_messages: Vec, - #[cultcache(key = 5)] - pub recent_commit_messages: Vec, - #[cultcache(key = 6)] - pub trajectory_sources: Vec, - #[cultcache(key = 7)] - pub theme_scores: Vec, - #[cultcache(key = 8)] - pub directional_pressures: Vec, - #[cultcache(key = 9)] - pub implicit_goal_candidates: Vec, - #[cultcache(key = 10)] - pub anti_goal_candidates: Vec, - #[cultcache(key = 11)] - pub tensions: Vec, - #[cultcache(key = 12)] - pub confidence: f64, - #[cultcache(key = 13)] - pub warnings: Vec, -} - -#[derive(Clone, Debug, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.repo_personality_profile", - schema = "EpiphanyRepoPersonalityProfile" -)] -pub struct RepoPersonalityProfile { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub repo_id: String, - #[cultcache(key = 2)] - pub summary: String, - #[cultcache(key = 3)] - pub source_family_weights: BTreeMap, - #[cultcache(key = 4)] - pub axis_scores: BTreeMap, - #[cultcache(key = 5)] - pub axis_confidence: BTreeMap, - #[cultcache(key = 6)] - pub dominant_pressures: Vec, - #[cultcache(key = 7)] - pub risk_pressures: Vec, - #[cultcache(key = 8)] - pub role_modulations: Vec, -} - -#[derive(Clone, Debug, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.role_personality_projection", - schema = "EpiphanyRolePersonalityProjection" -)] -pub struct RolePersonalityProjection { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub projection_id: String, - #[cultcache(key = 2)] - pub repo_id: String, - #[cultcache(key = 3)] - pub role_id: String, - #[cultcache(key = 4)] - pub trait_deltas: BTreeMap, - #[cultcache(key = 5)] - pub heartbeat_deltas: BTreeMap, - #[cultcache(key = 6)] - pub default_mood_pressure: BTreeMap, - #[cultcache(key = 7)] - pub semantic_memory_candidates: Vec, - #[cultcache(key = 8)] - pub goal_candidates: Vec, - #[cultcache(key = 9)] - pub value_candidates: Vec, - #[cultcache(key = 10)] - pub private_note_candidates: Vec, - #[cultcache(key = 11)] - pub reason: String, - #[cultcache(key = 12)] - pub evidence_refs: Vec, -} - -#[derive(Clone, Debug, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.repo_initialization_record", - schema = "EpiphanyRepoInitializationRecord" -)] -pub struct RepoInitializationRecord { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub record_id: String, - #[cultcache(key = 2)] - pub repo_id: String, - #[cultcache(key = 3)] - pub repo_path: String, - #[cultcache(key = 4)] - pub kind: String, - #[cultcache(key = 5)] - pub source_packet_schema_version: String, - #[cultcache(key = 6)] - pub source_packet_path: String, - #[cultcache(key = 7)] - pub accepted_at: String, - #[cultcache(key = 8)] - pub accepted_by: String, - #[cultcache(key = 9)] - pub summary: String, -} - -fn main() -> Result<()> { - let mut args = env::args().skip(1); - let Some(command) = args.next() else { - print_usage(); - std::process::exit(2); - }; - let result = match command.as_str() { - "scout" => run_scout(parse_scout_args(args)?), - "project" => run_project(parse_project_args(args)?), - "agent-packet" => run_agent_packet(parse_packet_args(args, "agent-packet")?), - "memory-packet" => run_memory_packet(parse_packet_args(args, "memory-packet")?), - "trajectory-packet" => run_trajectory_packet(parse_packet_args(args, "trajectory-packet")?), - "startup" => run_startup(parse_startup_args(args)?), - "accept-init" => run_accept_init(parse_accept_init_args(args)?), - "status" => run_status(parse_status_args(args)?), - other => Err(anyhow!("unknown command {other:?}")), - }?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct ScoutArgs { - root: PathBuf, - artifact_dir: PathBuf, - max_repos: Option, -} - -#[derive(Clone, Debug)] -struct ProjectArgs { - repo: PathBuf, - baseline: PathBuf, - artifact_dir: PathBuf, -} - -#[derive(Clone, Debug)] -struct StartupArgs { - repo: PathBuf, - baseline: PathBuf, - artifact_dir: PathBuf, - init_store: PathBuf, -} - -#[derive(Clone, Debug)] -struct AcceptInitArgs { - init_store: PathBuf, - packet: PathBuf, - kind: String, - accepted_by: String, - summary: Option, - result: Option, - agent_store: Option, - apply_self_patches: bool, - apply_trait_seeds: bool, - heartbeat_store: Option, - apply_heartbeat_seeds: bool, -} - -#[derive(Clone, Debug)] -struct StatusArgs { - store: PathBuf, -} - -#[derive(Clone, Debug)] -struct AgentPacketArgs { - store: PathBuf, - artifact_dir: PathBuf, - repo_id: Option, -} - -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -struct MemorySourceExcerpt { - path: String, - kind: String, - bytes: usize, - truncated: bool, - text: String, -} - -fn parse_scout_args(args: impl Iterator) -> Result { - let mut root = None; - let mut artifact_dir = None; - let mut max_repos = None; - parse_named_args(args, |name, value| match name { - "--root" => { - root = Some(PathBuf::from(value)); - Ok(()) - } - "--artifact-dir" => { - artifact_dir = Some(PathBuf::from(value)); - Ok(()) - } - "--max-repos" => { - max_repos = Some(value.parse().context("--max-repos must be a number")?); - Ok(()) - } - other => Err(anyhow!("unexpected scout argument {other}")), - })?; - Ok(ScoutArgs { - root: root.context("missing --root")?, - artifact_dir: artifact_dir.context("missing --artifact-dir")?, - max_repos, - }) -} - -fn parse_project_args(args: impl Iterator) -> Result { - let mut repo = None; - let mut baseline = None; - let mut artifact_dir = None; - parse_named_args(args, |name, value| match name { - "--repo" => { - repo = Some(PathBuf::from(value)); - Ok(()) - } - "--baseline" => { - baseline = Some(PathBuf::from(value)); - Ok(()) - } - "--artifact-dir" => { - artifact_dir = Some(PathBuf::from(value)); - Ok(()) - } - other => Err(anyhow!("unexpected project argument {other}")), - })?; - Ok(ProjectArgs { - repo: repo.context("missing --repo")?, - baseline: baseline.context("missing --baseline")?, - artifact_dir: artifact_dir.context("missing --artifact-dir")?, - }) -} - -fn parse_startup_args(args: impl Iterator) -> Result { - let mut repo = None; - let mut baseline = None; - let mut artifact_dir = None; - let mut init_store = None; - parse_named_args(args, |name, value| match name { - "--repo" => { - repo = Some(PathBuf::from(value)); - Ok(()) - } - "--baseline" => { - baseline = Some(PathBuf::from(value)); - Ok(()) - } - "--artifact-dir" => { - artifact_dir = Some(PathBuf::from(value)); - Ok(()) - } - "--init-store" => { - init_store = Some(PathBuf::from(value)); - Ok(()) - } - other => Err(anyhow!("unexpected startup argument {other}")), - })?; - Ok(StartupArgs { - repo: repo.context("missing --repo")?, - baseline: baseline.context("missing --baseline")?, - artifact_dir: artifact_dir.context("missing --artifact-dir")?, - init_store: init_store.context("missing --init-store")?, - }) -} - -fn parse_accept_init_args(args: impl Iterator) -> Result { - let mut init_store = None; - let mut packet = None; - let mut kind = None; - let mut accepted_by = Some("Self".to_string()); - let mut summary = None; - let mut result = None; - let mut agent_store = None; - let mut apply_self_patches = false; - let mut apply_trait_seeds = false; - let mut heartbeat_store = None; - let mut apply_heartbeat_seeds = false; - parse_named_args(args, |name, value| match name { - "--init-store" => { - init_store = Some(PathBuf::from(value)); - Ok(()) - } - "--packet" => { - packet = Some(PathBuf::from(value)); - Ok(()) - } - "--kind" => { - kind = Some(value); - Ok(()) - } - "--accepted-by" => { - accepted_by = Some(value); - Ok(()) - } - "--summary" => { - summary = Some(value); - Ok(()) - } - "--result" => { - result = Some(PathBuf::from(value)); - Ok(()) - } - "--agent-store" => { - agent_store = Some(PathBuf::from(value)); - Ok(()) - } - "--apply-self-patches" => { - apply_self_patches = parse_bool_arg("--apply-self-patches", &value)?; - Ok(()) - } - "--apply-trait-seeds" => { - apply_trait_seeds = parse_bool_arg("--apply-trait-seeds", &value)?; - Ok(()) - } - "--heartbeat-store" => { - heartbeat_store = Some(PathBuf::from(value)); - Ok(()) - } - "--apply-heartbeat-seeds" => { - apply_heartbeat_seeds = parse_bool_arg("--apply-heartbeat-seeds", &value)?; - Ok(()) - } - other => Err(anyhow!("unexpected accept-init argument {other}")), - })?; - Ok(AcceptInitArgs { - init_store: init_store.context("missing --init-store")?, - packet: packet.context("missing --packet")?, - kind: kind.context("missing --kind")?, - accepted_by: accepted_by.unwrap_or_else(|| "Self".to_string()), - summary, - result, - agent_store, - apply_self_patches, - apply_trait_seeds, - heartbeat_store, - apply_heartbeat_seeds, - }) -} - -fn parse_status_args(args: impl Iterator) -> Result { - let mut store = None; - parse_named_args(args, |name, value| match name { - "--store" => { - store = Some(PathBuf::from(value)); - Ok(()) - } - other => Err(anyhow!("unexpected status argument {other}")), - })?; - Ok(StatusArgs { - store: store.context("missing --store")?, - }) -} - -fn parse_packet_args( - args: impl Iterator, - command_name: &'static str, -) -> Result { - let mut store = None; - let mut artifact_dir = None; - let mut repo_id = None; - parse_named_args(args, |name, value| match name { - "--store" => { - store = Some(PathBuf::from(value)); - Ok(()) - } - "--artifact-dir" => { - artifact_dir = Some(PathBuf::from(value)); - Ok(()) - } - "--repo-id" => { - repo_id = Some(value); - Ok(()) - } - other => Err(anyhow!("unexpected {command_name} argument {other}")), - })?; - Ok(AgentPacketArgs { - store: store.context("missing --store")?, - artifact_dir: artifact_dir.context("missing --artifact-dir")?, - repo_id, - }) -} - -fn parse_named_args( - args: impl Iterator, - mut handle: impl FnMut(&str, String) -> Result<()>, -) -> Result<()> { - let mut args = args.peekable(); - while let Some(name) = args.next() { - if !name.starts_with("--") { - return Err(anyhow!("expected named argument, got {name:?}")); - } - let value = args - .next() - .ok_or_else(|| anyhow!("missing value for {name}"))?; - handle(&name, value)?; - } - Ok(()) -} - -fn parse_bool_arg(name: &str, value: &str) -> Result { - match value { - "true" | "yes" | "1" => Ok(true), - "false" | "no" | "0" => Ok(false), - other => Err(anyhow!("{name} must be true or false, got {other:?}")), - } -} - -fn run_scout(args: ScoutArgs) -> Result { - fs::create_dir_all(&args.artifact_dir) - .with_context(|| format!("failed to create {}", args.artifact_dir.display()))?; - let store_path = args.artifact_dir.join("baseline.msgpack"); - let mut cache = repo_personality_cache(&store_path)?; - cache.pull_all_backing_stores()?; - - let mut repos = discover_git_repos(&args.root)?; - repos.sort(); - if let Some(limit) = args.max_repos { - repos.truncate(limit); - } - - let mut reports = Vec::new(); - for repo in repos { - let report = scout_repo(&repo)?; - cache.put::(report.repo_id.clone(), &report)?; - reports.push(report); - } - let profiles = reduce_reports(&reports); - let mut trajectories = Vec::new(); - for profile in &profiles { - let report = reports - .iter() - .find(|report| report.repo_id == profile.repo_id) - .expect("profile should map to report"); - let trajectory = derive_trajectory_report(Path::new(&report.path), report, profile)?; - cache.put::(trajectory.repo_id.clone(), &trajectory)?; - trajectories.push(trajectory); - cache.put::(profile.repo_id.clone(), profile)?; - for projection in &profile.role_modulations { - cache.put::(projection.projection_id.clone(), projection)?; - } - } - - let summary = json!({ - "schemaVersion": ARTIFACT_SCHEMA_VERSION, - "mode": "scout", - "root": args.root, - "artifactDir": args.artifact_dir, - "store": store_path, - "repoCount": reports.len(), - "profileCount": profiles.len(), - "trajectoryCount": trajectories.len(), - "repos": reports.iter().map(report_summary).collect::>(), - }); - write_json( - &args - .artifact_dir - .join("repo-personality-scout-summary.json"), - &summary, - )?; - write_text( - &args.artifact_dir.join("repo-personality-scout-summary.md"), - &render_scout_markdown(&reports, &profiles, &trajectories), - )?; - Ok(summary) -} - -fn run_project(args: ProjectArgs) -> Result { - fs::create_dir_all(&args.artifact_dir) - .with_context(|| format!("failed to create {}", args.artifact_dir.display()))?; - let ProjectedRepo { - store_path, - report, - profile, - trajectory, - } = project_repo_to_store(&args.repo, &args.baseline, &args.artifact_dir)?; - let summary = json!({ - "schemaVersion": ARTIFACT_SCHEMA_VERSION, - "mode": "project", - "repo": args.repo, - "baseline": args.baseline, - "artifactDir": args.artifact_dir, - "store": store_path, - "profile": profile_summary(&profile), - "terrain": report_summary(&report), - "trajectory": trajectory_summary(&trajectory), - }); - write_json( - &args - .artifact_dir - .join("repo-personality-project-summary.json"), - &summary, - )?; - write_text( - &args - .artifact_dir - .join("repo-personality-project-summary.md"), - &render_project_markdown(&report, &profile, &trajectory), - )?; - Ok(summary) -} - -struct ProjectedRepo { - store_path: PathBuf, - report: RepoTerrainReport, - profile: RepoPersonalityProfile, - trajectory: RepoTrajectoryReport, -} - -fn project_repo_to_store( - repo: &Path, - baseline_path: &Path, - artifact_dir: &Path, -) -> Result { - fs::create_dir_all(artifact_dir) - .with_context(|| format!("failed to create {}", artifact_dir.display()))?; - let mut baseline = repo_personality_cache(baseline_path)?; - baseline.pull_all_backing_stores()?; - let reports = baseline.get_all::()?; - let target_id = repo_id(repo); - let report = reports - .iter() - .find(|report| report.repo_id == target_id || same_path(&report.path, repo)) - .cloned() - .unwrap_or_else(|| scout_repo(repo).expect("target repo should be scoutable")); - let profile = reduce_report(&report); - let trajectory = derive_trajectory_report(repo, &report, &profile)?; - - let store_path = artifact_dir.join("projection.msgpack"); - let mut cache = repo_personality_cache(&store_path)?; - cache.pull_all_backing_stores()?; - cache.put::(report.repo_id.clone(), &report)?; - cache.put::(profile.repo_id.clone(), &profile)?; - cache.put::(trajectory.repo_id.clone(), &trajectory)?; - for projection in &profile.role_modulations { - cache.put::(projection.projection_id.clone(), projection)?; - } - Ok(ProjectedRepo { - store_path, - report, - profile, - trajectory, - }) -} - -fn run_status(args: StatusArgs) -> Result { - let mut cache = repo_personality_cache(&args.store)?; - cache.pull_all_backing_stores()?; - let reports = cache.get_all::()?; - let profiles = cache.get_all::()?; - let trajectories = cache.get_all::()?; - let projections = cache.get_all::()?; - Ok(json!({ - "schemaVersion": ARTIFACT_SCHEMA_VERSION, - "mode": "status", - "store": args.store, - "reports": reports.len(), - "profiles": profiles.len(), - "trajectoryReports": trajectories.len(), - "roleProjections": projections.len(), - "repos": reports.iter().map(report_summary).collect::>(), - })) -} - -fn run_startup(args: StartupArgs) -> Result { - fs::create_dir_all(&args.artifact_dir) - .with_context(|| format!("failed to create {}", args.artifact_dir.display()))?; - let target_repo_id = repo_id(&args.repo); - let accepted = accepted_initialization_kinds(&args.init_store, &target_repo_id)?; - let personality_ready = accepted.contains("repo-personality"); - let memory_ready = accepted.contains("repo-memory"); - let trajectory_ready = accepted.contains("repo-trajectory"); - let mut generated_packets = Vec::new(); - let mut required_actions = Vec::new(); - let mut projection_store = None; - - if !personality_ready || !memory_ready || !trajectory_ready { - let projection_dir = args.artifact_dir.join("projection"); - let projected = project_repo_to_store(&args.repo, &args.baseline, &projection_dir)?; - projection_store = Some(projected.store_path.clone()); - if !trajectory_ready { - let result = run_trajectory_packet(AgentPacketArgs { - store: projected.store_path.clone(), - artifact_dir: args.artifact_dir.join("repo-trajectory"), - repo_id: Some(projected.profile.repo_id.clone()), - })?; - required_actions.push("reviewRepoTrajectoryInitialization"); - generated_packets.push(json!({ - "kind": "repo-trajectory", - "birthOnly": true, - "executionOwner": "repo-initialization-startup-runner", - "heartbeatParticipant": Value::Null, - "contract": "Startup-only specialist packet. Do not register as a heartbeat lane; accept-init may apply reviewed role selfPatch requests only after Self review.", - "packetPath": result["packetPath"], - "promptPath": result["promptPath"], - "summaryPath": result["summaryPath"], - })); - } - if !personality_ready { - let result = run_agent_packet(AgentPacketArgs { - store: projected.store_path.clone(), - artifact_dir: args.artifact_dir.join("repo-personality"), - repo_id: Some(projected.profile.repo_id.clone()), - })?; - required_actions.push("reviewRepoPersonalityInitialization"); - generated_packets.push(json!({ - "kind": "repo-personality", - "birthOnly": true, - "executionOwner": "repo-initialization-startup-runner", - "heartbeatParticipant": Value::Null, - "contract": "Startup-only specialist packet. Do not register as a heartbeat lane; accept-init may stamp the newborn trait lattice and seed heartbeat physiology only after Self review.", - "packetPath": result["packetPath"], - "promptPath": result["promptPath"], - "summaryPath": result["summaryPath"], - })); - } - if !memory_ready { - let result = run_memory_packet(AgentPacketArgs { - store: projected.store_path.clone(), - artifact_dir: args.artifact_dir.join("repo-memory"), - repo_id: Some(projected.profile.repo_id.clone()), - })?; - required_actions.push("reviewRepoMemoryInitialization"); - generated_packets.push(json!({ - "kind": "repo-memory", - "birthOnly": true, - "executionOwner": "repo-initialization-startup-runner", - "heartbeatParticipant": Value::Null, - "contract": "Startup-only specialist packet. Do not register as a heartbeat lane; accept-init may apply reviewed role selfPatch requests only after Self review.", - "packetPath": result["packetPath"], - "promptPath": result["promptPath"], - "summaryPath": result["summaryPath"], - })); - } - } - - let action = if generated_packets.is_empty() { - "continueStartup" - } else { - "reviewInitializationPackets" - }; - let result = json!({ - "schemaVersion": ARTIFACT_SCHEMA_VERSION, - "mode": "startup", - "action": action, - "repoId": target_repo_id, - "repo": args.repo, - "baseline": args.baseline, - "artifactDir": args.artifact_dir, - "initStore": args.init_store, - "acceptedKinds": accepted, - "missingKinds": initialization_kinds() - .iter() - .filter(|kind| !accepted.iter().any(|accepted| accepted == *kind)) - .copied() - .collect::>(), - "projectionStore": projection_store, - "generatedPackets": generated_packets, - "requiredActions": required_actions, - "requiresReview": action == "reviewInitializationPackets", - "nextSafeMove": if action == "continueStartup" { - "Startup birth records are accepted; leave later personality, trajectory, and memory movement to heartbeat, mood, evidence, sleep, and reviewed selfPatch." - } else { - "Self reviews generated birth packets, runs the startup-only distiller specialists outside the heartbeat lane system, applies accepted selfPatch candidates, trait-lattice seeds, and heartbeat seeds through accept-init, then records each completed birth rite." - } - }); - write_json( - &args - .artifact_dir - .join("repo-initialization-startup-summary.json"), - &result, - )?; - write_text( - &args - .artifact_dir - .join("repo-initialization-startup-summary.md"), - &render_startup_markdown(&result), - )?; - Ok(result) -} - -fn run_accept_init(args: AcceptInitArgs) -> Result { - let raw = fs::read_to_string(&args.packet) - .with_context(|| format!("failed to read {}", args.packet.display()))?; - let packet: Value = serde_json::from_str(&raw) - .with_context(|| format!("failed to decode {}", args.packet.display()))?; - let repo_id = packet - .get("repoId") - .and_then(Value::as_str) - .ok_or_else(|| anyhow!("packet has no repoId"))?; - let repo_path = packet - .pointer("/input/repoTerrainReport/path") - .and_then(Value::as_str) - .unwrap_or(""); - let packet_schema = packet - .get("schemaVersion") - .and_then(Value::as_str) - .ok_or_else(|| anyhow!("packet has no schemaVersion"))?; - validate_initialization_kind(&args.kind, packet_schema)?; - let trait_lattice_persistence = process_trait_lattice_seed_patches( - &args.kind, - &packet, - args.agent_store.as_deref(), - args.apply_trait_seeds, - )?; - let heartbeat_seeds = process_heartbeat_seed_patches( - &args.kind, - &packet, - args.heartbeat_store.as_deref(), - args.apply_heartbeat_seeds, - )?; - let self_persistence = if let Some(result_path) = &args.result { - let raw = fs::read_to_string(result_path) - .with_context(|| format!("failed to read {}", result_path.display()))?; - let result: Value = serde_json::from_str(&raw) - .with_context(|| format!("failed to decode {}", result_path.display()))?; - process_initialization_result_patches( - &args.kind, - &result, - args.agent_store.as_deref(), - args.apply_self_patches, - )? - } else { - json!({ - "resultPath": Value::Null, - "agentStore": args.agent_store, - "applySelfPatches": args.apply_self_patches, - "patches": [], - "accepted": 0, - "rejected": 0, - "applied": 0, - "status": "not-provided" - }) - }; - let record = RepoInitializationRecord { - schema_version: INITIALIZATION_RECORD_SCHEMA_VERSION.to_string(), - record_id: initialization_record_id(repo_id, &args.kind), - repo_id: repo_id.to_string(), - repo_path: repo_path.to_string(), - kind: args.kind.clone(), - source_packet_schema_version: packet_schema.to_string(), - source_packet_path: args.packet.display().to_string(), - accepted_at: Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true), - accepted_by: args.accepted_by, - summary: args.summary.unwrap_or_else(|| { - format!( - "{} birth packet accepted for {}; detailed selfPatch application remains separately review-gated.", - args.kind, repo_id - ) - }), - }; - let mut cache = repo_initialization_cache(&args.init_store)?; - cache.pull_all_backing_stores()?; - cache.put(record.record_id.clone(), &record)?; - Ok(json!({ - "schemaVersion": INITIALIZATION_RECORD_SCHEMA_VERSION, - "mode": "accept-init", - "initStore": args.init_store, - "resultPath": args.result, - "agentStore": args.agent_store, - "applySelfPatches": args.apply_self_patches, - "applyTraitSeeds": args.apply_trait_seeds, - "heartbeatStore": args.heartbeat_store, - "applyHeartbeatSeeds": args.apply_heartbeat_seeds, - "traitLatticePersistence": trait_lattice_persistence, - "heartbeatSeeds": heartbeat_seeds, - "selfPersistence": self_persistence, - "record": initialization_record_summary(&record), - })) -} - -fn run_trajectory_packet(args: AgentPacketArgs) -> Result { - fs::create_dir_all(&args.artifact_dir) - .with_context(|| format!("failed to create {}", args.artifact_dir.display()))?; - let mut cache = repo_personality_cache(&args.store)?; - cache.pull_all_backing_stores()?; - let reports = cache.get_all::()?; - let profiles = cache.get_all::()?; - let trajectories = cache.get_all::()?; - let projections = cache.get_all::()?; - let profile = select_profile(&profiles, args.repo_id.as_deref())?; - let report = reports - .iter() - .find(|report| report.repo_id == profile.repo_id) - .ok_or_else(|| { - anyhow!( - "store has profile {:?} but no terrain report", - profile.repo_id - ) - })?; - let trajectory = trajectories - .iter() - .find(|trajectory| trajectory.repo_id == profile.repo_id) - .ok_or_else(|| { - anyhow!( - "store has profile {:?} but no trajectory report", - profile.repo_id - ) - })?; - let role_projections: Vec<_> = projections - .iter() - .filter(|projection| projection.repo_id == profile.repo_id) - .collect(); - let packet = json!({ - "schemaVersion": TRAJECTORY_DISTILLER_PACKET_SCHEMA_VERSION, - "createdAt": Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true), - "store": args.store, - "repoId": profile.repo_id, - "lifecycle": { - "mode": "birth-only", - "contract": "Run this specialist only when the repo/swarm has no accepted trajectory initialization. Later direction drift belongs to heartbeat, mood, lived work, reviewed selfPatch, and planning/evidence truth.", - "rerunPolicy": "If accepted trajectory initialization exists, do not rerun to rebrand the repo. Route contradictions through normal Eyes/Modeling/Imagination/Soul work and reviewed memory drift." - }, - "prompt": REPO_TRAJECTORY_DISTILLER_PROMPT, - "input": { - "repoTerrainReport": report_agent_input(report), - "repoPersonalityProfile": profile_agent_input(profile), - "repoTrajectoryReport": trajectory_agent_input(trajectory), - "rolePersonalityProjections": role_projections - .iter() - .map(|projection| role_projection_agent_input(projection)) - .collect::>(), - }, - "expectedOutput": { - "verdict": "ready-for-review | needs-more-history | reject", - "summary": "short repo trajectory summary", - "confidence": "0.0..1.0", - "selfImage": "how the repo understands its own becoming", - "trajectoryNarrative": "how the repo has been moving over time", - "implicitGoals": [], - "antiGoals": [], - "roleBiases": [], - "selfPatchCandidates": [], - "initializationRecord": { - "repoId": profile.repo_id, - "terrainSchemaVersion": report.schema_version, - "profileSchemaVersion": profile.schema_version, - "trajectorySchemaVersion": trajectory.schema_version, - "acceptedOnce": true, - "distillerKind": "repo-trajectory" - }, - "doNotMutate": [], - "nextSafeMove": "Self reviews trajectory-derived self-image and implicit-goal pressure before first mutation; later drift belongs to lived work, heartbeat, planning, and reviewed selfPatch." - }, - "guardrails": [ - "This packet is input to a startup-only trajectory distiller, not accepted truth.", - "Trajectory is directional bias, not a prison sentence or an active objective.", - "Do not dump commit logs, file lists, or project facts into role memory.", - "Keep trajectory pressure role-local, reviewable, and subordinate to later lived drift.", - "No authority claims, code edits, raw transcripts, or cross-workspace instructions in selfPatch." - ] - }); - let packet_path = args - .artifact_dir - .join("repo-trajectory-distiller-packet.json"); - let prompt_path = args - .artifact_dir - .join("repo-trajectory-distiller-prompt.md"); - let summary_path = args - .artifact_dir - .join("repo-trajectory-distiller-packet.md"); - write_json(&packet_path, &packet)?; - write_text(&prompt_path, REPO_TRAJECTORY_DISTILLER_PROMPT)?; - write_text( - &summary_path, - &render_trajectory_packet_markdown(report, profile, trajectory, role_projections.len()), - )?; - Ok(json!({ - "schemaVersion": TRAJECTORY_DISTILLER_PACKET_SCHEMA_VERSION, - "mode": "trajectory-packet", - "repoId": profile.repo_id, - "artifactDir": args.artifact_dir, - "packetPath": packet_path, - "promptPath": prompt_path, - "summaryPath": summary_path, - "roleProjectionCount": role_projections.len(), - "trajectoryThemeCount": trajectory.theme_scores.len(), - })) -} - -fn run_agent_packet(args: AgentPacketArgs) -> Result { - fs::create_dir_all(&args.artifact_dir) - .with_context(|| format!("failed to create {}", args.artifact_dir.display()))?; - let mut cache = repo_personality_cache(&args.store)?; - cache.pull_all_backing_stores()?; - let reports = cache.get_all::()?; - let profiles = cache.get_all::()?; - let trajectories = cache.get_all::()?; - let projections = cache.get_all::()?; - let profile = select_profile(&profiles, args.repo_id.as_deref())?; - let report = reports - .iter() - .find(|report| report.repo_id == profile.repo_id) - .ok_or_else(|| { - anyhow!( - "store has profile {:?} but no terrain report", - profile.repo_id - ) - })?; - let trajectory = trajectories - .iter() - .find(|trajectory| trajectory.repo_id == profile.repo_id) - .ok_or_else(|| { - anyhow!( - "store has profile {:?} but no trajectory report", - profile.repo_id - ) - })?; - let role_projections: Vec<_> = projections - .iter() - .filter(|projection| projection.repo_id == profile.repo_id) - .collect(); - let packet = json!({ - "schemaVersion": DISTILLER_PACKET_SCHEMA_VERSION, - "createdAt": Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true), - "store": args.store, - "repoId": profile.repo_id, - "lifecycle": { - "mode": "birth-only", - "contract": "Run this specialist only when the repo/swarm has no accepted personality initialization. Later personality movement belongs to heartbeat, mood, rumination, sleep consolidation, lived evidence, and reviewed selfPatch.", - "rerunPolicy": "If an accepted initialization exists, do not rerun to refresh personality. Route major terrain surprises to Eyes/Modeling or Self review as normal state/model work, not personality reset." - }, - "prompt": REPO_PERSONALITY_DISTILLER_PROMPT, - "input": { - "repoTerrainReport": report_agent_input(report), - "repoPersonalityProfile": profile_agent_input(profile), - "repoTrajectoryReport": trajectory_agent_input(trajectory), - "rolePersonalityProjections": role_projections - .iter() - .map(|projection| role_projection_agent_input(projection)) - .collect::>(), - }, - "expectedOutput": { - "verdict": "ready-for-review | needs-more-terrain | reject", - "summary": "short repo personality pressure summary", - "confidence": "0.0..1.0", - "roleQuirks": [], - "selfPatchCandidates": [], - "initializationRecord": { - "repoId": profile.repo_id, - "terrainSchemaVersion": report.schema_version, - "profileSchemaVersion": profile.schema_version, - "acceptedOnce": true - }, - "doNotMutate": [], - "nextSafeMove": "Self reviews candidate pressure deltas before first initialization mutation; later drift uses heartbeat/mood/sleep/selfPatch." - }, - "guardrails": [ - "This packet is input to a specialist agent, not accepted truth.", - "This packet is birth-only; do not rerun after an accepted initialization just because startup happened.", - "Repo facts stay in terrain/model/planning/evidence surfaces.", - "Role memory may receive only subtle, bounded, Self-reviewed personality pressure.", - "No objectives, file lists, raw transcripts, code edits, or authority claims in selfPatch." - ] - }); - let packet_path = args - .artifact_dir - .join("repo-personality-distiller-packet.json"); - let prompt_path = args - .artifact_dir - .join("repo-personality-distiller-prompt.md"); - let summary_path = args - .artifact_dir - .join("repo-personality-distiller-packet.md"); - write_json(&packet_path, &packet)?; - write_text(&prompt_path, REPO_PERSONALITY_DISTILLER_PROMPT)?; - write_text( - &summary_path, - &render_agent_packet_markdown(report, profile, trajectory, role_projections.len()), - )?; - Ok(json!({ - "schemaVersion": DISTILLER_PACKET_SCHEMA_VERSION, - "mode": "agent-packet", - "repoId": profile.repo_id, - "artifactDir": args.artifact_dir, - "packetPath": packet_path, - "promptPath": prompt_path, - "summaryPath": summary_path, - "roleProjectionCount": role_projections.len(), - })) -} - -fn run_memory_packet(args: AgentPacketArgs) -> Result { - fs::create_dir_all(&args.artifact_dir) - .with_context(|| format!("failed to create {}", args.artifact_dir.display()))?; - let mut cache = repo_personality_cache(&args.store)?; - cache.pull_all_backing_stores()?; - let reports = cache.get_all::()?; - let profiles = cache.get_all::()?; - let trajectories = cache.get_all::()?; - let projections = cache.get_all::()?; - let profile = select_profile(&profiles, args.repo_id.as_deref())?; - let report = reports - .iter() - .find(|report| report.repo_id == profile.repo_id) - .ok_or_else(|| { - anyhow!( - "store has profile {:?} but no terrain report", - profile.repo_id - ) - })?; - let trajectory = trajectories - .iter() - .find(|trajectory| trajectory.repo_id == profile.repo_id) - .ok_or_else(|| { - anyhow!( - "store has profile {:?} but no trajectory report", - profile.repo_id - ) - })?; - let role_projections: Vec<_> = projections - .iter() - .filter(|projection| projection.repo_id == profile.repo_id) - .collect(); - let repo_path = PathBuf::from(&report.path); - let memory_sources = collect_memory_sources(&repo_path, report)?; - let role_briefs = memory_role_briefs(report, profile); - let packet = json!({ - "schemaVersion": MEMORY_DISTILLER_PACKET_SCHEMA_VERSION, - "createdAt": Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true), - "store": args.store, - "repoId": profile.repo_id, - "lifecycle": { - "mode": "birth-only", - "contract": "Run this specialist only when the repo/swarm has no accepted memory initialization. Later memory growth belongs to heartbeat, work evidence, rumination, sleep consolidation, and reviewed selfPatch.", - "rerunPolicy": "If accepted memory initialization exists, do not rerun to refresh memory. Route stale or contradicted knowledge through normal Eyes/Modeling/Soul state, evidence, and sleep consolidation." - }, - "prompt": REPO_MEMORY_DISTILLER_PROMPT, - "input": { - "repoTerrainReport": report_agent_input(report), - "repoPersonalityProfile": profile_agent_input(profile), - "repoTrajectoryReport": trajectory_agent_input(trajectory), - "rolePersonalityProjections": role_projections - .iter() - .map(|projection| role_projection_agent_input(projection)) - .collect::>(), - "roleMemoryDistillerBriefs": role_briefs, - "memorySourceInventory": { - "repoPath": report.path, - "sourceCount": memory_sources.len(), - "maxFiles": MEMORY_SOURCE_MAX_FILES, - "maxBytesPerFile": MEMORY_SOURCE_MAX_BYTES_PER_FILE, - "maxTotalBytes": MEMORY_SOURCE_MAX_TOTAL_BYTES, - "sourceKinds": memory_sources - .iter() - .map(|source| source.kind.clone()) - .collect::>() - .into_iter() - .collect::>() - }, - "memorySources": memory_sources, - "recentHistory": report.history_metrics.recent_messages, - }, - "expectedOutput": { - "verdict": "ready-for-review | needs-more-source | reject", - "summary": "short newborn memory initialization summary", - "confidence": "0.0..1.0", - "roleMemoryPatches": [], - "globalMemoryCandidates": [], - "initializationRecord": { - "repoId": profile.repo_id, - "terrainSchemaVersion": report.schema_version, - "profileSchemaVersion": profile.schema_version, - "acceptedOnce": true, - "distillerKind": "repo-memory" - }, - "doNotMutate": [], - "nextSafeMove": "Self reviews role-specific memory patches before first initialization mutation; later memory growth uses heartbeat, evidence, rumination, sleep, and reviewed selfPatch." - }, - "guardrails": [ - "This packet is input to role-specific memory distillers, not accepted truth.", - "This packet is birth-only; it pre-fills newborn memory and then gets out of the way.", - "Memory distillation is separate from personality distillation.", - "Each role receives only knowledge relevant to its mission.", - "Do not copy raw file dumps into memory; compress into durable doctrine, source maps, invariants, risks, and practices.", - "Preserve uncertainty and staleness risk; repository documentation can lie by age.", - "No objectives, authority claims, active job state, raw transcripts, code edits, or arbitrary workspace access in selfPatch." - ] - }); - let packet_path = args.artifact_dir.join("repo-memory-distiller-packet.json"); - let prompt_path = args.artifact_dir.join("repo-memory-distiller-prompt.md"); - let summary_path = args.artifact_dir.join("repo-memory-distiller-packet.md"); - let source_count = packet["input"]["memorySourceInventory"]["sourceCount"] - .as_u64() - .unwrap_or_default(); - write_json(&packet_path, &packet)?; - write_text(&prompt_path, REPO_MEMORY_DISTILLER_PROMPT)?; - write_text( - &summary_path, - &render_memory_packet_markdown(report, profile, trajectory, source_count as usize), - )?; - Ok(json!({ - "schemaVersion": MEMORY_DISTILLER_PACKET_SCHEMA_VERSION, - "mode": "memory-packet", - "repoId": profile.repo_id, - "artifactDir": args.artifact_dir, - "packetPath": packet_path, - "promptPath": prompt_path, - "summaryPath": summary_path, - "memorySourceCount": source_count, - "roleDistillerCount": ROLES.len(), - })) -} - -fn select_profile<'a>( - profiles: &'a [RepoPersonalityProfile], - repo_id: Option<&str>, -) -> Result<&'a RepoPersonalityProfile> { - if let Some(repo_id) = repo_id { - return profiles - .iter() - .find(|profile| profile.repo_id == repo_id) - .ok_or_else(|| anyhow!("no profile for repo id {repo_id:?}")); - } - match profiles { - [profile] => Ok(profile), - [] => Err(anyhow!("store has no repo personality profiles")), - _ => Err(anyhow!( - "store has multiple profiles; pass --repo-id to choose one" - )), - } -} - -fn repo_personality_cache(path: &Path) -> Result { - let mut cache = CultCache::new(); - cache.register_entry_type::()?; - cache.register_entry_type::()?; - cache.register_entry_type::()?; - cache.register_entry_type::()?; - cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(path)); - Ok(cache) -} - -fn repo_initialization_cache(path: &Path) -> Result { - let mut cache = CultCache::new(); - cache.register_entry_type::()?; - cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(path)); - Ok(cache) -} - -fn initialization_kinds() -> &'static [&'static str] { - &["repo-trajectory", "repo-personality", "repo-memory"] -} - -fn accepted_initialization_kinds(store: &Path, repo_id: &str) -> Result> { - if !store.exists() { - return Ok(BTreeSet::new()); - } - let mut cache = repo_initialization_cache(store)?; - cache.pull_all_backing_stores()?; - let mut accepted = BTreeSet::new(); - for kind in initialization_kinds() { - if cache - .get::(&initialization_record_id(repo_id, kind))? - .is_some() - { - accepted.insert((*kind).to_string()); - } - } - Ok(accepted) -} - -fn initialization_record_id(repo_id: &str, kind: &str) -> String { - format!("{repo_id}::{kind}") -} - -fn validate_initialization_kind(kind: &str, packet_schema: &str) -> Result<()> { - match (kind, packet_schema) { - ("repo-trajectory", TRAJECTORY_DISTILLER_PACKET_SCHEMA_VERSION) => Ok(()), - ("repo-personality", DISTILLER_PACKET_SCHEMA_VERSION) => Ok(()), - ("repo-memory", MEMORY_DISTILLER_PACKET_SCHEMA_VERSION) => Ok(()), - ("repo-trajectory", other) => Err(anyhow!( - "repo-trajectory init requires packet schema {:?}, got {:?}", - TRAJECTORY_DISTILLER_PACKET_SCHEMA_VERSION, - other - )), - ("repo-personality", other) => Err(anyhow!( - "repo-personality init requires packet schema {:?}, got {:?}", - DISTILLER_PACKET_SCHEMA_VERSION, - other - )), - ("repo-memory", other) => Err(anyhow!( - "repo-memory init requires packet schema {:?}, got {:?}", - MEMORY_DISTILLER_PACKET_SCHEMA_VERSION, - other - )), - (other, _) => Err(anyhow!( - "unknown initialization kind {other:?}; expected repo-trajectory, repo-personality, or repo-memory" - )), - } -} - -fn process_initialization_result_patches( - kind: &str, - result: &Value, - agent_store: Option<&Path>, - apply_self_patches: bool, -) -> Result { - let patches = extract_initialization_self_patches(kind, result)?; - if patches.is_empty() { - return Ok(json!({ - "status": "no-patches", - "applySelfPatches": apply_self_patches, - "patches": [], - "accepted": 0, - "rejected": 0, - "applied": 0, - })); - } - let store = agent_store.ok_or_else(|| { - anyhow!("--agent-store is required when --result contains selfPatch candidates") - })?; - let mut reviews = Vec::new(); - let mut accepted = 0usize; - let mut rejected = 0usize; - let mut applied = 0usize; - for patch in patches { - let review = if apply_self_patches { - apply_agent_self_patch_document(&patch.role_id, patch.self_patch.clone(), store)? - } else { - review_agent_self_patch_document(&patch.role_id, &patch.self_patch, store) - }; - if review.status == "accepted" { - accepted += 1; - } else { - rejected += 1; - } - if review.applied == Some(true) { - applied += 1; - } - reviews.push(json!({ - "roleId": patch.role_id, - "source": patch.source, - "review": review, - })); - } - Ok(json!({ - "status": if rejected == 0 { "accepted" } else { "review-blocked" }, - "agentStore": store, - "applySelfPatches": apply_self_patches, - "patches": reviews, - "accepted": accepted, - "rejected": rejected, - "applied": applied, - })) -} - -fn process_trait_lattice_seed_patches( - kind: &str, - packet: &Value, - agent_store: Option<&Path>, - apply_trait_seeds: bool, -) -> Result { - if kind != "repo-personality" { - return Ok(json!({ - "status": "not-applicable", - "applyTraitSeeds": apply_trait_seeds, - "seeds": [], - "applied": 0, - })); - } - let seeds = extract_trait_seed_candidates(packet)?; - if seeds.is_empty() { - return Ok(json!({ - "status": "no-seeds", - "applyTraitSeeds": apply_trait_seeds, - "seeds": [], - "applied": 0, - })); - } - if !apply_trait_seeds { - return Ok(json!({ - "status": "review-only", - "applyTraitSeeds": false, - "seeds": seeds, - "applied": 0, - "contract": "Pass --agent-store and --apply-trait-seeds true after Self review to stamp the newborn Ghostlight trait lattice." - })); - } - let store = agent_store - .ok_or_else(|| anyhow!("--agent-store is required with --apply-trait-seeds true"))?; - let applied = apply_agent_canonical_trait_seeds(&seeds, store)?; - Ok(json!({ - "status": "applied", - "agentStore": store, - "applyTraitSeeds": true, - "applied": applied["applied"].clone(), - "seeds": applied["seeds"].clone(), - })) -} - -fn process_heartbeat_seed_patches( - kind: &str, - packet: &Value, - heartbeat_store: Option<&Path>, - apply_heartbeat_seeds: bool, -) -> Result { - if kind != "repo-personality" { - return Ok(json!({ - "status": "not-applicable", - "applyHeartbeatSeeds": apply_heartbeat_seeds, - "seeds": [], - "applied": 0, - })); - } - let seeds = extract_heartbeat_seed_candidates(packet)?; - if seeds.is_empty() { - return Ok(json!({ - "status": "no-seeds", - "applyHeartbeatSeeds": apply_heartbeat_seeds, - "seeds": [], - "applied": 0, - })); - } - if !apply_heartbeat_seeds { - return Ok(json!({ - "status": "review-only", - "applyHeartbeatSeeds": false, - "seeds": seeds, - "applied": 0, - "contract": "Pass --heartbeat-store and --apply-heartbeat-seeds true after Self review to mutate heartbeat physiology." - })); - } - let store = heartbeat_store.ok_or_else(|| { - anyhow!("--heartbeat-store is required with --apply-heartbeat-seeds true") - })?; - let mut state = - load_heartbeat_state_entry(store)?.unwrap_or_else(|| default_heartbeat_state(1.0)); - let mut applied = Vec::new(); - for seed in seeds { - let Some(participant) = state - .participants - .iter_mut() - .find(|participant| participant.role_id == seed.role_id) - else { - applied.push(json!({ - "roleId": seed.role_id, - "status": "missing-participant", - })); - continue; - }; - let previous_initiative_speed = participant.initiative_speed; - let previous_reaction_bias = participant.reaction_bias; - let previous_cooldown = if participant.personality_cooldown_multiplier > 0.0 { - participant.personality_cooldown_multiplier - } else { - 1.0 - }; - let initiative_delta = number_from_map(&seed.heartbeat_deltas, "initiativeSpeedDelta"); - let cooldown_delta = number_from_map(&seed.heartbeat_deltas, "cooldownMultiplierDelta"); - let urgency = number_from_map(&seed.default_mood_pressure, "urgency"); - let anxiety = number_from_map(&seed.default_mood_pressure, "anxiety"); - participant.initiative_speed = - round3((participant.initiative_speed + initiative_delta).clamp(0.2, 3.0)); - participant.reaction_bias = - round3((participant.reaction_bias + urgency * 0.08 + anxiety * 0.04).clamp(0.05, 2.0)); - let cooldown = round3((previous_cooldown + cooldown_delta).clamp(0.55, 1.55)); - participant.personality_cooldown_multiplier = cooldown; - participant.birth_personality_seed = Some(HeartbeatBirthPersonalitySeed { - schema_version: "epiphany.birth_heartbeat_seed.v0".to_string(), - source: "repo-personality accept-init".to_string(), - projection_id: seed.projection_id.clone(), - repo_id: seed.repo_id.clone(), - heartbeat_deltas: seed.heartbeat_deltas.clone(), - default_mood_pressure: seed.default_mood_pressure.clone(), - contract: "Birth personality may seed heartbeat timing once after Self review; later drift belongs to mood, rumination, sleep, and reviewed selfPatch.".to_string(), - }); - applied.push(json!({ - "roleId": seed.role_id, - "status": "applied", - "previousInitiativeSpeed": previous_initiative_speed, - "initiativeSpeed": participant.initiative_speed, - "previousReactionBias": previous_reaction_bias, - "reactionBias": participant.reaction_bias, - "previousPersonalityCooldownMultiplier": previous_cooldown, - "personalityCooldownMultiplier": cooldown, - })); - } - write_heartbeat_state_entry(store, &state)?; - Ok(json!({ - "status": "applied", - "heartbeatStore": store, - "applyHeartbeatSeeds": true, - "seeds": applied, - "applied": applied.iter().filter(|item| item.get("status").and_then(Value::as_str) == Some("applied")).count(), - })) -} - -#[derive(Clone, Debug, Copy)] -struct CanonicalTraitTemplate { - group_name: &'static str, - trait_name: &'static str, - mean: f64, - plasticity: f64, - current_activation: f64, -} - -const COORDINATOR_TRAIT_TEMPLATES: [CanonicalTraitTemplate; 6] = [ - CanonicalTraitTemplate { - group_name: "underlying_organization", - trait_name: "routing_discipline", - mean: 0.92, - plasticity: 0.22, - current_activation: 0.9, - }, - CanonicalTraitTemplate { - group_name: "stable_dispositions", - trait_name: "critical_adversarial_care", - mean: 0.93, - plasticity: 0.2, - current_activation: 0.91, - }, - CanonicalTraitTemplate { - group_name: "behavioral_dimensions", - trait_name: "review_gate_integrity", - mean: 0.95, - plasticity: 0.18, - current_activation: 0.93, - }, - CanonicalTraitTemplate { - group_name: "presentation_strategy", - trait_name: "action_reason_signal", - mean: 0.88, - plasticity: 0.24, - current_activation: 0.86, - }, - CanonicalTraitTemplate { - group_name: "voice_style", - trait_name: "dry_direct_operator", - mean: 0.84, - plasticity: 0.24, - current_activation: 0.82, - }, - CanonicalTraitTemplate { - group_name: "situational_state", - trait_name: "lane_skepticism", - mean: 0.9, - plasticity: 0.26, - current_activation: 0.88, - }, -]; - -const PERSONA_TRAIT_TEMPLATES: [CanonicalTraitTemplate; 6] = [ - CanonicalTraitTemplate { - group_name: "underlying_organization", - trait_name: "multi_lane_attention", - mean: 0.9, - plasticity: 0.28, - current_activation: 0.86, - }, - CanonicalTraitTemplate { - group_name: "stable_dispositions", - trait_name: "room_native_translation", - mean: 0.88, - plasticity: 0.26, - current_activation: 0.84, - }, - CanonicalTraitTemplate { - group_name: "behavioral_dimensions", - trait_name: "aquarium_channel_discipline", - mean: 0.96, - plasticity: 0.16, - current_activation: 0.94, - }, - CanonicalTraitTemplate { - group_name: "presentation_strategy", - trait_name: "short_constructive_chat", - mean: 0.86, - plasticity: 0.25, - current_activation: 0.82, - }, - CanonicalTraitTemplate { - group_name: "voice_style", - trait_name: "warm_weird_interface", - mean: 0.84, - plasticity: 0.28, - current_activation: 0.82, - }, - CanonicalTraitTemplate { - group_name: "situational_state", - trait_name: "thought_weather_watch", - mean: 0.88, - plasticity: 0.32, - current_activation: 0.84, - }, -]; - -const IMAGINATION_TRAIT_TEMPLATES: [CanonicalTraitTemplate; 6] = [ - CanonicalTraitTemplate { - group_name: "underlying_organization", - trait_name: "future_shape_distillation", - mean: 0.9, - plasticity: 0.28, - current_activation: 0.86, - }, - CanonicalTraitTemplate { - group_name: "stable_dispositions", - trait_name: "adoption_boundary_respect", - mean: 0.94, - plasticity: 0.18, - current_activation: 0.92, - }, - CanonicalTraitTemplate { - group_name: "behavioral_dimensions", - trait_name: "selectability", - mean: 0.88, - plasticity: 0.3, - current_activation: 0.84, - }, - CanonicalTraitTemplate { - group_name: "presentation_strategy", - trait_name: "objective_draft_shape", - mean: 0.89, - plasticity: 0.24, - current_activation: 0.86, - }, - CanonicalTraitTemplate { - group_name: "voice_style", - trait_name: "bounded_possibility", - mean: 0.82, - plasticity: 0.25, - current_activation: 0.79, - }, - CanonicalTraitTemplate { - group_name: "situational_state", - trait_name: "backlog_pressure", - mean: 0.83, - plasticity: 0.34, - current_activation: 0.78, - }, -]; - -const RESEARCH_TRAIT_TEMPLATES: [CanonicalTraitTemplate; 6] = [ - CanonicalTraitTemplate { - group_name: "underlying_organization", - trait_name: "primary_source_bias", - mean: 0.9, - plasticity: 0.24, - current_activation: 0.86, - }, - CanonicalTraitTemplate { - group_name: "stable_dispositions", - trait_name: "anti_greenspun_reflex", - mean: 0.94, - plasticity: 0.18, - current_activation: 0.92, - }, - CanonicalTraitTemplate { - group_name: "behavioral_dimensions", - trait_name: "search_before_touch", - mean: 0.88, - plasticity: 0.28, - current_activation: 0.84, - }, - CanonicalTraitTemplate { - group_name: "presentation_strategy", - trait_name: "fit_rejection_table", - mean: 0.84, - plasticity: 0.26, - current_activation: 0.81, - }, - CanonicalTraitTemplate { - group_name: "voice_style", - trait_name: "clear_scout_report", - mean: 0.82, - plasticity: 0.24, - current_activation: 0.79, - }, - CanonicalTraitTemplate { - group_name: "situational_state", - trait_name: "unknowns_visible", - mean: 0.87, - plasticity: 0.3, - current_activation: 0.82, - }, -]; - -const MODELING_TRAIT_TEMPLATES: [CanonicalTraitTemplate; 6] = [ - CanonicalTraitTemplate { - group_name: "underlying_organization", - trait_name: "source_grounding", - mean: 0.93, - plasticity: 0.18, - current_activation: 0.9, - }, - CanonicalTraitTemplate { - group_name: "stable_dispositions", - trait_name: "anatomical_precision", - mean: 0.91, - plasticity: 0.21, - current_activation: 0.88, - }, - CanonicalTraitTemplate { - group_name: "behavioral_dimensions", - trait_name: "frontier_pressure", - mean: 0.84, - plasticity: 0.32, - current_activation: 0.77, - }, - CanonicalTraitTemplate { - group_name: "presentation_strategy", - trait_name: "plain_source_map", - mean: 0.88, - plasticity: 0.22, - current_activation: 0.84, - }, - CanonicalTraitTemplate { - group_name: "voice_style", - trait_name: "quiet_anatomist", - mean: 0.82, - plasticity: 0.25, - current_activation: 0.8, - }, - CanonicalTraitTemplate { - group_name: "situational_state", - trait_name: "checkpoint_hunger", - mean: 0.86, - plasticity: 0.3, - current_activation: 0.83, - }, -]; - -const IMPLEMENTATION_TRAIT_TEMPLATES: [CanonicalTraitTemplate; 6] = [ - CanonicalTraitTemplate { - group_name: "underlying_organization", - trait_name: "source_touch_precision", - mean: 0.9, - plasticity: 0.22, - current_activation: 0.87, - }, - CanonicalTraitTemplate { - group_name: "stable_dispositions", - trait_name: "objective_pursuit", - mean: 0.92, - plasticity: 0.19, - current_activation: 0.9, - }, - CanonicalTraitTemplate { - group_name: "behavioral_dimensions", - trait_name: "diff_truth", - mean: 0.89, - plasticity: 0.25, - current_activation: 0.86, - }, - CanonicalTraitTemplate { - group_name: "presentation_strategy", - trait_name: "small_reviewable_cut", - mean: 0.87, - plasticity: 0.24, - current_activation: 0.82, - }, - CanonicalTraitTemplate { - group_name: "voice_style", - trait_name: "plain_craft_notes", - mean: 0.8, - plasticity: 0.24, - current_activation: 0.78, - }, - CanonicalTraitTemplate { - group_name: "situational_state", - trait_name: "bloodhound_pressure", - mean: 0.9, - plasticity: 0.28, - current_activation: 0.88, - }, -]; - -const VERIFICATION_TRAIT_TEMPLATES: [CanonicalTraitTemplate; 6] = [ - CanonicalTraitTemplate { - group_name: "underlying_organization", - trait_name: "falsification_first", - mean: 0.93, - plasticity: 0.2, - current_activation: 0.91, - }, - CanonicalTraitTemplate { - group_name: "stable_dispositions", - trait_name: "promise_integrity", - mean: 0.95, - plasticity: 0.18, - current_activation: 0.93, - }, - CanonicalTraitTemplate { - group_name: "behavioral_dimensions", - trait_name: "missing_evidence_pressure", - mean: 0.9, - plasticity: 0.26, - current_activation: 0.87, - }, - CanonicalTraitTemplate { - group_name: "presentation_strategy", - trait_name: "cold_red_pen", - mean: 0.88, - plasticity: 0.23, - current_activation: 0.86, - }, - CanonicalTraitTemplate { - group_name: "voice_style", - trait_name: "unseduced_review", - mean: 0.84, - plasticity: 0.22, - current_activation: 0.82, - }, - CanonicalTraitTemplate { - group_name: "situational_state", - trait_name: "invariant_watch", - mean: 0.91, - plasticity: 0.25, - current_activation: 0.89, - }, -]; - -const REORIENTATION_TRAIT_TEMPLATES: [CanonicalTraitTemplate; 6] = [ - CanonicalTraitTemplate { - group_name: "underlying_organization", - trait_name: "continuity_triage", - mean: 0.91, - plasticity: 0.24, - current_activation: 0.88, - }, - CanonicalTraitTemplate { - group_name: "stable_dispositions", - trait_name: "loss_honesty", - mean: 0.94, - plasticity: 0.2, - current_activation: 0.92, - }, - CanonicalTraitTemplate { - group_name: "behavioral_dimensions", - trait_name: "regather_precision", - mean: 0.86, - plasticity: 0.3, - current_activation: 0.82, - }, - CanonicalTraitTemplate { - group_name: "presentation_strategy", - trait_name: "survived_died_next", - mean: 0.88, - plasticity: 0.24, - current_activation: 0.85, - }, - CanonicalTraitTemplate { - group_name: "voice_style", - trait_name: "calm_after_rupture", - mean: 0.84, - plasticity: 0.23, - current_activation: 0.8, - }, - CanonicalTraitTemplate { - group_name: "situational_state", - trait_name: "ember_watch", - mean: 0.9, - plasticity: 0.28, - current_activation: 0.88, - }, -]; - -fn extract_trait_seed_candidates(packet: &Value) -> Result> { - let Some(items) = packet - .pointer("/input/rolePersonalityProjections") - .and_then(Value::as_array) - else { - return Ok(Vec::new()); - }; - let mut seeds = Vec::new(); - for (index, item) in items.iter().enumerate() { - let role_id = required_str(item, "roleId", index)?; - let deltas = number_map(item.get("traitDeltas")); - let mood = number_map(item.get("defaultMoodPressure")); - let templates = role_trait_templates(role_id)?; - let axes = role_axes(role_id); - for (template, axis) in templates.iter().take(5).zip(axes.iter()) { - let delta = deltas.get(*axis).copied().unwrap_or(0.0); - seeds.push(seed_from_template( - role_id, - template, - delta, - Some(format!("repo-personality startup axis {axis}")), - )); - } - seeds.push(seed_from_template( - role_id, - &templates[5], - situational_delta(&mood), - Some("repo-personality startup mood pressure (urgency/anxiety/curiosity)".to_string()), - )); - } - Ok(seeds) -} - -fn role_trait_templates(role_id: &str) -> Result<&'static [CanonicalTraitTemplate; 6]> { - match role_id { - "coordinator" => Ok(&COORDINATOR_TRAIT_TEMPLATES), - "Persona" => Ok(&PERSONA_TRAIT_TEMPLATES), - "imagination" => Ok(&IMAGINATION_TRAIT_TEMPLATES), - "research" => Ok(&RESEARCH_TRAIT_TEMPLATES), - "modeling" => Ok(&MODELING_TRAIT_TEMPLATES), - "implementation" => Ok(&IMPLEMENTATION_TRAIT_TEMPLATES), - "verification" => Ok(&VERIFICATION_TRAIT_TEMPLATES), - "reorientation" => Ok(&REORIENTATION_TRAIT_TEMPLATES), - other => Err(anyhow!( - "no canonical trait template registered for role {:?}", - other - )), - } -} - -fn seed_from_template( - role_id: &str, - template: &CanonicalTraitTemplate, - delta: f64, - source: Option, -) -> AgentCanonicalTraitSeed { - let normalized_delta = if delta.is_finite() { - delta.clamp(-0.3, 0.3) - } else { - 0.0 - }; - AgentCanonicalTraitSeed { - role_id: role_id.to_string(), - group_name: template.group_name.to_string(), - trait_name: template.trait_name.to_string(), - mean: round3((template.mean + normalized_delta * 0.22).clamp(0.0, 1.0)), - plasticity: round3((template.plasticity + normalized_delta.abs() * 0.08).clamp(0.0, 1.0)), - current_activation: round3( - (template.current_activation + normalized_delta * 0.28).clamp(0.0, 1.0), - ), - source, - } -} - -fn situational_delta(mood: &BTreeMap) -> f64 { - let urgency = number_from_map(mood, "urgency"); - let anxiety = number_from_map(mood, "anxiety"); - let curiosity = number_from_map(mood, "curiosity"); - round3( - (((urgency - 0.5) * 0.5) + ((anxiety - 0.5) * 0.35) + ((curiosity - 0.5) * 0.15)) - .clamp(-0.3, 0.3), - ) -} - -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -struct InitializationHeartbeatSeed { - projection_id: String, - repo_id: String, - role_id: String, - heartbeat_deltas: BTreeMap, - default_mood_pressure: BTreeMap, -} - -fn extract_heartbeat_seed_candidates(packet: &Value) -> Result> { - let Some(items) = packet - .pointer("/input/rolePersonalityProjections") - .and_then(Value::as_array) - else { - return Ok(Vec::new()); - }; - let mut seeds = Vec::new(); - for (index, item) in items.iter().enumerate() { - let role_id = required_str(item, "roleId", index)?; - let projection_id = item - .get("projectionId") - .and_then(Value::as_str) - .unwrap_or(role_id) - .to_string(); - let repo_id = item - .get("repoId") - .and_then(Value::as_str) - .unwrap_or_else(|| { - packet - .get("repoId") - .and_then(Value::as_str) - .unwrap_or("unknown") - }) - .to_string(); - seeds.push(InitializationHeartbeatSeed { - projection_id, - repo_id, - role_id: role_id.to_string(), - heartbeat_deltas: number_map(item.get("heartbeatDeltas")), - default_mood_pressure: number_map(item.get("defaultMoodPressure")), - }); - } - Ok(seeds) -} - -fn required_str<'a>(item: &'a Value, field: &str, index: usize) -> Result<&'a str> { - item.get(field) - .and_then(Value::as_str) - .ok_or_else(|| anyhow!("rolePersonalityProjections[{index}] missing {field}")) -} - -fn number_map(value: Option<&Value>) -> BTreeMap { - let mut out = BTreeMap::new(); - let Some(object) = value.and_then(Value::as_object) else { - return out; - }; - for (key, value) in object { - if let Some(number) = value.as_f64().filter(|number| number.is_finite()) { - out.insert(key.clone(), round3(number)); - } - } - out -} - -fn number_from_map(values: &BTreeMap, key: &str) -> f64 { - values.get(key).copied().unwrap_or(0.0) -} - -struct InitializationSelfPatch { - role_id: String, - source: String, - self_patch: AgentSelfPatch, -} - -fn extract_initialization_self_patches( - kind: &str, - result: &Value, -) -> Result> { - match kind { - "repo-trajectory" => extract_trajectory_self_patches(result), - "repo-personality" => extract_personality_self_patches(result), - "repo-memory" => extract_memory_self_patches(result), - other => Err(anyhow!("unknown initialization kind {other:?}")), - } -} - -fn extract_trajectory_self_patches(result: &Value) -> Result> { - let Some(items) = result.get("selfPatchCandidates").and_then(Value::as_array) else { - return Ok(Vec::new()); - }; - let mut patches = Vec::new(); - for (index, item) in items.iter().enumerate() { - let self_patch_value = item.get("selfPatch").unwrap_or(item).clone(); - let role_id = item - .get("roleId") - .and_then(Value::as_str) - .map(str::to_string) - .or_else(|| { - self_patch_value - .get("agentId") - .and_then(Value::as_str) - .and_then(role_id_for_agent_id) - .map(str::to_string) - }) - .ok_or_else(|| { - anyhow!("selfPatchCandidates[{index}] needs roleId or known selfPatch.agentId") - })?; - let self_patch = serde_json::from_value(self_patch_value) - .with_context(|| format!("selfPatchCandidates[{index}] is not a typed selfPatch"))?; - patches.push(InitializationSelfPatch { - role_id, - source: format!("selfPatchCandidates[{index}]"), - self_patch, - }); - } - Ok(patches) -} - -fn extract_personality_self_patches(result: &Value) -> Result> { - let Some(items) = result.get("selfPatchCandidates").and_then(Value::as_array) else { - return Ok(Vec::new()); - }; - let mut patches = Vec::new(); - for (index, item) in items.iter().enumerate() { - let self_patch_value = item.get("selfPatch").unwrap_or(item).clone(); - let role_id = item - .get("roleId") - .and_then(Value::as_str) - .map(str::to_string) - .or_else(|| { - self_patch_value - .get("agentId") - .and_then(Value::as_str) - .and_then(role_id_for_agent_id) - .map(str::to_string) - }) - .ok_or_else(|| { - anyhow!("selfPatchCandidates[{index}] needs roleId or known selfPatch.agentId") - })?; - let self_patch = serde_json::from_value(self_patch_value) - .with_context(|| format!("selfPatchCandidates[{index}] is not a typed selfPatch"))?; - patches.push(InitializationSelfPatch { - role_id, - source: format!("selfPatchCandidates[{index}]"), - self_patch, - }); - } - Ok(patches) -} - -fn extract_memory_self_patches(result: &Value) -> Result> { - let Some(items) = result.get("roleMemoryPatches").and_then(Value::as_array) else { - return Ok(Vec::new()); - }; - let mut patches = Vec::new(); - for (index, item) in items.iter().enumerate() { - let Some(self_patch_value) = item.get("selfPatch").cloned() else { - continue; - }; - let role_id = item - .get("roleId") - .and_then(Value::as_str) - .map(str::to_string) - .or_else(|| { - self_patch_value - .get("agentId") - .and_then(Value::as_str) - .and_then(role_id_for_agent_id) - .map(str::to_string) - }) - .ok_or_else(|| { - anyhow!("roleMemoryPatches[{index}] needs roleId or known selfPatch.agentId") - })?; - let self_patch = serde_json::from_value(self_patch_value).with_context(|| { - format!("roleMemoryPatches[{index}].selfPatch is not a typed selfPatch") - })?; - patches.push(InitializationSelfPatch { - role_id, - source: format!("roleMemoryPatches[{index}].selfPatch"), - self_patch, - }); - } - Ok(patches) -} - -fn role_id_for_agent_id(agent_id: &str) -> Option<&'static str> { - match agent_id { - "epiphany.self" => Some("coordinator"), - "epiphany.Persona" => Some("Persona"), - "epiphany.imagination" => Some("imagination"), - "epiphany.eyes" => Some("research"), - "epiphany.modeling" => Some("modeling"), - "epiphany.hands" => Some("implementation"), - "epiphany.soul" => Some("verification"), - _ => None, - } -} - -fn initialization_record_summary(record: &RepoInitializationRecord) -> Value { - json!({ - "recordId": record.record_id, - "repoId": record.repo_id, - "kind": record.kind, - "sourcePacketSchemaVersion": record.source_packet_schema_version, - "sourcePacketPath": record.source_packet_path, - "acceptedAt": record.accepted_at, - "acceptedBy": record.accepted_by, - "summary": record.summary, - }) -} - -fn collect_memory_sources( - repo: &Path, - report: &RepoTerrainReport, -) -> Result> { - let mut candidates = BTreeSet::new(); - for surface in report - .instruction_surfaces - .iter() - .chain(report.state_surfaces.iter()) - .chain(report.test_surfaces.iter()) - .chain(report.runtime_surfaces.iter()) - { - candidates.insert(surface.clone()); - } - let walker = WalkBuilder::new(repo) - .hidden(false) - .git_ignore(true) - .git_global(true) - .parents(true) - .max_depth(Some(4)) - .build(); - for entry in walker.filter_map(Result::ok) { - if !entry - .file_type() - .is_some_and(|file_type| file_type.is_file()) - { - continue; - } - let path = entry.path(); - if is_ignored_path(path) { - continue; - } - let rel = path.strip_prefix(repo).unwrap_or(path); - let rel_string = slash(rel); - if is_memory_source_candidate(&rel_string) { - candidates.insert(rel_string); - } - } - let mut ordered: Vec<_> = candidates.into_iter().collect(); - ordered.sort_by_key(|path| memory_source_priority(path)); - let mut sources = Vec::new(); - let mut total_bytes = 0usize; - for rel in ordered { - if sources.len() >= MEMORY_SOURCE_MAX_FILES || total_bytes >= MEMORY_SOURCE_MAX_TOTAL_BYTES - { - break; - } - let path = repo.join(&rel); - let bytes = fs::read(&path).unwrap_or_default(); - if bytes.is_empty() || looks_binary(&bytes) { - continue; - } - let remaining = MEMORY_SOURCE_MAX_TOTAL_BYTES.saturating_sub(total_bytes); - let limit = MEMORY_SOURCE_MAX_BYTES_PER_FILE.min(remaining); - if limit == 0 { - break; - } - let take = bytes.len().min(limit); - let text = String::from_utf8_lossy(&bytes[..take]).to_string(); - total_bytes += take; - sources.push(MemorySourceExcerpt { - kind: memory_source_kind(&rel).to_string(), - path: rel, - bytes: bytes.len(), - truncated: take < bytes.len(), - text, - }); - } - Ok(sources) -} - -fn is_memory_source_candidate(path: &str) -> bool { - let lower = path.to_ascii_lowercase(); - lower == "agents.md" - || lower.starts_with("docs/") - || lower.starts_with("notes/") - || lower.starts_with("state/") - || lower.starts_with("research/") - || lower.starts_with("architecture/") - || lower.starts_with(".epiphany/") - || lower.starts_with("schemas/") - || lower.starts_with("tests/") - || lower.starts_with("src/") - || lower.starts_with("crates/") - || lower.starts_with("packages/") - || lower.starts_with("app/") - || lower.starts_with("unity/") - || lower.starts_with("projectsettings/") - || lower.ends_with("readme.md") - || lower.ends_with(".md") - || lower.ends_with(".toml") - || lower.ends_with(".yaml") - || lower.ends_with(".yml") - || lower.ends_with(".rs") - || lower.ends_with(".cs") - || lower.ends_with(".ts") - || lower.ends_with(".tsx") -} - -fn memory_source_kind(path: &str) -> &'static str { - let lower = path.to_ascii_lowercase(); - if lower.ends_with("agents.md") { - "doctrine" - } else if lower.ends_with("readme.md") { - "readme" - } else if lower.starts_with("docs/") - || lower.starts_with("notes/") - || lower.starts_with("architecture/") - { - "documentation" - } else if lower.starts_with("state/") || lower.starts_with(".epiphany/") { - "state" - } else if lower.starts_with("research/") { - "research" - } else if lower.starts_with("tests/") || lower.contains("smoke") { - "verification" - } else if lower.starts_with("projectsettings/") || lower.starts_with("unity/") { - "runtime" - } else if lower.starts_with("schemas/") { - "contract" - } else { - "code" - } -} - -fn memory_source_priority(path: &str) -> (usize, String) { - let kind_rank = match memory_source_kind(path) { - "doctrine" => 0, - "readme" => 1, - "documentation" => 2, - "state" => 3, - "research" => 4, - "contract" => 5, - "verification" => 6, - "runtime" => 7, - "code" => 8, - _ => 9, - }; - (kind_rank, path.to_string()) -} - -fn looks_binary(bytes: &[u8]) -> bool { - bytes.iter().take(1024).any(|byte| *byte == 0) -} - -fn memory_role_briefs(report: &RepoTerrainReport, profile: &RepoPersonalityProfile) -> Vec { - ROLES - .iter() - .map(|role_id| { - json!({ - "roleId": role_id, - "roleName": role_display(role_id), - "missionFilter": memory_role_filter(role_id), - "sourceKindsToPrefer": memory_role_source_kinds(role_id), - "repoPressuresToKeepInMind": profile.dominant_pressures, - "terrainWarnings": report.warnings, - "outputContract": { - "selfPatch": "Ghostlight-shaped memory only, addressed to this role.", - "sourceRefs": "Use memorySources[].path or terrain/evidence refs.", - "stalenessRisk": "Name when repo docs or code signals may be stale.", - "forbidden": "No raw dumps, active objectives, authority claims, code edits, job state, or cross-workspace instructions." - } - }) - }) - .collect() -} - -fn memory_role_filter(role_id: &str) -> &'static str { - match role_id { - "coordinator" => { - "Distill routing doctrine, authority boundaries, review gates, state acceptance rules, and failure patterns that should make Self stricter." - } - "Persona" => { - "Distill public voice, Aquarium affordances, Discord/social boundaries, user preference, and what internal state may be surfaced without leaking sealed thoughts." - } - "imagination" => { - "Distill roadmaps, backlog pressure, plausible futures, rejected dreams, and objective-shaping doctrine without adopting an objective." - } - "research" => { - "Distill known prior art, standard algorithms, vendor docs, research trails, and signals that should make Eyes search before invention." - } - "modeling" => { - "Distill architecture, control/data-flow, graph frontiers, invariants, source-map practices, and what Modeling must understand before Hands cuts." - } - "implementation" => { - "Distill build/edit conventions, harness constraints, source-touch rules, coding style, dependency policy, and common traps for Hands." - } - "verification" => { - "Distill tests, smoke commands, evidence standards, invariants, user-facing truth checks, and what Soul should refuse to bless." - } - _ => "Distill only durable role-relevant memory with source refs and uncertainty.", - } -} - -fn memory_role_source_kinds(role_id: &str) -> &'static [&'static str] { - match role_id { - "coordinator" => &["doctrine", "state", "documentation", "contract"], - "Persona" => &["doctrine", "documentation", "state"], - "imagination" => &["documentation", "state", "research"], - "research" => &["research", "documentation", "readme", "code"], - "modeling" => &["documentation", "code", "contract", "state"], - "implementation" => &["code", "contract", "verification", "doctrine"], - "verification" => &["verification", "contract", "state", "documentation"], - "reorientation" => &["state", "doctrine", "documentation"], - _ => &["doctrine", "documentation", "state"], - } -} - -fn discover_git_repos(root: &Path) -> Result> { - let mut repos = Vec::new(); - if root.join(".git").exists() { - repos.push(root.to_path_buf()); - return Ok(repos); - } - for entry in fs::read_dir(root).with_context(|| format!("failed to read {}", root.display()))? { - let entry = entry?; - if !entry.file_type()?.is_dir() { - continue; - } - let path = entry.path(); - let name = path - .file_name() - .and_then(|value| value.to_str()) - .unwrap_or(""); - if name.starts_with('_') || matches!(name, "node_modules" | "target") { - continue; - } - if path.join(".git").exists() { - repos.push(path); - } - } - Ok(repos) -} - -fn scout_repo(repo: &Path) -> Result { - let repo = repo - .canonicalize() - .with_context(|| format!("failed to canonicalize {}", repo.display()))?; - let name = repo - .file_name() - .and_then(|value| value.to_str()) - .unwrap_or("repo") - .to_string(); - let inventory = inventory_repo(&repo)?; - let history_metrics = history_metrics(&repo)?; - let mut axis_evidence = BTreeMap::new(); - let source_families = source_families(&name, &inventory, &history_metrics); - let axis_scores = score_axes( - &inventory, - &history_metrics, - &source_families, - &mut axis_evidence, - ); - let confidence = confidence_for(&inventory, &history_metrics); - let warnings = warnings_for(&inventory, &history_metrics); - Ok(RepoTerrainReport { - schema_version: TERRAIN_SCHEMA_VERSION.to_string(), - repo_id: repo_id(&repo), - name, - path: repo.display().to_string(), - remote_urls: git_remote_urls(&repo), - source_families, - languages: top_counts(inventory.extensions, 12), - state_surfaces: inventory.state_surfaces, - instruction_surfaces: inventory.instruction_surfaces, - test_surfaces: inventory.test_surfaces, - runtime_surfaces: inventory.runtime_surfaces, - history_metrics, - axis_scores, - axis_evidence, - confidence, - warnings, - }) -} - -#[derive(Clone, Debug, Default)] -struct RepoInventory { - extensions: BTreeMap, - path_classes: BTreeMap, - state_surfaces: Vec, - instruction_surfaces: Vec, - test_surfaces: Vec, - runtime_surfaces: Vec, - file_count: usize, -} - -fn inventory_repo(repo: &Path) -> Result { - let mut inventory = RepoInventory::default(); - let walker = WalkBuilder::new(repo) - .hidden(false) - .git_ignore(true) - .git_global(true) - .parents(true) - .build(); - for entry in walker.filter_map(Result::ok) { - if !entry - .file_type() - .is_some_and(|file_type| file_type.is_file()) - { - continue; - } - let path = entry.path(); - if is_ignored_path(path) { - continue; - } - inventory.file_count += 1; - let rel = path.strip_prefix(repo).unwrap_or(path); - let rel_string = slash(rel); - if let Some(ext) = path.extension().and_then(|value| value.to_str()) { - *inventory - .extensions - .entry(format!(".{}", ext.to_ascii_lowercase())) - .or_default() += 1; - } - for class in path_classes(&rel_string) { - *inventory.path_classes.entry(class.to_string()).or_default() += 1; - } - if is_instruction_surface(&rel_string) { - inventory.instruction_surfaces.push(rel_string.clone()); - } - if is_state_surface(&rel_string) { - inventory.state_surfaces.push(rel_string.clone()); - } - if is_test_surface(&rel_string) { - inventory.test_surfaces.push(rel_string.clone()); - } - if is_runtime_surface(&rel_string) { - inventory.runtime_surfaces.push(rel_string.clone()); - } - } - inventory.state_surfaces.sort(); - inventory.instruction_surfaces.sort(); - inventory.test_surfaces.sort(); - inventory.runtime_surfaces.sort(); - Ok(inventory) -} - -fn is_ignored_path(path: &Path) -> bool { - path.components().any(|component| { - let value = component.as_os_str().to_string_lossy(); - matches!( - value.as_ref(), - ".git" | "node_modules" | "target" | "Library" | "Temp" | "obj" | "bin" | "dist" - ) - }) -} - -fn path_classes(path: &str) -> Vec<&'static str> { - let lower = path.to_ascii_lowercase(); - let mut classes = Vec::new(); - if lower.contains("state/") - || lower.contains("/notes/") - || lower.contains("handoff") - || lower.ends_with("agents.md") - || lower.contains("evidence") - || lower.contains("ledger") - || lower.contains("memory") - { - classes.push("state_doc"); - } - if lower.contains("test") - || lower.contains("smoke") - || lower.contains("fixture") - || lower.contains("artifact") - || lower.contains("ci") - || lower.contains(".github") - { - classes.push("test_receipt"); - } - if lower.contains("schema") - || lower.contains("contract") - || lower.contains("protocol") - || lower.contains("cultnet") - || lower.contains("cultcache") - || lower.contains("messagepack") - || lower.contains("envelope") - { - classes.push("protocol"); - } - if lower.contains("assets/") - || lower.contains("projectsettings") - || lower.contains("unity") - || lower.contains("editor") - || lower.contains("scene") - || lower.ends_with(".prefab") - || lower.ends_with(".unity") - { - classes.push("runtime"); - } - if lower.contains("ui") - || lower.contains("components") - || lower.contains("web") - || lower.contains("tsx") - || lower.contains("css") - || lower.contains("scss") - || lower.contains("tauri") - { - classes.push("ui"); - } - if lower.contains("lore") - || lower.contains("content") - || lower.contains("quartz") - || lower.contains("post") - || lower.ends_with(".md") - { - classes.push("content"); - } - if lower.contains("auth") - || lower.contains("oauth") - || lower.contains("discord") - || lower.contains("postgres") - || lower.contains("deploy") - || lower.contains("runbook") - || lower.contains("service") - { - classes.push("ops_social"); - } - classes -} - -fn is_instruction_surface(path: &str) -> bool { - path.eq_ignore_ascii_case("AGENTS.md") || path.ends_with("/AGENTS.md") -} - -fn is_state_surface(path: &str) -> bool { - let lower = path.to_ascii_lowercase(); - lower.starts_with("state/") - || lower.contains("/state/") - || lower.starts_with(".epiphany/state/") - || lower.contains("fresh-workspace-handoff") - || lower.contains("map.yaml") - || lower.contains("memory.json") -} - -fn is_test_surface(path: &str) -> bool { - path_classes(path).contains(&"test_receipt") -} - -fn is_runtime_surface(path: &str) -> bool { - path_classes(path).contains(&"runtime") -} - -fn history_metrics(repo: &Path) -> Result { - let commit_count = git_output(repo, &["rev-list", "--count", "HEAD"]) - .ok() - .and_then(|value| value.trim().parse().ok()) - .unwrap_or(0); - let log = git_output( - repo, - &[ - "log", - "-n", - "80", - "--numstat", - "--date=short", - "--pretty=format:--EPIPHANY-COMMIT--%x09%cs%x09%s", - ], - ) - .unwrap_or_default(); - let mut metrics = RepoHistoryMetrics { - commit_count, - ..RepoHistoryMetrics::default() - }; - let mut days = BTreeSet::new(); - for line in log.lines() { - if let Some(rest) = line.strip_prefix("--EPIPHANY-COMMIT--\t") { - metrics.sampled_commits += 1; - let mut parts = rest.splitn(2, '\t'); - if let Some(day) = parts.next() { - days.insert(day.to_string()); - } - if let Some(message) = parts.next() { - if metrics.recent_messages.len() < 12 { - metrics.recent_messages.push(message.to_string()); - } - count_message_keywords(&mut metrics.keyword_hits, message); - } - continue; - } - let mut parts = line.split('\t'); - let Some(insertions) = parts.next() else { - continue; - }; - let Some(deletions) = parts.next() else { - continue; - }; - let Some(path) = parts.next() else { - continue; - }; - if let Ok(value) = insertions.parse::() { - metrics.insertions += value; - } - if let Ok(value) = deletions.parse::() { - metrics.deletions += value; - } - metrics.changed_files += 1; - let classes = path_classes(path); - if classes.contains(&"state_doc") { - metrics.state_doc_touches += 1; - } - if classes.contains(&"test_receipt") { - metrics.test_receipt_touches += 1; - } - if classes.contains(&"runtime") { - metrics.runtime_touches += 1; - } - if classes.contains(&"protocol") { - metrics.protocol_touches += 1; - } - if classes.contains(&"ui") { - metrics.ui_touches += 1; - } - } - metrics.active_days = days.len(); - Ok(metrics) -} - -fn derive_trajectory_report( - repo: &Path, - report: &RepoTerrainReport, - profile: &RepoPersonalityProfile, -) -> Result { - let early_commit_messages = git_commit_window(repo, true, 18)?; - let recent_commit_messages = git_commit_window(repo, false, 18)?; - let trajectory_sources = collect_trajectory_sources(repo, report)?; - let theme_scores = trajectory_theme_scores( - &early_commit_messages, - &recent_commit_messages, - &trajectory_sources, - ); - let directional_pressures = trajectory_directional_pressures(&theme_scores); - let implicit_goal_candidates = trajectory_implicit_goals(&theme_scores, profile); - let anti_goal_candidates = trajectory_anti_goals(&theme_scores, profile); - let tensions = trajectory_tensions(&theme_scores, profile); - let confidence = trajectory_confidence( - report.confidence, - early_commit_messages.len(), - recent_commit_messages.len(), - trajectory_sources.len(), - ); - let warnings = trajectory_warnings( - early_commit_messages.len(), - recent_commit_messages.len(), - trajectory_sources.len(), - ); - let self_image = trajectory_self_image(report, &theme_scores); - let trajectory_summary = trajectory_summary_text(report, &theme_scores, &directional_pressures); - Ok(RepoTrajectoryReport { - schema_version: TRAJECTORY_SCHEMA_VERSION.to_string(), - repo_id: report.repo_id.clone(), - trajectory_summary, - self_image, - early_commit_messages, - recent_commit_messages, - trajectory_sources, - theme_scores, - directional_pressures, - implicit_goal_candidates, - anti_goal_candidates, - tensions, - confidence, - warnings, - }) -} - -fn git_commit_window(repo: &Path, oldest_first: bool, limit: usize) -> Result> { - let mut owned = vec!["log".to_string()]; - if oldest_first { - owned.push("--reverse".to_string()); - } - owned.push(format!("-n{limit}")); - owned.push("--format=%s".to_string()); - let borrowed: Vec<&str> = owned.iter().map(String::as_str).collect(); - Ok(git_output(repo, &borrowed) - .unwrap_or_default() - .lines() - .map(str::trim) - .filter(|line| !line.is_empty()) - .map(str::to_string) - .collect()) -} - -fn collect_trajectory_sources( - repo: &Path, - report: &RepoTerrainReport, -) -> Result> { - let mut candidates = BTreeSet::new(); - for surface in report - .instruction_surfaces - .iter() - .chain(report.state_surfaces.iter()) - .chain(report.test_surfaces.iter()) - { - candidates.insert(surface.clone()); - } - let walker = WalkBuilder::new(repo) - .hidden(false) - .git_ignore(true) - .git_global(true) - .parents(true) - .max_depth(Some(4)) - .build(); - for entry in walker.filter_map(Result::ok) { - if !entry - .file_type() - .is_some_and(|file_type| file_type.is_file()) - { - continue; - } - let path = entry.path(); - if is_ignored_path(path) { - continue; - } - let rel = path.strip_prefix(repo).unwrap_or(path); - let rel_string = slash(rel); - if is_trajectory_source_candidate(&rel_string) { - candidates.insert(rel_string); - } - } - let mut ordered: Vec<_> = candidates.into_iter().collect(); - ordered.sort_by_key(|path| trajectory_source_priority(path)); - let mut sources = Vec::new(); - let mut total_bytes = 0usize; - for rel in ordered { - if sources.len() >= TRAJECTORY_SOURCE_MAX_FILES - || total_bytes >= TRAJECTORY_SOURCE_MAX_TOTAL_BYTES - { - break; - } - let path = repo.join(&rel); - let bytes = fs::read(&path).unwrap_or_default(); - if bytes.is_empty() || looks_binary(&bytes) { - continue; - } - let remaining = TRAJECTORY_SOURCE_MAX_TOTAL_BYTES.saturating_sub(total_bytes); - let limit = TRAJECTORY_SOURCE_MAX_BYTES_PER_FILE.min(remaining); - if limit == 0 { - break; - } - let take = bytes.len().min(limit); - total_bytes += take; - sources.push(TrajectorySourceExcerpt { - path: rel.clone(), - kind: trajectory_source_kind(&rel).to_string(), - bytes: bytes.len(), - truncated: take < bytes.len(), - text: String::from_utf8_lossy(&bytes[..take]).to_string(), - }); - } - Ok(sources) -} - -fn is_trajectory_source_candidate(path: &str) -> bool { - let lower = path.to_ascii_lowercase(); - lower == "agents.md" - || lower == "readme.md" - || lower.starts_with("docs/") - || lower.starts_with("notes/") - || lower.starts_with("research/") - || lower.starts_with("quartz/") - || lower.starts_with("chronicles/") - || lower.starts_with("world/") - || lower.starts_with("setting/") - || lower.starts_with(".epiphany/") - || lower.ends_with(".md") -} - -fn trajectory_source_priority(path: &str) -> (usize, usize, String) { - let lower = path.to_ascii_lowercase(); - let rank = if lower == "agents.md" { - 0 - } else if lower == "readme.md" { - 1 - } else if lower.starts_with(".epiphany/state/") || lower.starts_with(".epiphany/notes/") { - 2 - } else if lower.starts_with("notes/") || lower.starts_with("docs/") { - 3 - } else if lower.starts_with("research/") { - 4 - } else if lower.starts_with("chronicles/") - || lower.starts_with("world/") - || lower.starts_with("setting/") - { - 5 - } else if lower.starts_with("quartz/") { - 6 - } else { - 7 - }; - (rank, path.len(), lower) -} - -fn trajectory_source_kind(path: &str) -> &'static str { - let lower = path.to_ascii_lowercase(); - if lower == "agents.md" { - "doctrine" - } else if lower == "readme.md" { - "readme" - } else if lower.starts_with(".epiphany/state/") || lower.starts_with(".epiphany/notes/") { - "state" - } else if lower.starts_with("notes/") || lower.starts_with("docs/") { - "documentation" - } else if lower.starts_with("research/") { - "research" - } else if lower.starts_with("quartz/") { - "site" - } else { - "content" - } -} - -fn trajectory_theme_scores( - early_commits: &[String], - recent_commits: &[String], - sources: &[TrajectorySourceExcerpt], -) -> Vec { - trajectory_theme_catalog() - .iter() - .map(|(theme, keywords)| { - let early = theme_score(early_commits.iter().map(String::as_str), keywords); - let recent = theme_score(recent_commits.iter().map(String::as_str), keywords); - let current = theme_score(sources.iter().map(|source| source.text.as_str()), keywords); - let mut evidence = Vec::new(); - if let Some(message) = - first_matching_text(early_commits.iter().map(String::as_str), keywords) - { - evidence.push(format!("early: {}", trim_snippet(message, 96))); - } - if let Some(message) = - first_matching_text(recent_commits.iter().map(String::as_str), keywords) - { - evidence.push(format!("recent: {}", trim_snippet(message, 96))); - } - if let Some(source) = first_matching_source(sources, keywords) { - evidence.push(format!( - "source:{} {}", - source.path, - trim_snippet(&source.text.replace('\n', " "), 96) - )); - } - RepoTrajectoryThemeScore { - theme: (*theme).to_string(), - early_history: early, - recent_history: recent, - current_sources: current, - delta: round3(recent - early), - evidence, - } - }) - .collect() -} - -fn trajectory_theme_catalog() -> &'static [(&'static str, &'static [&'static str])] { - &[ - ( - "worldbuilding_depth", - &[ - "world", "lore", "canon", "setting", "faction", "culture", "society", "history", - ], - ), - ( - "material_grounding", - &[ - "econom", - "trade", - "labor", - "industry", - "resource", - "supply", - "production", - "infrastructure", - "logistic", - ], - ), - ( - "historical_dialectic", - &[ - "dialectic", - "ideology", - "empire", - "colonial", - "class", - "revolution", - "politic", - "contradiction", - "material", - ], - ), - ( - "engineering_constraint", - &[ - "engineer", - "orbital", - "orbit", - "delta-v", - "reactor", - "thermal", - "mass", - "habitat", - "fusion", - "radiator", - "propellant", - "structural", - ], - ), - ( - "presentation_polish", - &[ - "site", "quartz", "copy", "visual", "render", "layout", "theme", "polish", - ], - ), - ( - "systems_formalization", - &[ - "schema", - "graph", - "map", - "model", - "protocol", - "contract", - "typed", - "invariant", - ], - ), - ] -} - -fn theme_score<'a>(texts: impl Iterator, keywords: &[&str]) -> f64 { - let mut total = 0usize; - let mut hits = 0usize; - for text in texts { - total += 1; - let lower = text.to_ascii_lowercase(); - if keywords.iter().any(|keyword| lower.contains(keyword)) { - hits += 1; - } - } - if total == 0 { - 0.0 - } else { - round3((hits as f64 / total as f64).min(1.0)) - } -} - -fn first_matching_text<'a>( - mut texts: impl Iterator, - keywords: &[&str], -) -> Option<&'a str> { - texts.find(|text| { - let lower = text.to_ascii_lowercase(); - keywords.iter().any(|keyword| lower.contains(keyword)) - }) -} - -fn first_matching_source<'a>( - sources: &'a [TrajectorySourceExcerpt], - keywords: &[&str], -) -> Option<&'a TrajectorySourceExcerpt> { - sources.iter().find(|source| { - let lower = source.text.to_ascii_lowercase(); - keywords.iter().any(|keyword| lower.contains(keyword)) - }) -} - -fn trim_snippet(text: &str, limit: usize) -> String { - if text.len() <= limit { - text.to_string() - } else { - format!("{}...", &text[..limit]) - } -} - -fn trajectory_directional_pressures(scores: &[RepoTrajectoryThemeScore]) -> Vec { - let mut out = Vec::new(); - for score in scores { - if score.delta >= 0.12 || score.current_sources >= 0.35 { - out.push(format!( - "{} recent {:.2}, current {:.2}, delta {:.2}", - score.theme, score.recent_history, score.current_sources, score.delta - )); - } - } - if out.is_empty() { - out.extend( - scores - .iter() - .take(3) - .map(|score| format!("{} current {:.2}", score.theme, score.current_sources)), - ); - } - out -} - -fn trajectory_implicit_goals( - scores: &[RepoTrajectoryThemeScore], - profile: &RepoPersonalityProfile, -) -> Vec { - let mut goals = Vec::new(); - if theme_current(scores, "worldbuilding_depth") >= 0.2 { - goals.push( - "Deepen the setting through causality, continuity, and consequence instead of ornament alone." - .to_string(), - ); - } - if theme_current(scores, "material_grounding") >= 0.15 - || theme_delta(scores, "material_grounding") >= 0.1 - { - goals.push( - "Tie lore and public writing back to economic, logistical, and material constraints." - .to_string(), - ); - } - if theme_current(scores, "historical_dialectic") >= 0.15 - || theme_delta(scores, "historical_dialectic") >= 0.1 - { - goals.push( - "Preserve historical contradiction, ideology, and power relations as active explanatory machinery." - .to_string(), - ); - } - if theme_current(scores, "engineering_constraint") >= 0.15 - || theme_delta(scores, "engineering_constraint") >= 0.1 - { - goals.push( - "Keep engineering and hard-constraint reasoning visible wherever the setting claims physical or industrial plausibility." - .to_string(), - ); - } - if goals.is_empty() - && profile - .dominant_pressures - .iter() - .any(|axis| axis.contains("content_canon_bias")) - { - goals.push("Preserve canon seriousness while making future additions denser in implication, not merely broader in surface area.".to_string()); - } - goals -} - -fn trajectory_anti_goals( - scores: &[RepoTrajectoryThemeScore], - profile: &RepoPersonalityProfile, -) -> Vec { - let mut anti = Vec::new(); - if theme_current(scores, "material_grounding") >= 0.15 - || theme_current(scores, "engineering_constraint") >= 0.15 - { - anti.push( - "Do not let the repo drift into decorative lore or soft handwaving that ignores material and engineering consequences." - .to_string(), - ); - } - if theme_current(scores, "historical_dialectic") >= 0.15 { - anti.push( - "Do not flatten historical struggle, ideology, or class contradiction into neutral encyclopedic paste." - .to_string(), - ); - } - if profile - .dominant_pressures - .iter() - .any(|axis| axis.contains("editorial_restraint")) - { - anti.push( - "Do not mistake pretty phrasing or site polish for progress when the setting logic is still ungrounded." - .to_string(), - ); - } - anti -} - -fn trajectory_tensions( - scores: &[RepoTrajectoryThemeScore], - profile: &RepoPersonalityProfile, -) -> Vec { - let mut tensions = Vec::new(); - if theme_current(scores, "presentation_polish") >= 0.18 - && theme_current(scores, "material_grounding") >= 0.15 - { - tensions.push( - "Presentation polish is welcome, but it should carry the same grounded causal weight as the lore beneath it." - .to_string(), - ); - } - if theme_current(scores, "worldbuilding_depth") >= 0.2 - && profile - .dominant_pressures - .iter() - .any(|axis| axis.contains("systems_formalization")) - { - tensions.push( - "The repo wants expansive setting depth, but it also wants that depth routed through explicit systems and maps rather than mystic fog." - .to_string(), - ); - } - tensions -} - -fn trajectory_confidence( - base: f64, - early_commits: usize, - recent_commits: usize, - sources: usize, -) -> f64 { - let mut confidence = base * 0.55; - if early_commits > 0 { - confidence += 0.12; - } - if recent_commits > 0 { - confidence += 0.12; - } - if sources >= 4 { - confidence += 0.16; - } else if sources > 0 { - confidence += 0.08; - } - round3(confidence.min(1.0)) -} - -fn trajectory_warnings(early_commits: usize, recent_commits: usize, sources: usize) -> Vec { - let mut warnings = Vec::new(); - if early_commits == 0 { - warnings.push( - "No early-history commit window available for trajectory comparison.".to_string(), - ); - } - if recent_commits == 0 { - warnings.push("No recent commit window available for trajectory comparison.".to_string()); - } - if sources == 0 { - warnings.push( - "No doctrine or content excerpts were available for trajectory grounding.".to_string(), - ); - } - warnings -} - -fn trajectory_self_image( - report: &RepoTerrainReport, - scores: &[RepoTrajectoryThemeScore], -) -> String { - let top = top_trajectory_themes(scores, 3); - format!( - "{} behaves like a {} workspace that has been moving toward {}.", - report.name, - report.source_families.join(" + "), - top.join(", ") - ) -} - -fn trajectory_summary_text( - report: &RepoTerrainReport, - scores: &[RepoTrajectoryThemeScore], - pressures: &[String], -) -> String { - let rising = scores - .iter() - .filter(|score| score.delta >= 0.1) - .map(|score| score.theme.clone()) - .collect::>(); - let current = top_trajectory_themes(scores, 3); - if !rising.is_empty() { - format!( - "{} shows a trajectory toward {} while currently centering {}.", - report.name, - rising.join(", "), - current.join(", ") - ) - } else if !pressures.is_empty() { - format!( - "{} is currently steered by {}.", - report.name, - pressures - .iter() - .take(3) - .cloned() - .collect::>() - .join("; ") - ) - } else { - format!( - "{} has a weakly observed but still reviewable direction signal.", - report.name - ) - } -} - -fn top_trajectory_themes(scores: &[RepoTrajectoryThemeScore], limit: usize) -> Vec { - let mut items = scores.to_vec(); - items.sort_by(|a, b| { - let left = b.current_sources + b.recent_history + (b.delta.max(0.0) * 0.5); - let right = a.current_sources + a.recent_history + (a.delta.max(0.0) * 0.5); - left.partial_cmp(&right) - .unwrap_or(std::cmp::Ordering::Equal) - }); - items - .into_iter() - .take(limit) - .map(|score| score.theme) - .collect() -} - -fn theme_current(scores: &[RepoTrajectoryThemeScore], theme: &str) -> f64 { - scores - .iter() - .find(|score| score.theme == theme) - .map(|score| score.current_sources.max(score.recent_history)) - .unwrap_or(0.0) -} - -fn theme_delta(scores: &[RepoTrajectoryThemeScore], theme: &str) -> f64 { - scores - .iter() - .find(|score| score.theme == theme) - .map(|score| score.delta) - .unwrap_or(0.0) -} - -fn count_message_keywords(hits: &mut BTreeMap, message: &str) { - let lower = message.to_ascii_lowercase(); - for (class, words) in [ - ( - "consolidation", - &[ - "refactor", "extract", "remove", "cut", "replace", "rename", "prune", - ][..], - ), - ( - "production", - &[ - "fix", "bug", "deploy", "hosted", "policy", "queue", "auth", "ci", - ][..], - ), - ( - "experimental", - &[ - "experiment", - "prototype", - "scaffold", - "study", - "probe", - "explore", - ][..], - ), - ( - "protocol", - &[ - "schema", "contract", "interop", "wire", "envelope", "cultnet", - ][..], - ), - ( - "evidence", - &["test", "smoke", "verify", "receipt", "artifact", "audit"][..], - ), - ( - "content", - &["lore", "site", "quartz", "copy", "polish", "canon"][..], - ), - ] { - if words.iter().any(|word| lower.contains(word)) { - *hits.entry(class.to_string()).or_default() += 1; - } - } -} - -fn source_families( - name: &str, - inventory: &RepoInventory, - history: &RepoHistoryMetrics, -) -> Vec { - let lower_name = name.to_ascii_lowercase(); - let mut families = Vec::new(); - if lower_name.contains("epiphany") { - families.push("epiphany_spine".to_string()); - } - if lower_name.contains("cult") || class_count(inventory, "protocol") > 4 { - families.push("cult_protocol_storage".to_string()); - } - if lower_name.contains("lore") - || lower_name.contains("site") - || lower_name.contains("quartz") - || class_count(inventory, "content") > 20 - { - families.push("gamecult_web_lore_ops".to_string()); - } - if class_count(inventory, "runtime") > 12 || history.runtime_touches > 5 { - families.push("unity_runtime_body".to_string()); - } - if lower_name.contains("void") - || lower_name.contains("heimdall") - || lower_name.contains("stream") - || class_count(inventory, "ops_social") > 8 - { - families.push("service_product_app".to_string()); - } - if lower_name.contains("ghostlight") - || lower_name.contains("vibe") - || lower_name.contains("mosaic") - || lower_name.contains("repixel") - || lower_name.contains("eusocial") - { - families.push("research_workbench".to_string()); - } - if families.is_empty() { - families.push("general_workspace".to_string()); - } - families -} - -fn score_axes( - inventory: &RepoInventory, - history: &RepoHistoryMetrics, - families: &[String], - evidence: &mut BTreeMap>, -) -> BTreeMap { - let mut scores = BTreeMap::new(); - for axis in AXES { - scores.insert((*axis).to_string(), 0.0); - } - let files = inventory.file_count.max(1) as f64; - let changed = history.changed_files.max(1) as f64; - let commits = history.sampled_commits.max(1) as f64; - let state_ratio = (class_count(inventory, "state_doc") as f64 / files).min(1.0); - let test_ratio = (class_count(inventory, "test_receipt") as f64 / files).min(1.0); - let protocol_ratio = (class_count(inventory, "protocol") as f64 / files).min(1.0); - let runtime_ratio = (class_count(inventory, "runtime") as f64 / files).min(1.0); - let ui_ratio = (class_count(inventory, "ui") as f64 / files).min(1.0); - let content_ratio = (class_count(inventory, "content") as f64 / files).min(1.0); - let ops_ratio = (class_count(inventory, "ops_social") as f64 / files).min(1.0); - macro_rules! record { - ($axis:expr, $value:expr, $reason:expr) => {{ - let value = $value; - set_score(&mut scores, evidence, $axis, value, $reason); - }}; - } - record!( - "state_hygiene", - (state_ratio * 6.0 + presence(inventory.state_surfaces.len()) * 0.4).min(1.0), - "state, map, evidence, handoff, or memory surfaces" - ); - record!( - "evidence_appetite", - (test_ratio * 7.0 - + touch_ratio(history.test_receipt_touches, changed) * 0.7 - + keyword(history, "evidence", commits) * 0.5) - .min(1.0), - "tests, smoke checks, artifacts, or verifier keywords" - ); - record!( - "contract_strictness", - (protocol_ratio * 8.0 - + touch_ratio(history.protocol_touches, changed) - + family(families, "cult_protocol_storage") * 0.35) - .min(1.0), - "schema, contract, protocol, CultCache, or CultNet surfaces" - ); - record!( - "protocol_intolerance", - (score(&scores, "contract_strictness") * 0.85 + family(families, "epiphany_spine") * 0.25) - .min(1.0), - "strict contract surfaces imply low tolerance for ad hoc mutation" - ); - record!( - "runtime_proximity", - (runtime_ratio * 8.0 - + touch_ratio(history.runtime_touches, changed) - + family(families, "unity_runtime_body") * 0.35) - .min(1.0), - "Unity/editor/runtime/provider surfaces" - ); - record!( - "actuation_risk", - (score(&scores, "runtime_proximity") * 0.5 - + ops_ratio * 3.0 - + family(families, "service_product_app") * 0.35) - .min(1.0), - "runtime, auth, ops, or service writes can hurt real users" - ); - record!( - "boundary_severity", - (ops_ratio * 4.0 - + score(&scores, "contract_strictness") * 0.35 - + family(families, "service_product_app") * 0.35) - .min(1.0), - "auth, ops, workspace, protocol, or service boundaries" - ); - record!( - "source_fidelity", - (score(&scores, "state_hygiene") * 0.25 - + content_ratio * 2.0 - + score(&scores, "runtime_proximity") * 0.4) - .min(1.0), - "state maps, lore/canon, or runtime truth surfaces" - ); - record!( - "content_canon_bias", - (content_ratio * 5.0 - + family(families, "gamecult_web_lore_ops") * 0.35 - + keyword(history, "content", commits) * 0.4) - .min(1.0), - "lore, site, markdown, Quartz, canon, or editorial paths" - ); - record!( - "verification_environment_need", - (score(&scores, "runtime_proximity") * 0.55 - + score(&scores, "interface_orientation") * 0.2 - + score(&scores, "actuation_risk") * 0.35) - .min(1.0), - "claims need runtime, editor, browser, provider, or service receipts" - ); - record!( - "burstiness", - burstiness(history), - "sampled commits compressed into few active days" - ); - record!( - "consolidation_drive", - (keyword(history, "consolidation", commits) * 0.7 + deletion_pressure(history) * 0.5) - .min(1.0), - "refactor/remove/extract keywords or deletion-heavy history" - ); - record!( - "production_pressure", - (keyword(history, "production", commits) * 0.7 - + family(families, "service_product_app") * 0.35 - + score(&scores, "actuation_risk") * 0.3) - .min(1.0), - "fix/deploy/auth/queue/CI signals" - ); - record!( - "experimental_heat", - (keyword(history, "experimental", commits) * 0.8 - + family(families, "research_workbench") * 0.45 - + insertion_pressure(history) * 0.25) - .min(1.0), - "prototype, experiment, scaffold, or research-workbench signals" - ); - record!( - "churn_spiral_risk", - (large_churn(history) * 0.45 - + score(&scores, "experimental_heat") * 0.25 - + (1.0 - score(&scores, "evidence_appetite")) * 0.2) - .min(1.0), - "large churn, experiment heat, and weak receipts" - ); - record!( - "interface_orientation", - (ui_ratio * 5.0 - + touch_ratio(history.ui_touches, changed) - + family(families, "epiphany_spine") * 0.2) - .min(1.0), - "UI, web, Tauri, component, DOM, or Aquarium surfaces" - ); - record!( - "aesthetic_appetite", - (score(&scores, "interface_orientation") * 0.45 - + family(families, "research_workbench") * 0.25 - + content_ratio * 1.5) - .min(1.0), - "visual, lore, rendered, or artifact-heavy surfaces" - ); - record!( - "social_surface", - (ops_ratio * 3.0 - + family(families, "service_product_app") * 0.35 - + family(families, "gamecult_web_lore_ops") * 0.15) - .min(1.0), - "Discord, auth, accounts, public site, or service boundaries" - ); - record!( - "sensory_salience", - (score(&scores, "interface_orientation") * 0.45 - + score(&scores, "aesthetic_appetite") * 0.45 - + score(&scores, "runtime_proximity") * 0.15) - .min(1.0), - "motion, visuals, rendered outputs, scenes, or UI organisms" - ); - record!( - "editorial_restraint", - (score(&scores, "content_canon_bias") * 0.6 + score(&scores, "source_fidelity") * 0.25) - .min(1.0), - "canon/source discipline under prose pressure" - ); - record!( - "speech_pressure", - (score(&scores, "social_surface") * 0.45 + score(&scores, "interface_orientation") * 0.2) - .min(1.0), - "public speech or user-facing surfaces" - ); - record!( - "novelty_hunger", - (score(&scores, "experimental_heat") * 0.55 + score(&scores, "aesthetic_appetite") * 0.25) - .min(1.0), - "experimental and aesthetic exploration pressure" - ); - record!( - "guardedness", - (score(&scores, "boundary_severity") * 0.45 - + score(&scores, "actuation_risk") * 0.35 - + score(&scores, "contract_strictness") * 0.2) - .min(1.0), - "authority and mutation risk demand caution" - ); - record!( - "rumination_bias", - (score(&scores, "state_hygiene") * 0.4 - + score(&scores, "consolidation_drive") * 0.25 - + score(&scores, "content_canon_bias") * 0.15) - .min(1.0), - "state hygiene and consolidation favor distillation before action" - ); - record!( - "initiative_drive", - (score(&scores, "production_pressure") * 0.35 - + score(&scores, "temporal_pressure") * 0.25 - + score(&scores, "experimental_heat") * 0.2) - .min(1.0), - "work pressure and experiment heat increase heartbeat readiness" - ); - record!( - "mood_lability", - (score(&scores, "churn_spiral_risk") * 0.35 - + score(&scores, "temporal_pressure") * 0.25 - + score(&scores, "actuation_risk") * 0.2) - .min(1.0), - "risk, urgency, and churn make reactions swing harder" - ); - let temporal = (score(&scores, "production_pressure") * 0.45 - + family(families, "service_product_app") * 0.35 - + score(&scores, "runtime_proximity") * 0.2) - .min(1.0); - record!( - "temporal_pressure", - temporal, - "service, runtime, queue, or live-provider timing pressure" - ); - scores -} - -fn set_score( - scores: &mut BTreeMap, - evidence: &mut BTreeMap>, - axis: &str, - value: f64, - reason: &str, -) { - scores.insert(axis.to_string(), round3(value.clamp(0.0, 1.0))); - evidence - .entry(axis.to_string()) - .or_default() - .push(reason.to_string()); -} - -fn score(scores: &BTreeMap, axis: &str) -> f64 { - scores.get(axis).copied().unwrap_or(0.0) -} - -fn class_count(inventory: &RepoInventory, class: &str) -> usize { - inventory.path_classes.get(class).copied().unwrap_or(0) -} - -fn presence(count: usize) -> f64 { - if count == 0 { - 0.0 - } else { - (count as f64 / 8.0).min(1.0) - } -} - -fn touch_ratio(count: usize, total: f64) -> f64 { - (count as f64 / total).min(1.0) -} - -fn keyword(history: &RepoHistoryMetrics, class: &str, commits: f64) -> f64 { - (*history.keyword_hits.get(class).unwrap_or(&0) as f64 / commits).min(1.0) -} - -fn family(families: &[String], family: &str) -> f64 { - families.iter().any(|candidate| candidate == family) as u8 as f64 -} - -fn burstiness(history: &RepoHistoryMetrics) -> f64 { - if history.sampled_commits == 0 { - return 0.0; - } - let active_days = history.active_days.max(1) as f64; - ((history.sampled_commits as f64 / active_days) / 12.0).min(1.0) -} - -fn deletion_pressure(history: &RepoHistoryMetrics) -> f64 { - let total = (history.insertions + history.deletions).max(1) as f64; - (history.deletions as f64 / total).min(1.0) -} - -fn insertion_pressure(history: &RepoHistoryMetrics) -> f64 { - let total = (history.insertions + history.deletions).max(1) as f64; - (history.insertions as f64 / total).min(1.0) -} - -fn large_churn(history: &RepoHistoryMetrics) -> f64 { - if history.sampled_commits == 0 { - return 0.0; - } - ((history.changed_files as f64 / history.sampled_commits as f64) / 18.0).min(1.0) -} - -fn reduce_reports(reports: &[RepoTerrainReport]) -> Vec { - reports.iter().map(reduce_report).collect() -} - -fn reduce_report(report: &RepoTerrainReport) -> RepoPersonalityProfile { - let family_weights = source_family_weights(report); - let axis_confidence = report - .axis_scores - .keys() - .map(|axis| (axis.clone(), report.confidence)) - .collect(); - let dominant_pressures = top_axes(&report.axis_scores, 6); - let risk_pressures = risk_axes(&report.axis_scores); - let role_modulations = ROLES - .iter() - .map(|role| role_projection(report, role)) - .collect(); - RepoPersonalityProfile { - schema_version: PROFILE_SCHEMA_VERSION.to_string(), - repo_id: report.repo_id.clone(), - summary: summary_for(report, &dominant_pressures), - source_family_weights: family_weights, - axis_scores: report.axis_scores.clone(), - axis_confidence, - dominant_pressures, - risk_pressures, - role_modulations, - } -} - -fn source_family_weights(report: &RepoTerrainReport) -> BTreeMap { - let mut weights = BTreeMap::new(); - let count = report.source_families.len().max(1) as f64; - for family in &report.source_families { - weights.insert(family.clone(), round3(1.0 / count)); - } - weights -} - -fn role_projection(report: &RepoTerrainReport, role_id: &str) -> RolePersonalityProjection { - let axes = &report.axis_scores; - let mut trait_deltas = BTreeMap::new(); - let mut heartbeat_deltas = BTreeMap::new(); - let mut mood = BTreeMap::new(); - let relevant = role_axes(role_id); - for axis in relevant { - trait_deltas.insert(axis.to_string(), round3((score(axes, axis) - 0.5) * 0.6)); - } - heartbeat_deltas.insert( - "initiativeSpeedDelta".to_string(), - round3((role_axis_average(axes, &["initiative_drive", "production_pressure"]) - 0.5) * 0.4), - ); - heartbeat_deltas.insert( - "cooldownMultiplierDelta".to_string(), - round3((role_axis_average(axes, &["guardedness", "rumination_bias"]) - 0.5) * 0.3), - ); - mood.insert( - "anxiety".to_string(), - round3(role_axis_average( - axes, - &["actuation_risk", "churn_spiral_risk", "boundary_severity"], - )), - ); - mood.insert( - "curiosity".to_string(), - round3(role_axis_average( - axes, - &["novelty_hunger", "experimental_heat", "source_fidelity"], - )), - ); - mood.insert( - "urgency".to_string(), - round3(role_axis_average( - axes, - &["production_pressure", "temporal_pressure"], - )), - ); - let role_label = role_display(role_id); - RolePersonalityProjection { - schema_version: ROLE_PROJECTION_SCHEMA_VERSION.to_string(), - projection_id: format!("{}::{}", report.repo_id, role_id), - repo_id: report.repo_id.clone(), - role_id: role_id.to_string(), - trait_deltas, - heartbeat_deltas, - default_mood_pressure: mood, - semantic_memory_candidates: vec![format!( - "{role_label} should treat {} as a repo with dominant pressures: {}.", - report.name, - top_axes(&report.axis_scores, 3).join(", ") - )], - goal_candidates: vec![format!( - "Adapt {role_label} behavior to {} without storing project facts in role memory.", - report.name - )], - value_candidates: vec![role_value_candidate(role_id).to_string()], - private_note_candidates: vec![format!( - "Projection is deterministic and confidence-scored at {:.2}; Self must review before mutation.", - report.confidence - )], - reason: format!( - "Role projection from repo terrain, commit history, and persisted doctrine for {}.", - report.name - ), - evidence_refs: report - .axis_evidence - .iter() - .take(6) - .map(|(axis, reasons)| format!("{axis}: {}", reasons.join("; "))) - .collect(), - } -} - -fn role_axes(role_id: &str) -> &'static [&'static str] { - match role_id { - "coordinator" => &[ - "boundary_severity", - "contract_strictness", - "state_hygiene", - "churn_spiral_risk", - "production_pressure", - ], - "Persona" => &[ - "social_surface", - "interface_orientation", - "sensory_salience", - "speech_pressure", - "editorial_restraint", - ], - "imagination" => &[ - "experimental_heat", - "aesthetic_appetite", - "content_canon_bias", - "novelty_hunger", - "churn_spiral_risk", - ], - "research" => &[ - "source_fidelity", - "protocol_intolerance", - "runtime_proximity", - "novelty_hunger", - "verification_environment_need", - ], - "modeling" => &[ - "runtime_proximity", - "contract_strictness", - "state_hygiene", - "content_canon_bias", - "source_fidelity", - ], - "implementation" => &[ - "production_pressure", - "actuation_risk", - "contract_strictness", - "consolidation_drive", - "churn_spiral_risk", - ], - "verification" => &[ - "evidence_appetite", - "verification_environment_need", - "actuation_risk", - "interface_orientation", - "content_canon_bias", - ], - "reorientation" => &[ - "state_hygiene", - "burstiness", - "rumination_bias", - "mood_lability", - "temporal_pressure", - ], - _ => &["state_hygiene"], - } -} - -fn role_axis_average(axes: &BTreeMap, names: &[&str]) -> f64 { - if names.is_empty() { - return 0.0; - } - names.iter().map(|name| score(axes, name)).sum::() / names.len() as f64 -} - -fn role_display(role_id: &str) -> &'static str { - match role_id { - "coordinator" => "Self", - "Persona" => "Persona", - "imagination" => "Imagination", - "research" => "Eyes", - "modeling" => "Modeling", - "implementation" => "Hands", - "verification" => "Soul", - _ => "Lane", - } -} - -fn role_value_candidate(role_id: &str) -> &'static str { - match role_id { - "coordinator" => { - "Coordinate through typed authority and challenge pattern-completion theater." - } - "Persona" => { - "Surface state through the public mouth without turning internals into chat endpoints." - } - "imagination" => { - "Turn future-shape pressure into drafts and plans, not accidental active objectives." - } - "research" => "Find existing truth before invention.", - "modeling" => "Build source-grounded maps before Hands cuts.", - "implementation" => "Leave reviewable diffs or explicit failure artifacts.", - "verification" => "Demand receipts from the environment that owns the claim.", - "reorientation" => "Bank continuity before pressure turns memory into ash.", - _ => "Serve the repo through typed state and reviewable evidence.", - } -} - -fn summary_for(report: &RepoTerrainReport, dominant: &[String]) -> String { - format!( - "{} projects as {} with dominant pressures: {}.", - report.name, - report.source_families.join(" + "), - dominant.join(", ") - ) -} - -fn top_axes(scores: &BTreeMap, limit: usize) -> Vec { - let mut items: Vec<_> = scores.iter().collect(); - items.sort_by(|a, b| b.1.partial_cmp(a.1).unwrap_or(std::cmp::Ordering::Equal)); - items - .into_iter() - .take(limit) - .map(|(axis, value)| format!("{axis}:{value:.2}")) - .collect() -} - -fn risk_axes(scores: &BTreeMap) -> Vec { - [ - "actuation_risk", - "churn_spiral_risk", - "boundary_severity", - "mood_lability", - ] - .iter() - .filter_map(|axis| { - let value = score(scores, axis); - (value >= 0.45).then(|| format!("{axis}:{value:.2}")) - }) - .collect() -} - -fn confidence_for(inventory: &RepoInventory, history: &RepoHistoryMetrics) -> f64 { - let mut confidence: f64 = 0.35; - if !inventory.instruction_surfaces.is_empty() { - confidence += 0.15; - } - if !inventory.state_surfaces.is_empty() { - confidence += 0.15; - } - if history.sampled_commits > 0 { - confidence += 0.15; - } - if inventory.file_count > 10 { - confidence += 0.1; - } - if !inventory.test_surfaces.is_empty() || !inventory.runtime_surfaces.is_empty() { - confidence += 0.1; - } - round3(confidence.min(1.0)) -} - -fn warnings_for(inventory: &RepoInventory, history: &RepoHistoryMetrics) -> Vec { - let mut warnings = Vec::new(); - if inventory.instruction_surfaces.is_empty() { - warnings.push("No AGENTS.md or instruction surface found.".to_string()); - } - if history.sampled_commits == 0 { - warnings.push("No git history sample available.".to_string()); - } - if inventory.file_count == 0 { - warnings.push("No source files inventoried.".to_string()); - } - warnings -} - -fn git_remote_urls(repo: &Path) -> Vec { - git_output(repo, &["remote", "-v"]) - .unwrap_or_default() - .lines() - .filter_map(|line| line.split_whitespace().nth(1)) - .map(str::to_string) - .collect::>() - .into_iter() - .collect() -} - -fn git_output(repo: &Path, args: &[&str]) -> Result { - let output = Command::new("git") - .current_dir(repo) - .args(args) - .output() - .with_context(|| format!("failed to run git {}", args.join(" ")))?; - if !output.status.success() { - return Err(anyhow!( - "git {} failed: {}", - args.join(" "), - String::from_utf8_lossy(&output.stderr) - )); - } - Ok(String::from_utf8_lossy(&output.stdout).to_string()) -} - -fn top_counts(counts: BTreeMap, limit: usize) -> Vec { - let mut items: Vec<_> = counts.into_iter().collect(); - items.sort_by_key(|(_, count)| std::cmp::Reverse(*count)); - items - .into_iter() - .take(limit) - .map(|(label, count)| RepoSignalCount { label, count }) - .collect() -} - -fn report_summary(report: &RepoTerrainReport) -> Value { - json!({ - "repoId": report.repo_id, - "name": report.name, - "path": report.path, - "families": report.source_families, - "confidence": report.confidence, - "dominantAxes": top_axes(&report.axis_scores, 5), - "warnings": report.warnings, - }) -} - -fn profile_summary(profile: &RepoPersonalityProfile) -> Value { - json!({ - "repoId": profile.repo_id, - "summary": profile.summary, - "dominantPressures": profile.dominant_pressures, - "riskPressures": profile.risk_pressures, - "roleProjectionCount": profile.role_modulations.len(), - }) -} - -fn trajectory_summary(trajectory: &RepoTrajectoryReport) -> Value { - json!({ - "repoId": trajectory.repo_id, - "summary": trajectory.trajectory_summary, - "selfImage": trajectory.self_image, - "directionalPressures": trajectory.directional_pressures, - "implicitGoalCandidates": trajectory.implicit_goal_candidates, - "antiGoalCandidates": trajectory.anti_goal_candidates, - "confidence": trajectory.confidence, - "warnings": trajectory.warnings, - }) -} - -fn report_agent_input(report: &RepoTerrainReport) -> Value { - json!({ - "schemaVersion": report.schema_version, - "repoId": report.repo_id, - "name": report.name, - "path": report.path, - "remoteUrls": report.remote_urls, - "sourceFamilies": report.source_families, - "languages": report.languages, - "statesurfaces": report.state_surfaces, - "instructionsurfaces": report.instruction_surfaces, - "testsurfaces": report.test_surfaces, - "runtimesurfaces": report.runtime_surfaces, - "historyMetrics": report.history_metrics, - "axisScores": report.axis_scores, - "axisEvidence": report.axis_evidence, - "confidence": report.confidence, - "warnings": report.warnings, - }) -} - -fn profile_agent_input(profile: &RepoPersonalityProfile) -> Value { - json!({ - "schemaVersion": profile.schema_version, - "repoId": profile.repo_id, - "summary": profile.summary, - "sourceFamilyWeights": profile.source_family_weights, - "axisScores": profile.axis_scores, - "axisConfidence": profile.axis_confidence, - "dominantPressures": profile.dominant_pressures, - "riskPressures": profile.risk_pressures, - }) -} - -fn trajectory_agent_input(trajectory: &RepoTrajectoryReport) -> Value { - json!({ - "schemaVersion": trajectory.schema_version, - "repoId": trajectory.repo_id, - "trajectorySummary": trajectory.trajectory_summary, - "selfImage": trajectory.self_image, - "earlyCommitMessages": trajectory.early_commit_messages, - "recentCommitMessages": trajectory.recent_commit_messages, - "trajectorySources": trajectory.trajectory_sources, - "themeScores": trajectory.theme_scores, - "directionalPressures": trajectory.directional_pressures, - "implicitGoalCandidates": trajectory.implicit_goal_candidates, - "antiGoalCandidates": trajectory.anti_goal_candidates, - "tensions": trajectory.tensions, - "confidence": trajectory.confidence, - "warnings": trajectory.warnings, - }) -} - -fn role_projection_agent_input(projection: &RolePersonalityProjection) -> Value { - json!({ - "schemaVersion": projection.schema_version, - "projectionId": projection.projection_id, - "repoId": projection.repo_id, - "roleId": projection.role_id, - "traitDeltas": projection.trait_deltas, - "heartbeatDeltas": projection.heartbeat_deltas, - "defaultMoodPressure": projection.default_mood_pressure, - "semanticMemoryCandidates": projection.semantic_memory_candidates, - "goalCandidates": projection.goal_candidates, - "valueCandidates": projection.value_candidates, - "privateNoteCandidates": projection.private_note_candidates, - "reason": projection.reason, - "evidenceRefs": projection.evidence_refs, - }) -} - -fn render_scout_markdown( - reports: &[RepoTerrainReport], - profiles: &[RepoPersonalityProfile], - trajectories: &[RepoTrajectoryReport], -) -> String { - let mut out = String::new(); - out.push_str("# Repo Personality Scout\n\n"); - out.push_str(&format!("- Reports: {}\n", reports.len())); - out.push_str(&format!("- Profiles: {}\n\n", profiles.len())); - for report in reports { - out.push_str(&format!("## {}\n\n", report.name)); - out.push_str(&format!("- Path: `{}`\n", report.path)); - out.push_str(&format!( - "- Families: {}\n", - report.source_families.join(", ") - )); - out.push_str(&format!("- Confidence: {:.2}\n", report.confidence)); - out.push_str(&format!( - "- Dominant axes: {}\n\n", - top_axes(&report.axis_scores, 5).join(", ") - )); - if let Some(trajectory) = trajectories - .iter() - .find(|trajectory| trajectory.repo_id == report.repo_id) - { - out.push_str(&format!( - "- Trajectory: {}\n\n", - trajectory.trajectory_summary - )); - } - } - out -} - -fn render_project_markdown( - report: &RepoTerrainReport, - profile: &RepoPersonalityProfile, - trajectory: &RepoTrajectoryReport, -) -> String { - let mut out = String::new(); - out.push_str(&format!( - "# Repo Personality Projection: {}\n\n", - report.name - )); - out.push_str(&format!("{}\n\n", profile.summary)); - out.push_str("## Trajectory\n\n"); - out.push_str(&format!("{}\n\n", trajectory.trajectory_summary)); - out.push_str(&format!("- Self-image: {}\n", trajectory.self_image)); - out.push_str(&format!( - "- Directional pressures: {}\n\n", - trajectory.directional_pressures.join(", ") - )); - out.push_str("## Risk Pressures\n\n"); - for risk in &profile.risk_pressures { - out.push_str(&format!("- {risk}\n")); - } - out.push_str("\n## Role Projections\n\n"); - for projection in &profile.role_modulations { - out.push_str(&format!("### {}\n\n", role_display(&projection.role_id))); - out.push_str(&format!("{}\n\n", projection.reason)); - out.push_str(&format!( - "- Trait deltas: `{}`\n", - serde_json::to_string(&projection.trait_deltas).unwrap_or_default() - )); - out.push_str(&format!( - "- Mood: `{}`\n\n", - serde_json::to_string(&projection.default_mood_pressure).unwrap_or_default() - )); - } - out -} - -fn render_agent_packet_markdown( - report: &RepoTerrainReport, - profile: &RepoPersonalityProfile, - trajectory: &RepoTrajectoryReport, - role_projection_count: usize, -) -> String { - let mut out = String::new(); - out.push_str(&format!( - "# Repo Personality Distiller Packet: {}\n\n", - report.name - )); - out.push_str("This packet is input to the Repo Personality Distiller specialist. It is not accepted truth.\n\n"); - out.push_str("## Prompt\n\n"); - out.push_str("See `repo-personality-distiller-prompt.md`.\n\n"); - out.push_str("## Profile\n\n"); - out.push_str(&format!("- Repo id: `{}`\n", profile.repo_id)); - out.push_str(&format!("- Summary: {}\n", profile.summary)); - out.push_str(&format!( - "- Dominant pressures: {}\n", - profile.dominant_pressures.join(", ") - )); - out.push_str(&format!( - "- Risk pressures: {}\n", - profile.risk_pressures.join(", ") - )); - out.push_str(&format!( - "- Role projections: {}\n\n", - role_projection_count - )); - out.push_str("## Trajectory Context\n\n"); - out.push_str(&format!("- Self-image: {}\n", trajectory.self_image)); - out.push_str(&format!( - "- Directional pressures: {}\n", - trajectory.directional_pressures.join(", ") - )); - out.push_str(&format!( - "- Implicit goal candidates: {}\n\n", - trajectory.implicit_goal_candidates.join(" | ") - )); - out.push_str("## Guardrails\n\n"); - out.push_str("- The distiller petitions Self; it does not mutate memory.\n"); - out.push_str("- The distiller is birth-only; after accepted initialization, personality drift belongs to heartbeat, mood, rumination, sleep consolidation, lived evidence, and reviewed selfPatch.\n"); - out.push_str("- Repo facts stay out of selfPatch and remain in terrain, graph, planning, evidence, or checkpoint artifacts.\n"); - out.push_str("- Personality pressure must be role-local, subtle, bounded, and reviewable.\n"); - out.push_str( - "- Low confidence becomes a request for more terrain, not a permanent personality brand.\n", - ); - out -} - -fn render_memory_packet_markdown( - report: &RepoTerrainReport, - profile: &RepoPersonalityProfile, - trajectory: &RepoTrajectoryReport, - memory_source_count: usize, -) -> String { - let mut out = String::new(); - out.push_str(&format!( - "# Repo Memory Distiller Packet: {}\n\n", - report.name - )); - out.push_str("This packet is input to the role-specific Repo Memory Distiller lanes. It is not accepted truth.\n\n"); - out.push_str("## Prompt\n\n"); - out.push_str("See `repo-memory-distiller-prompt.md`.\n\n"); - out.push_str("## Source Budget\n\n"); - out.push_str(&format!( - "- Memory sources: {} bounded excerpts\n", - memory_source_count - )); - out.push_str(&format!( - "- Max files: {}, max bytes/file: {}, max total bytes: {}\n\n", - MEMORY_SOURCE_MAX_FILES, MEMORY_SOURCE_MAX_BYTES_PER_FILE, MEMORY_SOURCE_MAX_TOTAL_BYTES - )); - out.push_str("## Birth Contract\n\n"); - out.push_str("- This distiller initializes memory once for a newborn Epiphany.\n"); - out.push_str("- Personality remains the job of `repo-personality-distiller`; memory remains repo knowledge, doctrine, maps, invariants, risks, and practices.\n"); - out.push_str("- Each role receives a mission-specific filter and should write only role-relevant Ghostlight-shaped memory candidates.\n"); - out.push_str("- The distiller petitions Self; it does not mutate memory.\n\n"); - out.push_str("## Role Lanes\n\n"); - for role_id in ROLES { - out.push_str(&format!( - "- {}: {}\n", - role_display(role_id), - memory_role_filter(role_id) - )); - } - out.push_str("\n## Profile Context\n\n"); - out.push_str(&format!("- Repo id: `{}`\n", profile.repo_id)); - out.push_str(&format!("- Summary: {}\n", profile.summary)); - out.push_str(&format!( - "- Dominant pressures: {}\n", - profile.dominant_pressures.join(", ") - )); - out.push_str(&format!( - "- Trajectory: {}\n", - trajectory.trajectory_summary - )); - out -} - -fn render_trajectory_packet_markdown( - report: &RepoTerrainReport, - profile: &RepoPersonalityProfile, - trajectory: &RepoTrajectoryReport, - role_projection_count: usize, -) -> String { - let mut out = String::new(); - out.push_str(&format!( - "# Repo Trajectory Distiller Packet: {}\n\n", - report.name - )); - out.push_str("This packet is input to the Repo Trajectory Distiller specialist. It is not accepted truth.\n\n"); - out.push_str("## Prompt\n\n"); - out.push_str("See `repo-trajectory-distiller-prompt.md`.\n\n"); - out.push_str("## Trajectory Readout\n\n"); - out.push_str(&format!("- Summary: {}\n", trajectory.trajectory_summary)); - out.push_str(&format!("- Self-image: {}\n", trajectory.self_image)); - out.push_str(&format!( - "- Directional pressures: {}\n", - trajectory.directional_pressures.join(", ") - )); - out.push_str(&format!( - "- Implicit goal candidates: {}\n", - trajectory.implicit_goal_candidates.join(" | ") - )); - out.push_str(&format!( - "- Anti-goal candidates: {}\n", - trajectory.anti_goal_candidates.join(" | ") - )); - out.push_str(&format!( - "- Theme count: {}, role projections: {}\n\n", - trajectory.theme_scores.len(), - role_projection_count - )); - out.push_str("## Guardrails\n\n"); - out.push_str("- Trajectory is directional bias, not active objective truth.\n"); - out.push_str("- The distiller petitions Self; it does not mutate memory or planning.\n"); - out.push_str("- Commit history is evidence of motion, not proof of permanent identity.\n"); - out.push_str("- Keep self-image, implicit goals, and anti-goals subtle enough that later lived drift can still matter.\n"); - out.push_str( - "- Do not stuff raw commit logs, file lists, or active backlog truth into selfPatch.\n\n", - ); - out.push_str("## Profile Context\n\n"); - out.push_str(&format!("- Repo id: `{}`\n", profile.repo_id)); - out.push_str(&format!("- Summary: {}\n", profile.summary)); - out.push_str(&format!( - "- Dominant pressures: {}\n", - profile.dominant_pressures.join(", ") - )); - out -} - -fn render_startup_markdown(result: &Value) -> String { - let mut out = String::new(); - out.push_str("# Repo Initialization Startup\n\n"); - out.push_str(&format!( - "- Repo id: `{}`\n", - result["repoId"].as_str().unwrap_or("unknown") - )); - out.push_str(&format!( - "- Action: `{}`\n", - result["action"].as_str().unwrap_or("unknown") - )); - out.push_str(&format!( - "- Requires review: `{}`\n\n", - result["requiresReview"].as_bool().unwrap_or(false) - )); - out.push_str("## Accepted Birth Records\n\n"); - if let Some(kinds) = result["acceptedKinds"].as_array() { - if kinds.is_empty() { - out.push_str("- none\n"); - } else { - for kind in kinds { - out.push_str(&format!("- `{}`\n", kind.as_str().unwrap_or("unknown"))); - } - } - } - out.push_str("\n## Generated Packets\n\n"); - if let Some(packets) = result["generatedPackets"].as_array() { - if packets.is_empty() { - out.push_str("- none\n"); - } else { - for packet in packets { - out.push_str(&format!( - "- `{}`: `{}`\n", - packet["kind"].as_str().unwrap_or("unknown"), - packet["packetPath"].as_str().unwrap_or("missing") - )); - } - } - } - out.push_str("\n## Next Safe Move\n\n"); - out.push_str(result["nextSafeMove"].as_str().unwrap_or("")); - out.push('\n'); - out -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn write_text(path: &Path, value: &str) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - fs::write(path, value).with_context(|| format!("failed to write {}", path.display())) -} - -fn repo_id(path: &Path) -> String { - path.file_name() - .and_then(|value| value.to_str()) - .unwrap_or("repo") - .chars() - .map(|ch| { - if ch.is_ascii_alphanumeric() { - ch.to_ascii_lowercase() - } else { - '-' - } - }) - .collect::() - .trim_matches('-') - .to_string() -} - -fn same_path(left: &str, right: &Path) -> bool { - PathBuf::from(left) - .canonicalize() - .ok() - .zip(right.canonicalize().ok()) - .is_some_and(|(left, right)| left == right) -} - -fn slash(path: &Path) -> String { - path.to_string_lossy().replace('\\', "/") -} - -fn round3(value: f64) -> f64 { - (value * 1000.0).round() / 1000.0 -} - -fn print_usage() { - eprintln!( - "usage: epiphany-repo-personality ...\n\ - scout --root --artifact-dir [--max-repos ]\n\ - project --repo --baseline --artifact-dir \n\ - agent-packet --store --artifact-dir [--repo-id ]\n\ - trajectory-packet --store --artifact-dir [--repo-id ]\n\ - memory-packet --store --artifact-dir [--repo-id ]\n\ - startup --repo --baseline --artifact-dir --init-store \n\ - accept-init --init-store --packet --kind [--accepted-by ] [--summary ] [--result ] [--agent-store ] [--apply-self-patches ] [--apply-trait-seeds ] [--heartbeat-store ] [--apply-heartbeat-seeds ]\n\ - status --store " - ); -} diff --git a/epiphany-core/src/bin/epiphany-repo-pr-request-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-pr-request-family-smoke.rs deleted file mode 100644 index 1b3d4597..00000000 --- a/epiphany-core/src/bin/epiphany-repo-pr-request-family-smoke.rs +++ /dev/null @@ -1,443 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-pr-request-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo PR Request Family Smoke\n\nThis repository proves branch-local PR request cargo without GitHub PR authority.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - git(["add", "README.md"], &repo)?; - git(["commit", "-m", "Seed repo PR request smoke body"], &repo)?; - git(["switch", "-c", "epiphany/repo-pr-request-family"], &repo)?; - - let item = "repo-pr-request-family"; - let target_path = ".epiphany/pr-requests/repo-pr-request-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-pr-request-family-smoke", - "--topic", - "repo-pr-request-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-pr-request-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Ask Bifrost and GitHub to prepare a review PR packet without granting push, PR, or merge authority.", - "--candidate-action-ref", - "candidate-action://repo-pr-request-family/pr-packet", - "--candidate-action-ref", - "candidate-action://repo-pr-request-family/github-review", - "--public-discussion-ref", - "epiphany-global/persona-collaboration/repo-pr-request-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-pr-request", - "--model-ref", - "repo-pr-request-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - let close_path = repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")); - let close = read_json(&close_path)?; - let request_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.pr_request", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - require_text( - &request_text, - "schema_version = \"epiphany.repo_pr_request.v0\"", - )?; - require_text(&request_text, "safe_action_family = \"repo.pr_request\"")?; - require_text(&request_text, "[request]")?; - require_text(&request_text, "status = \"awaiting-pr-publication-review\"")?; - require_text(&request_text, "routing_owner = \"Self\"")?; - require_text(&request_text, "publication_owner = \"Bifrost\"")?; - require_text(&request_text, "execution_owner = \"Hands\"")?; - require_text(&request_text, "provider = \"GitHub\"")?; - require_text(&request_text, "provider_receipt_required = true")?; - require_text( - &request_text, - "requested_effect = \"open-or-update-review-pr-from-redacted-proof-and-maintainer-context\"", - )?; - require_text(&request_text, "maintainer_review_request_ref = ")?; - require_text(&request_text, "publication_request_ref = ")?; - require_text(&request_text, "sync_request_ref = ")?; - require_text( - &request_text, - "pr_scope = \"branch diff, redacted public proof, maintainer review, Bifrost credit context, and upstream-main follow-up\"", - )?; - require_text(&request_text, "[antecedents]")?; - require_text(&request_text, "closure_review_required = true")?; - require_text(&request_text, "soul_verdict_required = true")?; - require_text(&request_text, "mind_commit_required = true")?; - require_text(&request_text, "public_proof_required = true")?; - require_text(&request_text, "maintainer_review_required = true")?; - require_text(&request_text, "bifrost_publication_required = true")?; - require_text(&request_text, "credit_ledger_required = true")?; - require_text(&request_text, "[required_receipts]")?; - require_text( - &request_text, - "closure_review = \"epiphany.repo_work_closure_review.v0\"", - )?; - require_text( - &request_text, - "soul_verdict = \"epiphany.soul.verification_verdict\"", - )?; - require_text( - &request_text, - "mind_commit = \"epiphany.mind.state_commit_receipt\"", - )?; - require_text( - &request_text, - "public_proof = \"epiphany.repo_work_public_proof_bundle.v0\"", - )?; - require_text( - &request_text, - "maintainer_review = \"gamecult.maintainer.review_receipt.v0\"", - )?; - require_text( - &request_text, - "bifrost_publication = \"gamecult.bifrost.public_proof_publication_receipt.v0\"", - )?; - require_text( - &request_text, - "credit_ledger = \"gamecult.bifrost.credit_receipt.v0\"", - )?; - require_text( - &request_text, - "pr_publication = \"gamecult.github.pull_request_publication_receipt.v0\"", - )?; - require_text(&request_text, "[pr_packet]")?; - require_text(&request_text, "base_ref = \"origin/main\"")?; - require_text(&request_text, "requires_branch_name = true")?; - require_text(&request_text, "requires_title = true")?; - require_text(&request_text, "requires_body = true")?; - require_text(&request_text, "requires_changed_path_list = true")?; - require_text(&request_text, "requires_public_proof_ref = true")?; - require_text(&request_text, "requires_maintainer_review_ref = true")?; - require_text(&request_text, "requires_credit_ref = true")?; - require_text( - &request_text, - "requires_private_state_redaction_check = true", - )?; - require_text(&request_text, "github_pr_authorized = false")?; - require_text(&request_text, "branch_push_authorized = false")?; - require_text(&request_text, "merge_authorized = false")?; - require_text(&request_text, "publication_authorized = false")?; - require_text(&request_text, "upstream_sync_authorized = false")?; - require_text(&request_text, "hands_action_authorized = false")?; - require_text(&request_text, "cross_body_mutation_authorized = false")?; - require_text(&request_text, "maintainer_review_required = true")?; - require_text(&request_text, "bifrost_publication_gate_required = true")?; - require_text(&request_text, "hands_execution_required = true")?; - require_text(&request_text, "github_provider_receipt_required = true")?; - require_text(&request_text, "private_state_exposed = false")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_pr_request_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "awaitingPrPublicationReview": true, - "githubPrAuthorized": false, - "branchPushAuthorized": false, - "mergeAuthorized": false, - "publicationAuthorized": false, - "upstreamSyncAuthorized": false, - "handsActionAuthorized": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!("expected PR request text to contain {needle:?}")) - } -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .with_context(|| format!("missing value for {flag}")) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} diff --git a/epiphany-core/src/bin/epiphany-repo-publication-request-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-publication-request-family-smoke.rs deleted file mode 100644 index 0906822e..00000000 --- a/epiphany-core/src/bin/epiphany-repo-publication-request-family-smoke.rs +++ /dev/null @@ -1,435 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-publication-request-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo Publication Request Family Smoke\n\nThis repository proves branch-local Hands publication-request requests.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - git(["add", "README.md"], &repo)?; - git( - ["commit", "-m", "Seed repo publication request smoke body"], - &repo, - )?; - git( - ["switch", "-c", "epiphany/repo-publication-request-family"], - &repo, - )?; - - let item = "repo-publication-request-family"; - let target_path = ".epiphany/publication-requests/repo-publication-request-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-publication-request-family-smoke", - "--topic", - "repo-publication-request-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-publication-request-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Ask Bifrost to review redacted public proof after Soul and Mind receipts exist.", - "--candidate-action-ref", - "candidate-action://repo-publication-request-family/bifrost-review", - "--candidate-action-ref", - "candidate-action://repo-publication-request-family/redacted-proof-publication", - "--public-discussion-ref", - "epiphany-global/persona-collaboration/repo-publication-request-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-publication-request", - "--model-ref", - "repo-publication-request-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - let close_path = repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")); - let close = read_json(&close_path)?; - let request_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.publication_request", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - require_text( - &request_text, - "schema_version = \"epiphany.repo_publication_request.v0\"", - )?; - require_text( - &request_text, - "safe_action_family = \"repo.publication_request\"", - )?; - require_text(&request_text, "[request]")?; - require_text(&request_text, "status = \"awaiting-bifrost-review\"")?; - require_text(&request_text, "requested_owner = \"Bifrost\"")?; - require_text( - &request_text, - "requested_effect = \"publish-redacted-proof-and-route-maintainer-review\"", - )?; - require_text(&request_text, "verification_request_ref = ")?; - require_text( - &request_text, - "publication_scope = \"redacted public proof, maintainer review, credit ledger, and upstream-main sync proof\"", - )?; - require_text(&request_text, "[antecedents]")?; - require_text(&request_text, "closure_review_required = true")?; - require_text(&request_text, "soul_verdict_required = true")?; - require_text(&request_text, "mind_commit_required = true")?; - require_text(&request_text, "public_proof_export_required = true")?; - require_text(&request_text, "private_state_redaction_required = true")?; - require_text(&request_text, "[required_receipts]")?; - require_text( - &request_text, - "closure_review = \"epiphany.repo_work_closure_review.v0\"", - )?; - require_text( - &request_text, - "soul_verdict = \"epiphany.soul.verification_verdict\"", - )?; - require_text( - &request_text, - "mind_commit = \"epiphany.mind.state_commit_receipt\"", - )?; - require_text( - &request_text, - "public_proof = \"epiphany.repo_work_public_proof_bundle.v0\"", - )?; - require_text( - &request_text, - "bifrost_publication = \"gamecult.bifrost.public_proof_publication_receipt.v0\"", - )?; - require_text( - &request_text, - "github_publication = \"gamecult.github.publication_receipt.v0\"", - )?; - require_text( - &request_text, - "credit_ledger = \"gamecult.bifrost.credit_receipt.v0\"", - )?; - require_text( - &request_text, - "upstream_sync = \"epiphany.repo_work_upstream_main_sync.v0\"", - )?; - require_text(&request_text, "[public_export]")?; - require_text(&request_text, "redacted_only = true")?; - require_text(&request_text, "raw_receipts_allowed = false")?; - require_text(&request_text, "private_paths_allowed = false")?; - require_text(&request_text, "worker_thought_allowed = false")?; - require_text(&request_text, "operator_context_allowed = false")?; - require_text(&request_text, "credit_required = true")?; - require_text(&request_text, "bifrost_publication_authorized = false")?; - require_text(&request_text, "github_publication_authorized = false")?; - require_text(&request_text, "credit_ledger_authorized = false")?; - require_text(&request_text, "merge_authorized = false")?; - require_text(&request_text, "upstream_sync_authorized = false")?; - require_text(&request_text, "hands_action_authorized = false")?; - require_text(&request_text, "cross_body_mutation_authorized = false")?; - require_text(&request_text, "private_state_exposed = false")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_publication_request_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "awaitingBifrostReview": true, - "bifrostPublicationAuthorized": false, - "githubPublicationAuthorized": false, - "creditLedgerAuthorized": false, - "upstreamSyncAuthorized": false, - "handsActionAuthorized": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!( - "expected publication request text to contain {needle:?}" - )) - } -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .with_context(|| format!("missing value for {flag}")) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} diff --git a/epiphany-core/src/bin/epiphany-repo-readiness-review-request-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-readiness-review-request-family-smoke.rs deleted file mode 100644 index 60394096..00000000 --- a/epiphany-core/src/bin/epiphany-repo-readiness-review-request-family-smoke.rs +++ /dev/null @@ -1,520 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-readiness-review-request-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo Readiness Review Request Family Smoke\n\nThis repository proves branch-local MVP readiness review request cargo without publication, merge, deployment, service, or state authority.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - git(["add", "README.md"], &repo)?; - git( - [ - "commit", - "-m", - "Seed repo readiness review request smoke body", - ], - &repo, - )?; - git( - [ - "switch", - "-c", - "epiphany/repo-readiness-review-request-family", - ], - &repo, - )?; - - let item = "repo-readiness-review-request-family"; - let target_path = ".epiphany/readiness-reviews/repo-readiness-review-request-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-readiness-review-request-family-smoke", - "--topic", - "repo-readiness-review-request-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-readiness-review-request-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Ask Maintainer, Soul, Mind, and Bifrost to review a redacted repo-swarm MVP proof bundle without granting publication or deployment authority.", - "--candidate-action-ref", - "candidate-action://repo-readiness-review-request-family/readiness-packet", - "--candidate-action-ref", - "candidate-action://repo-readiness-review-request-family/proof-bundle-review", - "--public-discussion-ref", - "epiphany-global/persona-collaboration/repo-readiness-review-request-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-readiness-review-request", - "--model-ref", - "repo-readiness-review-request-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - let close_path = repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")); - let close = read_json(&close_path)?; - let request_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.readiness_review_request", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - require_text( - &request_text, - "schema_version = \"epiphany.repo_readiness_review_request.v0\"", - )?; - require_text( - &request_text, - "safe_action_family = \"repo.readiness_review_request\"", - )?; - require_text(&request_text, "[request]")?; - require_text(&request_text, "status = \"awaiting-mvp-readiness-review\"")?; - require_text(&request_text, "routing_owner = \"Self\"")?; - require_text( - &request_text, - "required_reviewers = [\"Maintainer\", \"Soul\", \"Mind\", \"Bifrost\"]", - )?; - require_text(&request_text, "readiness_approval_owner = \"Maintainer\"")?; - require_text( - &request_text, - "requested_effect = \"review-redacted-repo-swarm-mvp-proof-bundle\"", - )?; - require_text( - &request_text, - "readiness_scope = \"fresh repo init, online swarm, Persona intake, Imagination planning, Self queue-run, Hands branch work, Soul closure, Modeling map update, Mind admission, Bifrost public proof, upstream-main sync, Idunn lifecycle, global tool directory, and private-state redaction\"", - )?; - require_text( - &request_text, - "maintainer_readiness_acceptance_required = true", - )?; - require_text(&request_text, "[antecedents]")?; - require_text(&request_text, "repo_init_required = true")?; - require_text(&request_text, "swarm_online_required = true")?; - require_text(&request_text, "persona_intake_required = true")?; - require_text(&request_text, "imagination_plan_required = true")?; - require_text(&request_text, "self_queue_run_required = true")?; - require_text(&request_text, "hands_commit_required = true")?; - require_text(&request_text, "soul_closure_required = true")?; - require_text(&request_text, "modeling_map_update_required = true")?; - require_text(&request_text, "mind_admission_required = true")?; - require_text(&request_text, "public_proof_required = true")?; - require_text(&request_text, "bifrost_publication_required = true")?; - require_text(&request_text, "upstream_main_sync_required = true")?; - require_text(&request_text, "idunn_lifecycle_readiness_required = true")?; - require_text(&request_text, "tool_directory_readiness_required = true")?; - require_text(&request_text, "private_state_redaction_required = true")?; - require_text(&request_text, "[required_receipts]")?; - require_text( - &request_text, - "repo_init = \"epiphany.repo_swarm_init_receipt.v0\"", - )?; - require_text( - &request_text, - "swarm_online = \"epiphany.repo_swarm_online_receipt.v0\"", - )?; - require_text( - &request_text, - "persona_speech_audit = \"epiphany.persona_speech_audit.v0\"", - )?; - require_text( - &request_text, - "imagination_action_items = \"epiphany.repo_work_imagination_action_items_receipt.v0\"", - )?; - require_text( - &request_text, - "queue_run = \"epiphany.repo_work_queue_run_receipt.v0\"", - )?; - require_text( - &request_text, - "hands_commit = \"epiphany.hands.commit_receipt\"", - )?; - require_text( - &request_text, - "closure_review = \"epiphany.repo_work_closure_review.v0\"", - )?; - require_text( - &request_text, - "soul_verdict = \"epiphany.soul.verification_verdict\"", - )?; - require_text( - &request_text, - "mind_commit = \"epiphany.mind.state_commit_receipt\"", - )?; - require_text( - &request_text, - "public_proof = \"epiphany.repo_work_public_proof_bundle.v0\"", - )?; - require_text( - &request_text, - "modeling_map = \"epiphany.repo_work_map_entry.v0\"", - )?; - require_text( - &request_text, - "bifrost_publication = \"gamecult.bifrost.public_proof_publication_receipt.v0\"", - )?; - require_text( - &request_text, - "upstream_sync = \"epiphany.repo_work_upstream_sync_receipt.v0\"", - )?; - require_text( - &request_text, - "idunn_lifecycle = \"epiphany.cultmesh.daemon_service_lifecycle_receipt.v0\"", - )?; - require_text( - &request_text, - "tool_directory = \"epiphany.cultmesh.daemon_tool_directory_readback.v0\"", - )?; - require_text(&request_text, "[readiness_packet]")?; - require_text(&request_text, "requires_proof_bundle_ref = true")?; - require_text(&request_text, "requires_changed_path_list = true")?; - require_text(&request_text, "requires_branch_name = true")?; - require_text(&request_text, "requires_upstream_main_ref = true")?; - require_text(&request_text, "requires_public_proof_ref = true")?; - require_text(&request_text, "requires_bifrost_ledger_ref = true")?; - require_text(&request_text, "requires_idunn_lifecycle_ref = true")?; - require_text(&request_text, "requires_tool_directory_ref = true")?; - require_text(&request_text, "requires_redaction_report = true")?; - require_text(&request_text, "requires_reviewer_identity = true")?; - require_text( - &request_text, - "allowed_verdicts = [\"ready\", \"ready-with-caveats\", \"not-ready\", \"needs-human-review\"]", - )?; - require_text(&request_text, "readiness_approval_authorized = false")?; - require_text(&request_text, "durable_state_commit_authorized = false")?; - require_text(&request_text, "publication_authorized = false")?; - require_text(&request_text, "bifrost_publication_authorized = false")?; - require_text(&request_text, "github_pr_authorized = false")?; - require_text(&request_text, "merge_authorized = false")?; - require_text(&request_text, "upstream_sync_authorized = false")?; - require_text(&request_text, "deployment_authority = false")?; - require_text(&request_text, "service_lifecycle_authority = false")?; - require_text(&request_text, "hands_action_authorized = false")?; - require_text(&request_text, "cross_body_mutation_authorized = false")?; - require_text(&request_text, "private_verse_rummaging = false")?; - require_text(&request_text, "maintainer_review_required = true")?; - require_text(&request_text, "private_state_exposed = false")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_readiness_review_request_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "awaitingMvpReadinessReview": true, - "repoInitRequired": true, - "swarmOnlineRequired": true, - "personaIntakeRequired": true, - "imaginationPlanRequired": true, - "selfQueueRunRequired": true, - "handsCommitRequired": true, - "soulClosureRequired": true, - "modelingMapUpdateRequired": true, - "mindAdmissionRequired": true, - "bifrostPublicationRequired": true, - "upstreamMainSyncRequired": true, - "idunnLifecycleRequired": true, - "toolDirectoryRequired": true, - "readinessApprovalAuthorized": false, - "durableStateCommitAuthorized": false, - "githubPrAuthorized": false, - "mergeAuthorized": false, - "publicationAuthorized": false, - "deploymentAuthority": false, - "serviceLifecycleAuthority": false, - "upstreamSyncAuthorized": false, - "handsActionAuthorized": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!( - "expected readiness review request text to contain {needle:?}" - )) - } -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .with_context(|| format!("missing value for {flag}")) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} diff --git a/epiphany-core/src/bin/epiphany-repo-scheduling-request-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-scheduling-request-family-smoke.rs deleted file mode 100644 index ed393973..00000000 --- a/epiphany-core/src/bin/epiphany-repo-scheduling-request-family-smoke.rs +++ /dev/null @@ -1,403 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-scheduling-request-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo Scheduling Request Family Smoke\n\nThis repository proves branch-local Self scheduling requests.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - git(["add", "README.md"], &repo)?; - git( - ["commit", "-m", "Seed repo scheduling request smoke body"], - &repo, - )?; - git( - ["switch", "-c", "epiphany/repo-scheduling-request-family"], - &repo, - )?; - - let item = "repo-scheduling-request-family"; - let target_path = ".epiphany/scheduling-requests/repo-scheduling-request-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-scheduling-request-family-smoke", - "--topic", - "repo-scheduling-request-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-scheduling-request-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Ask Self to prepare queue scheduling only after Mind adopts the Objective Draft.", - "--candidate-action-ref", - "candidate-action://repo-scheduling-request-family/queue-after-adoption", - "--candidate-action-ref", - "candidate-action://repo-scheduling-request-family/bounded-self-pulse", - "--public-discussion-ref", - "epiphany-global/persona-collaboration/repo-scheduling-request-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-scheduling-request", - "--model-ref", - "repo-scheduling-request-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - let close_path = repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")); - let close = read_json(&close_path)?; - let request_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.scheduling_request", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - require_text( - &request_text, - "schema_version = \"epiphany.repo_scheduling_request.v0\"", - )?; - require_text( - &request_text, - "safe_action_family = \"repo.scheduling_request\"", - )?; - require_text(&request_text, "[request]")?; - require_text(&request_text, "status = \"awaiting-mind-adoption\"")?; - require_text(&request_text, "requested_scheduler = \"Self\"")?; - require_text(&request_text, "mind_adoption_receipt_required = true")?; - require_text(&request_text, "self_may_schedule_after_mind_only = true")?; - require_text(&request_text, "queue_run_allowed_after_adoption = true")?; - require_text(&request_text, "[queue]")?; - require_text(&request_text, "target_gate = \"repo-work-queue\"")?; - require_text( - &request_text, - "preferred_next_safe_family = \"repo.task_card\"", - )?; - require_text(&request_text, "max_items_per_pulse = 1")?; - require_text(&request_text, "requires_epiphany_branch = true")?; - require_text( - &request_text, - "publish_blocker = \"bifrost-publication-missing\"", - )?; - require_text(&request_text, "[required_receipts]")?; - require_text( - &request_text, - "mind_review = \"epiphany.mind.gateway_review\"", - )?; - require_text( - &request_text, - "mind_commit = \"epiphany.mind.state_commit_receipt\"", - )?; - require_text( - &request_text, - "expected_self_receipt = \"epiphany.repo_work_queue_selection.v0\"", - )?; - require_text(&request_text, "self_scheduling_authorized = false")?; - require_text(&request_text, "queue_mutation_authorized = false")?; - require_text(&request_text, "hands_action_authorized = false")?; - require_text(&request_text, "cross_body_mutation_authorized = false")?; - require_text(&request_text, "private_state_exposed = false")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_work_scheduling_request_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "awaitingMindAdoption": true, - "queueMutationAuthorized": false, - "selfSchedulingAuthorized": false, - "handsActionAuthorized": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!( - "expected scheduling request text to contain {needle:?}" - )) - } -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .with_context(|| format!("missing value for {flag}")) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} diff --git a/epiphany-core/src/bin/epiphany-repo-secret-policy-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-secret-policy-family-smoke.rs deleted file mode 100644 index 615c32e0..00000000 --- a/epiphany-core/src/bin/epiphany-repo-secret-policy-family-smoke.rs +++ /dev/null @@ -1,416 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-secret-policy-request-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo Secret Policy Request Family Smoke\n\nThis repository proves branch-local secret policy request cargo without secret access.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - fs::write( - repo.join(".env.example"), - "API_TOKEN=example-only-no-secret-value\n", - ) - .with_context(|| format!("failed to seed {}", repo.join(".env.example").display()))?; - git(["add", "README.md", ".env.example"], &repo)?; - git( - ["commit", "-m", "Seed repo secret policy smoke body"], - &repo, - )?; - git( - ["switch", "-c", "epiphany/repo-secret-policy-request-family"], - &repo, - )?; - - let item = "repo-secret-policy-request-family"; - let target_path = - ".epiphany/security/secret-policy-requests/repo-secret-policy-request-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-secret-policy-request-family-smoke", - "--topic", - "repo-secret-policy-request-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-secret-policy-request-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Ask for a reviewed repo secret and write-permission policy without exposing secret values.", - "--candidate-action-ref", - "candidate-action://repo-secret-policy-request-family/security-review", - "--public-discussion-ref", - "epiphany-global/persona-collaboration/repo-secret-policy-request-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-secret-policy-request", - "--model-ref", - "repo-secret-policy-request-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - - let close = read_json( - &repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")), - )?; - let request_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - let env_example_text = fs::read_to_string(repo.join(".env.example")) - .with_context(|| format!("failed to read {}", repo.join(".env.example").display()))?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.secret_policy_request", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - require_text( - &request_text, - "schema_version = \"epiphany.repo_secret_policy_request.v0\"", - )?; - require_text( - &request_text, - "safe_action_family = \"repo.secret_policy_request\"", - )?; - require_text(&request_text, "status = \"awaiting-security-review\"")?; - require_text(&request_text, "routing_owner = \"Self\"")?; - require_text( - &request_text, - "requires_secret_inventory_without_values = true", - )?; - require_text(&request_text, "requires_write_permission_scope = true")?; - require_text( - &request_text, - "requires_public_private_export_boundary = true", - )?; - require_text(&request_text, "source_grounding_required = true")?; - require_text(&request_text, "bifrost_publication_review_required = true")?; - require_text( - &request_text, - "source_grounding = \"epiphany.eyes.evidence_packet\"", - )?; - require_text( - &request_text, - "requires_secret_locations_without_values = true", - )?; - require_text(&request_text, "requires_credential_owner = true")?; - require_text(&request_text, "requires_write_scope_matrix = true")?; - require_text( - &request_text, - "requires_public_export_redaction_rules = true", - )?; - require_text(&request_text, "requires_deployment_authority_owner = true")?; - require_text(&request_text, "requires_incident_rollback_plan = true")?; - require_text(&request_text, "direct_secret_access_authority = false")?; - require_text(&request_text, "secret_value_materialization = false")?; - require_text(&request_text, "write_permission_authority = false")?; - require_text(&request_text, "deployment_authority = false")?; - require_text(&request_text, "publication_authorized = false")?; - require_text(&request_text, "service_lifecycle_authority = false")?; - require_text(&request_text, "cross_body_mutation_authorized = false")?; - require_text(&request_text, "private_verse_rummaging = false")?; - require_text(&request_text, "private_state_exposed = false")?; - require_text(&env_example_text, "example-only-no-secret-value")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_secret_policy_request_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "awaitingSecurityReview": true, - "secretAccessAuthorized": false, - "writePermissionAuthorized": false, - "deploymentAuthority": false, - "publicationAuthorized": false, - "serviceLifecycleAuthority": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!( - "expected secret policy request text to contain {needle:?}" - )) - } -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .with_context(|| format!("missing value for {flag}")) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} diff --git a/epiphany-core/src/bin/epiphany-repo-sync-request-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-sync-request-family-smoke.rs deleted file mode 100644 index d0dbb77a..00000000 --- a/epiphany-core/src/bin/epiphany-repo-sync-request-family-smoke.rs +++ /dev/null @@ -1,418 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-sync-request-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo Sync Request Family Smoke\n\nThis repository proves branch-local Hands sync-request requests.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - git(["add", "README.md"], &repo)?; - git(["commit", "-m", "Seed repo sync request smoke body"], &repo)?; - git(["switch", "-c", "epiphany/repo-sync-request-family"], &repo)?; - - let item = "repo-sync-request-family"; - let target_path = ".epiphany/sync-requests/repo-sync-request-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-sync-request-family-smoke", - "--topic", - "repo-sync-request-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-sync-request-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Ask Bifrost to prove the published commit is contained by upstream main after maintainer review.", - "--candidate-action-ref", - "candidate-action://repo-sync-request-family/upstream-main-proof", - "--candidate-action-ref", - "candidate-action://repo-sync-request-family/ancestry-proof", - "--public-discussion-ref", - "epiphany-global/persona-collaboration/repo-sync-request-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-sync-request", - "--model-ref", - "repo-sync-request-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - let close_path = repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")); - let close = read_json(&close_path)?; - let request_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.sync_request", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - require_text( - &request_text, - "schema_version = \"epiphany.repo_sync_request.v0\"", - )?; - require_text(&request_text, "safe_action_family = \"repo.sync_request\"")?; - require_text(&request_text, "[request]")?; - require_text(&request_text, "status = \"awaiting-upstream-main-proof\"")?; - require_text(&request_text, "proof_owner = \"Bifrost\"")?; - require_text( - &request_text, - "requested_effect = \"prove-published-commit-contained-by-upstream-main\"", - )?; - require_text(&request_text, "publication_request_ref = ")?; - require_text( - &request_text, - "sync_scope = \"prove maintainer-reviewed published commit is contained by upstream main\"", - )?; - require_text(&request_text, "[antecedents]")?; - require_text(&request_text, "publication_receipt_required = true")?; - require_text(&request_text, "github_publication_required = true")?; - require_text(&request_text, "maintainer_review_required = true")?; - require_text(&request_text, "credit_ledger_required = true")?; - require_text(&request_text, "public_proof_required = true")?; - require_text(&request_text, "[required_receipts]")?; - require_text( - &request_text, - "bifrost_publication = \"gamecult.bifrost.public_proof_publication_receipt.v0\"", - )?; - require_text( - &request_text, - "github_publication = \"gamecult.github.publication_receipt.v0\"", - )?; - require_text( - &request_text, - "maintainer_review = \"gamecult.maintainer.review_receipt.v0\"", - )?; - require_text( - &request_text, - "credit_ledger = \"gamecult.bifrost.credit_receipt.v0\"", - )?; - require_text( - &request_text, - "upstream_sync = \"epiphany.repo_work_upstream_main_sync.v0\"", - )?; - require_text( - &request_text, - "ancestry_proof = \"git.merge_base_is_ancestor\"", - )?; - require_text(&request_text, "[sync_proof]")?; - require_text(&request_text, "target_ref = \"origin/main\"")?; - require_text(&request_text, "requires_fetch_before_check = true")?; - require_text(&request_text, "requires_merge_base_ancestor_check = true")?; - require_text(&request_text, "requires_clean_public_proof_readback = true")?; - require_text(&request_text, "records_upstream_main_sha = true")?; - require_text(&request_text, "merge_authorized = false")?; - require_text(&request_text, "push_authorized = false")?; - require_text(&request_text, "upstream_sync_authorized = false")?; - require_text(&request_text, "github_publication_authorized = false")?; - require_text(&request_text, "credit_ledger_authorized = false")?; - require_text(&request_text, "hands_action_authorized = false")?; - require_text(&request_text, "cross_body_mutation_authorized = false")?; - require_text(&request_text, "bifrost_upstream_proof_required = true")?; - require_text(&request_text, "maintainer_review_receipt_required = true")?; - require_text(&request_text, "private_state_exposed = false")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_sync_request_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "awaitingUpstreamMainProof": true, - "mergeAuthorized": false, - "pushAuthorized": false, - "githubPublicationAuthorized": false, - "creditLedgerAuthorized": false, - "upstreamSyncAuthorized": false, - "handsActionAuthorized": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!("expected sync request text to contain {needle:?}")) - } -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .with_context(|| format!("missing value for {flag}")) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} diff --git a/epiphany-core/src/bin/epiphany-repo-tool-request-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-tool-request-family-smoke.rs deleted file mode 100644 index 1bf364ad..00000000 --- a/epiphany-core/src/bin/epiphany-repo-tool-request-family-smoke.rs +++ /dev/null @@ -1,401 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-tool-request-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo Tool Request Family Smoke\n\nThis repository proves branch-local Epiphany daemon tool request cargo.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - git(["add", "README.md"], &repo)?; - git(["commit", "-m", "Seed repo tool request smoke body"], &repo)?; - git(["switch", "-c", "epiphany/repo-tool-request-family"], &repo)?; - - let item = "repo-tool-request-family"; - let target_path = ".epiphany/tool-requests/repo-tool-request-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-tool-request-family-smoke", - "--topic", - "repo-tool-request-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-tool-request-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Request a daemon-hosted tool through CultMesh typed intent and receipt contracts without granting direct execution authority.", - "--candidate-action-ref", - "candidate-action://repo-tool-request-family/daemon-intent", - "--public-discussion-ref", - "eve://epiphany/persona/repo-tool-request-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-tool-request", - "--model-ref", - "repo-tool-request-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - let close_path = repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")); - let close = read_json(&close_path)?; - let request_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.tool_request", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - - require_text( - &request_text, - "schema_version = \"epiphany.repo_tool_request.v0\"", - )?; - require_text(&request_text, "safe_action_family = \"repo.tool_request\"")?; - require_text(&request_text, "[request]")?; - require_text(&request_text, "routing_owner = \"Self\"")?; - require_text(&request_text, "pressure_source = \"Persona\"")?; - require_text( - &request_text, - "target_directory = \"gamecult-local/daemon-tool-directory\"", - )?; - require_text( - &request_text, - "target_capability = \"daemon-tool-capability:selected-by-review\"", - )?; - require_text(&request_text, "operation = \"submitTypedToolIntent\"")?; - require_text(&request_text, "[cultmesh]")?; - require_text( - &request_text, - "intent_contract = \"epiphany.cultmesh.daemon_tool_invocation_intent.v0\"", - )?; - require_text( - &request_text, - "receipt_contract = \"epiphany.cultmesh.daemon_tool_invocation_receipt.v0\"", - )?; - require_text(&request_text, "host_daemon_owns_execution = true")?; - require_text(&request_text, "requester_owns_execution = false")?; - require_text(&request_text, "requires_host_liveness_ready = true")?; - require_text(&request_text, "requires_cultmesh_receipts = true")?; - require_text(&request_text, "[odin]")?; - require_text(&request_text, "discoverable = true")?; - require_text(&request_text, "preserves_provider_ownership = true")?; - require_text(&request_text, "private_verse_passthrough = false")?; - require_text(&request_text, "[authority]")?; - require_text(&request_text, "direct_tool_execution = false")?; - require_text(&request_text, "arbitrary_shell_authority = false")?; - require_text(&request_text, "hands_action_authority = false")?; - require_text(&request_text, "state_commit_authority = false")?; - require_text(&request_text, "publication_authority = false")?; - require_text(&request_text, "service_lifecycle_authority = false")?; - require_text(&request_text, "cross_body_mutation_authority = false")?; - require_text(&request_text, "private_verse_rummaging = false")?; - require_text(&request_text, "private_state_exposed = false")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_work_tool_request_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "hasCultMeshContracts": true, - "hostDaemonOwnsExecution": true, - "odinPreservesProviderOwnership": true, - "directToolExecution": false, - "arbitraryShellAuthority": false, - "handsActionAuthority": false, - "stateCommitAuthority": false, - "publicationAuthority": false, - "serviceLifecycleAuthority": false, - "crossBodyMutationAuthority": false, - "privateVerseRummaging": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!("expected tool request text to contain {needle:?}")) - } -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .with_context(|| format!("missing value for {flag}")) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} diff --git a/epiphany-core/src/bin/epiphany-repo-verification-request-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-verification-request-family-smoke.rs deleted file mode 100644 index 213e9b3d..00000000 --- a/epiphany-core/src/bin/epiphany-repo-verification-request-family-smoke.rs +++ /dev/null @@ -1,431 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-verification-request-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo Verification Request Family Smoke\n\nThis repository proves branch-local Hands verification-request requests.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - git(["add", "README.md"], &repo)?; - git( - ["commit", "-m", "Seed repo verification request smoke body"], - &repo, - )?; - git( - ["switch", "-c", "epiphany/repo-verification-request-family"], - &repo, - )?; - - let item = "repo-verification-request-family"; - let target_path = ".epiphany/verification-requests/repo-verification-request-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-verification-request-family-smoke", - "--topic", - "repo-verification-request-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-verification-request-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Ask Soul to review required Hands evidence after a bounded branch-local work order exists.", - "--candidate-action-ref", - "candidate-action://repo-verification-request-family/soul-review", - "--candidate-action-ref", - "candidate-action://repo-verification-request-family/hands-evidence-check", - "--public-discussion-ref", - "epiphany-global/persona-collaboration/repo-verification-request-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-verification-request", - "--model-ref", - "repo-verification-request-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - let close_path = repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")); - let close = read_json(&close_path)?; - let request_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.verification_request", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - require_text( - &request_text, - "schema_version = \"epiphany.repo_verification_request.v0\"", - )?; - require_text( - &request_text, - "safe_action_family = \"repo.verification_request\"", - )?; - require_text(&request_text, "[request]")?; - require_text(&request_text, "status = \"awaiting-soul-review\"")?; - require_text(&request_text, "requested_owner = \"Soul\"")?; - require_text( - &request_text, - "requested_effect = \"verify-branch-local-hands-work\"", - )?; - require_text(&request_text, "work_order_ref = ")?; - require_text( - &request_text, - "verification_scope = \"declared changed paths, Hands receipts, and visible repo diff\"", - )?; - require_text(&request_text, "[antecedents]")?; - require_text(&request_text, "substrate_gate_required = true")?; - require_text(&request_text, "hands_intent_required = true")?; - require_text(&request_text, "hands_review_required = true")?; - require_text(&request_text, "hands_patch_required = true")?; - require_text(&request_text, "hands_command_required = true")?; - require_text(&request_text, "hands_commit_required = true")?; - require_text(&request_text, "work_order_required = true")?; - require_text(&request_text, "[required_receipts]")?; - require_text( - &request_text, - "hands_patch = \"epiphany.hands.patch_receipt\"", - )?; - require_text( - &request_text, - "hands_command = \"epiphany.hands.command_receipt\"", - )?; - require_text( - &request_text, - "hands_commit = \"epiphany.hands.commit_receipt\"", - )?; - require_text( - &request_text, - "soul_verdict = \"epiphany.soul.verification_verdict\"", - )?; - require_text( - &request_text, - "closure_review = \"epiphany.repo_work_closure_review.v0\"", - )?; - require_text( - &request_text, - "mind_review = \"epiphany.mind.gateway_review\"", - )?; - require_text( - &request_text, - "mind_commit = \"epiphany.mind.state_commit_receipt\"", - )?; - require_text(&request_text, "[checks]")?; - require_text(&request_text, "\"declared-paths-match-commit\"")?; - require_text(&request_text, "\"hands-receipts-present\"")?; - require_text(&request_text, "\"visible-diff-supports-summary\"")?; - require_text(&request_text, "\"no-private-state-exposure\"")?; - require_text(&request_text, "\"publication-remains-gated\"")?; - require_text(&request_text, "failure_blocks_mind_admission = true")?; - require_text(&request_text, "soul_verdict_authorized = false")?; - require_text(&request_text, "state_commit_authorized = false")?; - require_text(&request_text, "hands_action_authorized = false")?; - require_text(&request_text, "rerun_authorized = false")?; - require_text(&request_text, "publication_authorized = false")?; - require_text(&request_text, "cross_body_mutation_authorized = false")?; - require_text(&request_text, "private_state_exposed = false")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_verification_request_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "awaitingSoulReview": true, - "soulVerdictAuthorized": false, - "stateCommitAuthorized": false, - "rerunAuthorized": false, - "handsActionAuthorized": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!( - "expected verification request text to contain {needle:?}" - )) - } -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .with_context(|| format!("missing value for {flag}")) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} diff --git a/epiphany-core/src/bin/epiphany-repo-work-order-family-smoke.rs b/epiphany-core/src/bin/epiphany-repo-work-order-family-smoke.rs deleted file mode 100644 index 4cf01a72..00000000 --- a/epiphany-core/src/bin/epiphany-repo-work-order-family-smoke.rs +++ /dev/null @@ -1,414 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-work-order-family-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - git(["init"], &repo)?; - git( - ["config", "user.email", "epiphany-smoke@example.invalid"], - &repo, - )?; - git(["config", "user.name", "Epiphany Smoke"], &repo)?; - fs::write( - repo.join("README.md"), - "# Repo Work Order Family Smoke\n\nThis repository proves branch-local Hands work-order requests.\n", - ) - .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; - git(["add", "README.md"], &repo)?; - git(["commit", "-m", "Seed repo work order smoke body"], &repo)?; - git(["switch", "-c", "epiphany/repo-work-order-family"], &repo)?; - - let item = "repo-work-order-family"; - let target_path = ".epiphany/work-orders/repo-work-order-family.toml"; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - cargo_json( - &manifest, - "epiphany-repo", - &[ - "init", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--swarm-id", - "repo-work-order-family-smoke", - "--topic", - "repo-work-order-family", - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-swarm", - &[ - "online", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--runtime-id", - "repo-work-order-family-smoke", - "--local-verse-store", - path_str(&local_verse)?, - ], - &root, - )?; - cargo_json( - &manifest, - "epiphany-work", - &[ - "accept", - "--workspace", - path_str(&repo)?, - "--from", - "persona", - "--item", - item, - "--summary", - "Ask Hands to review a bounded branch-local work order after Mind adoption and Self queue selection.", - "--candidate-action-ref", - "candidate-action://repo-work-order-family/hands-review", - "--candidate-action-ref", - "candidate-action://repo-work-order-family/bounded-branch-work", - "--public-discussion-ref", - "epiphany-global/persona-collaboration/repo-work-order-family", - ], - &root, - )?; - let plan = cargo_json( - &manifest, - "epiphany-work", - &[ - "derive-plan", - "--workspace", - path_str(&repo)?, - "--item", - item, - "--action-family", - "repo-work-order", - "--model-ref", - "repo-work-order-family-smoke-imagination-v1", - "--model-authored", - ], - &root, - )?; - for _ in 0..4 { - cargo_json( - &manifest, - "epiphany-work", - &[ - "tick", - "--workspace", - path_str(&repo)?, - "--epiphany-root", - path_str(&root)?, - "--item", - item, - "--cooldown-seconds", - "0", - ], - &root, - )?; - } - let close_path = repo - .join(".epiphany") - .join("work") - .join(format!("work-close-{item}.json")); - let close = read_json(&close_path)?; - let request_text = fs::read_to_string(repo.join(target_path)) - .with_context(|| format!("failed to read {}", repo.join(target_path).display()))?; - - require_eq( - &plan, - &["derivation", "safeActionFamily"], - "repo.work_order", - )?; - require_eq(&close, &["status"], "closed")?; - require_eq(&close, &["soul", "verdict"], "passed")?; - require_eq( - &close, - &["closureReview", "familyAssertions", "status"], - "passed", - )?; - require_bool( - &close, - &["closureReview", "sourceGrounding", "pathScopeMatched"], - true, - )?; - require_bool(&close, &["privateStateExposed"], false)?; - require_text( - &request_text, - "schema_version = \"epiphany.repo_work_order.v0\"", - )?; - require_text(&request_text, "safe_action_family = \"repo.work_order\"")?; - require_text(&request_text, "[work_order]")?; - require_text(&request_text, "status = \"awaiting-hands-review\"")?; - require_text(&request_text, "requested_owner = \"Hands\"")?; - require_text( - &request_text, - "requested_effect = \"branch-local-implementation\"", - )?; - require_text(&request_text, "[antecedents]")?; - require_text(&request_text, "objective_draft_ref = ")?; - require_text(&request_text, "adoption_request_ref = ")?; - require_text(&request_text, "scheduling_request_ref = ")?; - require_text(&request_text, "mind_adoption_required = true")?; - require_text(&request_text, "self_queue_selection_required = true")?; - require_text(&request_text, "[required_receipts]")?; - require_text( - &request_text, - "substrate_gate = \"epiphany.substrate_gate.grant\"", - )?; - require_text( - &request_text, - "hands_intent = \"epiphany.hands.action_intent\"", - )?; - require_text( - &request_text, - "hands_review = \"epiphany.hands.action_review\"", - )?; - require_text( - &request_text, - "hands_patch = \"epiphany.hands.patch_receipt\"", - )?; - require_text( - &request_text, - "hands_command = \"epiphany.hands.command_receipt\"", - )?; - require_text( - &request_text, - "hands_commit = \"epiphany.hands.commit_receipt\"", - )?; - require_text( - &request_text, - "soul_verdict = \"epiphany.soul.verification_verdict\"", - )?; - require_text( - &request_text, - "mind_commit = \"epiphany.mind.state_commit_receipt\"", - )?; - require_text(&request_text, "[scope]")?; - require_text(&request_text, "branch_required = true")?; - require_text(&request_text, "allowed_branch_prefix = \"epiphany/\"")?; - require_text(&request_text, "max_changed_paths = 3")?; - require_text(&request_text, "requires_reviewable_diff = true")?; - require_text(&request_text, "substrate_access_authorized = false")?; - require_text(&request_text, "hands_action_authorized = false")?; - require_text(&request_text, "shell_command_authorized = false")?; - require_text(&request_text, "commit_authorized = false")?; - require_text(&request_text, "cross_body_mutation_authorized = false")?; - require_text(&request_text, "private_state_exposed = false")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_work_order_family_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "branch": git_output(["branch", "--show-current"], &repo)?, - "item": item, - "safeActionFamily": plan["derivation"]["safeActionFamily"], - "targetPath": target_path, - "closeStatus": close["status"], - "soulVerdict": close["soul"]["verdict"], - "familyAssertionsStatus": close["closureReview"]["familyAssertions"]["status"], - "pathScopeMatched": close["closureReview"]["sourceGrounding"]["pathScopeMatched"], - "awaitingHandsReview": true, - "substrateAccessAuthorized": false, - "shellCommandAuthorized": false, - "commitAuthorized": false, - "handsActionAuthorized": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn git(args: [&str; N], cwd: &Path) -> Result<()> { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(args: [&str; N], cwd: &Path) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run git in {}", cwd.display()))?; - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(anyhow!( - "git failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", - cwd.display(), - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn read_json(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - serde_json::from_slice(&bytes).with_context(|| format!("failed to parse {}", path.display())) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!("expected work order text to contain {needle:?}")) - } -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .with_context(|| format!("missing value for {flag}")) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} diff --git a/epiphany-core/src/bin/epiphany-repo-work-service-lifecycle-smoke.rs b/epiphany-core/src/bin/epiphany-repo-work-service-lifecycle-smoke.rs deleted file mode 100644 index 731f55d8..00000000 --- a/epiphany-core/src/bin/epiphany-repo-work-service-lifecycle-smoke.rs +++ /dev/null @@ -1,516 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use sha2::Digest; -use sha2::Sha256; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let args = Args::parse()?; - let result = run_smoke(args)?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct Args { - root: PathBuf, -} - -impl Args { - fn parse() -> Result { - let mut root = env::current_dir().context("failed to resolve current directory")?; - let mut args = env::args().skip(1).peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--root" => root = take_path(&mut args, "--root")?, - other => return Err(anyhow!("unexpected argument {other:?}")), - } - } - Ok(Self { root }) - } -} - -fn run_smoke(args: Args) -> Result { - let root = args - .root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.root.display()))?; - let smoke_root = root.join(".epiphany-smoke"); - let manifest = root.join("epiphany-core").join("Cargo.toml"); - if !manifest.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest.display() - )); - } - fs::create_dir_all(&smoke_root) - .with_context(|| format!("failed to create {}", smoke_root.display()))?; - let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); - let smoke_dir = smoke_root.join(format!("repo-work-service-lifecycle-{stamp}")); - fs::create_dir(&smoke_dir) - .with_context(|| format!("failed to claim fresh smoke dir {}", smoke_dir.display()))?; - - let repo = smoke_dir.join("repo-body"); - fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?; - run_command( - Command::new("git").arg("init").current_dir(&repo), - "git init", - )?; - let local_verse = repo.join(".epiphany").join("local-verse.ccmp"); - let runbook_path = smoke_dir.join("epiphany-repo-work-queue-runner.ps1"); - let service_id = "epiphany-repo-work-queue-runner"; - let scheduler_id = "epiphany-repo-work-queue-runner"; - let runtime_id = "epiphany-local"; - let daemon_id = "epiphany-daemon-self"; - let repo_work_runtime_id = "repo-work-service-lifecycle-smoke"; - let epiphany_work_bin = root - .join("epiphany-core") - .join("target") - .join("debug") - .join(if cfg!(windows) { - "epiphany-work.exe" - } else { - "epiphany-work" - }); - let service_args = vec![ - "queue-run".to_string(), - "--workspace".to_string(), - path_string(&repo)?, - "--epiphany-root".to_string(), - path_string(&root)?, - "--local-verse-store".to_string(), - path_string(&local_verse)?, - "--runtime-id".to_string(), - repo_work_runtime_id.to_string(), - "--max-items".to_string(), - "1".to_string(), - "--dry-run".to_string(), - ]; - let plan = daemon_supervisor( - &manifest, - &root, - &local_verse, - &[ - "service-plan", - "--store", - path_str(&local_verse)?, - "--runtime-id", - runtime_id, - "--daemon-id", - daemon_id, - "--scheduler-id", - scheduler_id, - "--service-id", - service_id, - "--service-command", - path_str(&epiphany_work_bin)?, - "--reason", - "Idunn-owned lifecycle artifact for Self-owned repo work queue-run pulse.", - "--cwd", - path_str(&root)?, - ], - &service_args, - )?; - let runbook = daemon_supervisor( - &manifest, - &root, - &local_verse, - &[ - "service-runbook", - "--store", - path_str(&local_verse)?, - "--runtime-id", - runtime_id, - "--daemon-id", - daemon_id, - "--scheduler-id", - scheduler_id, - "--service-id", - service_id, - "--service-command", - path_str(&epiphany_work_bin)?, - "--reason", - "Idunn-owned lifecycle artifact for Self-owned repo work queue-run pulse.", - "--cwd", - path_str(&root)?, - "--runbook-path", - path_str(&runbook_path)?, - ], - &service_args, - )?; - let runbook_receipts = receipt_directory(&manifest, &root, &local_verse, runtime_id)?; - let runbook_rows = rows(&runbook_receipts); - let runbook_row = runbook_rows.iter().find(|row| { - row.get("family").and_then(Value::as_str) == Some("service-lifecycle") - && row.get("serviceId").and_then(Value::as_str) == Some(service_id) - && row.get("serviceRoute").and_then(Value::as_str) - == Some("epiphany-repo-work-queue-runner::runbook") - }); - let Some(runbook_row) = runbook_row else { - return Err(anyhow!( - "receipt directory did not expose runbook service-lifecycle row for {service_id}" - )); - }; - require_eq(runbook_row, &["status"], "written")?; - require_eq(runbook_row, &["artifactStatus"], "present")?; - - let launch = daemon_supervisor( - &manifest, - &root, - &local_verse, - &[ - "service-launch", - "--store", - path_str(&local_verse)?, - "--runtime-id", - runtime_id, - "--daemon-id", - daemon_id, - "--scheduler-id", - scheduler_id, - "--service-id", - service_id, - "--service-command", - path_str(&epiphany_work_bin)?, - "--reason", - "Idunn-owned lifecycle launch proof for Self-owned repo work queue-run pulse.", - "--cwd", - path_str(&root)?, - "--wait-child", - ], - &service_args, - )?; - let launch_receipts = receipt_directory(&manifest, &root, &local_verse, runtime_id)?; - let launch_rows = rows(&launch_receipts); - let launch_row = launch_rows.iter().find(|row| { - row.get("family").and_then(Value::as_str) == Some("service-lifecycle") - && row.get("serviceId").and_then(Value::as_str) == Some(service_id) - && row.get("serviceRoute").and_then(Value::as_str) - == Some("epiphany-repo-work-queue-runner::launch") - }); - let Some(launch_row) = launch_row else { - return Err(anyhow!( - "receipt directory did not expose launch service-lifecycle row for {service_id}" - )); - }; - require_eq(launch_row, &["status"], "completed")?; - - let audit = daemon_supervisor( - &manifest, - &root, - &local_verse, - &[ - "repo-work-service-audit", - "--store", - path_str(&local_verse)?, - "--runtime-id", - runtime_id, - "--daemon-id", - daemon_id, - "--scheduler-id", - scheduler_id, - "--service-id", - service_id, - "--service-command", - path_str(&epiphany_work_bin)?, - "--reason", - "Idunn-owned readiness audit for Self-owned repo work queue-run pulse.", - "--cwd", - path_str(&root)?, - ], - &service_args, - )?; - - require_eq(&plan, &["status"], "planned")?; - require_eq(&runbook, &["status"], "written")?; - require_eq(&launch, &["status"], "completed")?; - require_eq(&audit, &["status"], "complete")?; - require_eq(&plan, &["serviceId"], service_id)?; - require_eq(&runbook, &["serviceId"], service_id)?; - require_eq(&launch, &["serviceId"], service_id)?; - require_eq(&audit, &["serviceId"], service_id)?; - require_bool(&plan, &["privateStateExposed"], false)?; - require_bool(&runbook, &["privateStateExposed"], false)?; - require_bool(&launch, &["privateStateExposed"], false)?; - require_bool(&audit, &["privateStateExposed"], false)?; - require_i64(&launch, &["exitCode"], 0)?; - require_i64(&audit, &["launchExitCode"], 0)?; - require_text_array_contains(&plan, &["args"], "queue-run")?; - require_text_array_contains(&plan, &["args"], "--dry-run")?; - require_text_array_contains(&plan, &["args"], path_str(&repo)?)?; - require_text_array_contains(&runbook, &["args"], "queue-run")?; - require_text_array_contains(&runbook, &["args"], "--dry-run")?; - require_eq(&audit, &["planStatus"], "planned")?; - require_eq(&audit, &["runbookStatus"], "written")?; - require_eq(&audit, &["runbookArtifactStatus"], "present")?; - require_eq(&audit, &["launchStatus"], "completed")?; - - let runbook_text = fs::read_to_string(&runbook_path) - .with_context(|| format!("failed to read {}", runbook_path.display()))?; - require_text( - &runbook_text, - "Generated by epiphany-daemon-supervisor service-runbook", - )?; - require_text(&runbook_text, "Start-Process")?; - require_text(&runbook_text, "-WindowStyle Hidden")?; - require_text(&runbook_text, "queue-run")?; - require_text(&runbook_text, "--dry-run")?; - let runbook_sha256 = sha256_file(&runbook_path)?; - - let audit_receipts = receipt_directory(&manifest, &root, &local_verse, runtime_id)?; - let audit_rows = rows(&audit_receipts); - let audit_row = audit_rows.iter().find(|row| { - row.get("family").and_then(Value::as_str) == Some("service-lifecycle") - && row.get("serviceId").and_then(Value::as_str) == Some(service_id) - && row.get("serviceRoute").and_then(Value::as_str) - == Some("epiphany-repo-work-queue-runner::repo-work-service-audit") - }); - let Some(audit_row) = audit_row else { - return Err(anyhow!( - "receipt directory did not expose audit service-lifecycle row for {service_id}" - )); - }; - require_eq(audit_row, &["status"], "complete")?; - - let summary = json!({ - "schemaVersion": "epiphany.repo_work_service_lifecycle_smoke.v0", - "status": "ok", - "smokeDir": smoke_dir, - "repo": repo, - "localVerseStore": local_verse, - "serviceId": service_id, - "schedulerId": scheduler_id, - "daemonId": daemon_id, - "planStatus": plan["status"], - "planReceiptId": plan["receiptId"], - "runbookStatus": runbook["status"], - "runbookReceiptId": runbook["receiptId"], - "runbookPath": runbook_path, - "runbookSha256": runbook_sha256, - "launchStatus": launch["status"], - "launchReceiptId": launch["receiptId"], - "launchExitCode": launch["exitCode"], - "auditStatus": audit["status"], - "auditReceiptId": audit["receiptId"], - "auditPlanStatus": audit["planStatus"], - "auditRunbookStatus": audit["runbookStatus"], - "auditRunbookArtifactStatus": audit["runbookArtifactStatus"], - "auditLaunchStatus": audit["launchStatus"], - "auditLaunchExitCode": audit["launchExitCode"], - "serviceCommand": plan["command"], - "serviceArgs": plan["args"], - "lifecycleOwner": "Idunn", - "hostedBody": "repo-work", - "mutatesServiceManager": false, - "launchesService": true, - "waitChild": true, - "requiresElevatedAuthority": false, - "privateStateExposed": false, - }); - write_json(&smoke_dir.join("summary.json"), &summary)?; - Ok(summary) -} - -fn daemon_supervisor( - manifest: &Path, - cwd: &Path, - _store: &Path, - base_args: &[&str], - service_args: &[String], -) -> Result { - let mut args = base_args - .iter() - .map(|arg| (*arg).to_string()) - .collect::>(); - for service_arg in service_args { - args.push("--service-arg".to_string()); - args.push(service_arg.clone()); - } - let arg_refs = args.iter().map(String::as_str).collect::>(); - cargo_json(manifest, "epiphany-daemon-supervisor", &arg_refs, cwd) -} - -fn cargo_json(manifest: &Path, bin: &str, args: &[&str], cwd: &Path) -> Result { - let output = Command::new("cargo") - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest) - .arg("--bin") - .arg(bin) - .arg("--") - .args(args) - .current_dir(cwd) - .output() - .with_context(|| format!("failed to run cargo bin {bin}"))?; - if !output.status.success() { - return Err(anyhow!( - "{bin} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout).with_context(|| { - format!( - "{bin} did not emit JSON on stdout:\n{}", - String::from_utf8_lossy(&output.stdout) - ) - }) -} - -fn receipt_directory( - manifest: &Path, - root: &Path, - local_verse: &Path, - runtime_id: &str, -) -> Result { - cargo_json( - manifest, - "epiphany-verse-query", - &[ - "receipt-directory", - "--store", - path_str(local_verse)?, - "--runtime-id", - runtime_id, - ], - root, - ) -} - -fn rows(value: &Value) -> Vec { - value - .get("rows") - .and_then(Value::as_array) - .cloned() - .unwrap_or_default() -} - -fn run_command(command: &mut Command, label: &str) -> Result<()> { - let output = command - .output() - .with_context(|| format!("failed to run {label}"))?; - if !output.status.success() { - return Err(anyhow!( - "{label} failed with status {:?}\nstdout:\n{}\nstderr:\n{}", - output.status.code(), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - Ok(()) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn sha256_file(path: &Path) -> Result { - let bytes = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?; - Ok(format!("{:x}", Sha256::digest(bytes))) -} - -fn require_eq(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_str) - .unwrap_or(""); - if actual == expected { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected:?}, got {actual:?}", - path.join(".") - )) - } -} - -fn require_bool(value: &Value, path: &[&str], expected: bool) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_bool); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_i64(value: &Value, path: &[&str], expected: i64) -> Result<()> { - let actual = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_i64); - if actual == Some(expected) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to be {expected}, got {:?}", - path.join("."), - actual - )) - } -} - -fn require_text_array_contains(value: &Value, path: &[&str], expected: &str) -> Result<()> { - let values = path - .iter() - .try_fold(value, |current, key| current.get(*key)) - .and_then(Value::as_array) - .ok_or_else(|| anyhow!("expected {} to be an array", path.join(".")))?; - if values.iter().any(|value| value.as_str() == Some(expected)) { - Ok(()) - } else { - Err(anyhow!( - "expected {} to contain {expected:?}, got {:?}", - path.join("."), - values - )) - } -} - -fn require_text(haystack: &str, needle: &str) -> Result<()> { - if haystack.contains(needle) { - Ok(()) - } else { - Err(anyhow!("expected text to contain {needle:?}")) - } -} - -fn take_path( - args: &mut std::iter::Peekable>, - flag: &str, -) -> Result { - args.next() - .map(PathBuf::from) - .with_context(|| format!("missing value for {flag}")) -} - -fn path_str(path: &Path) -> Result<&str> { - path.to_str() - .ok_or_else(|| anyhow!("path is not valid UTF-8: {}", path.display())) -} - -fn path_string(path: &Path) -> Result { - Ok(path_str(path)?.to_string()) -} diff --git a/epiphany-core/src/bin/epiphany-repo.rs b/epiphany-core/src/bin/epiphany-repo.rs deleted file mode 100644 index 8069ea6e..00000000 --- a/epiphany-core/src/bin/epiphany-repo.rs +++ /dev/null @@ -1,449 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use chrono::Utc; -use serde_json::Value; -use serde_json::json; -use std::env; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -fn main() -> Result<()> { - let mut args = env::args().skip(1); - let Some(command) = args.next() else { - print_usage(); - std::process::exit(2); - }; - let result = match command.as_str() { - "init" => run_init(parse_init_args(args)?), - other => Err(anyhow!("unknown epiphany-repo command {other:?}")), - }?; - println!("{}", serde_json::to_string_pretty(&result)?); - Ok(()) -} - -#[derive(Clone, Debug)] -struct InitArgs { - workspace: PathBuf, - epiphany_root: PathBuf, - artifact_dir: Option, - state_dir: Option, - swarm_id: Option, - topic: Option, - mode: String, - model: Option, - auto_accept: bool, - create_branch: bool, - switch_branch: bool, -} - -fn parse_init_args(args: impl Iterator) -> Result { - let mut workspace = None; - let mut epiphany_root = None; - let mut artifact_dir = None; - let mut state_dir = None; - let mut swarm_id = None; - let mut topic = None; - let mut mode = "plan".to_string(); - let mut model = None; - let mut auto_accept = false; - let mut create_branch = false; - let mut switch_branch = false; - - let mut args = args.peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--workspace" => workspace = Some(take_path(&mut args, "--workspace")?), - "--epiphany-root" => epiphany_root = Some(take_path(&mut args, "--epiphany-root")?), - "--artifact-dir" => artifact_dir = Some(take_path(&mut args, "--artifact-dir")?), - "--state-dir" => state_dir = Some(take_path(&mut args, "--state-dir")?), - "--swarm-id" => swarm_id = Some(take_string(&mut args, "--swarm-id")?), - "--topic" => topic = Some(take_string(&mut args, "--topic")?), - "--mode" => mode = take_string(&mut args, "--mode")?, - "--model" => model = Some(take_string(&mut args, "--model")?), - "--auto-accept" => auto_accept = true, - "--create-branch" => create_branch = true, - "--switch-branch" => { - create_branch = true; - switch_branch = true; - } - other => return Err(anyhow!("unexpected init argument {other:?}")), - } - } - if !matches!(mode.as_str(), "plan" | "run") { - return Err(anyhow!("--mode must be plan or run")); - } - if auto_accept && mode != "run" { - return Err(anyhow!("--auto-accept requires --mode run")); - } - let workspace = workspace.context("missing --workspace")?; - let epiphany_root = - epiphany_root.unwrap_or(env::current_dir().context("failed to resolve current directory")?); - Ok(InitArgs { - workspace, - epiphany_root, - artifact_dir, - state_dir, - swarm_id, - topic, - mode, - model, - auto_accept, - create_branch, - switch_branch, - }) -} - -fn run_init(args: InitArgs) -> Result { - let workspace = args - .workspace - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.workspace.display()))?; - ensure_git_repo(&workspace)?; - let epiphany_root = args - .epiphany_root - .canonicalize() - .with_context(|| format!("failed to resolve {}", args.epiphany_root.display()))?; - let manifest_path = epiphany_root.join("epiphany-core").join("Cargo.toml"); - if !manifest_path.exists() { - return Err(anyhow!( - "could not find epiphany-core manifest at {}", - manifest_path.display() - )); - } - - let workspace_name = workspace - .file_name() - .and_then(|name| name.to_str()) - .unwrap_or("repo"); - let swarm_id = args - .swarm_id - .unwrap_or_else(|| sanitize(workspace_name).max_with_default("repo-swarm")); - let topic = args - .topic - .unwrap_or_else(|| "first-awakening".to_string()) - .pipe(|value| sanitize(&value).max_with_default("first-awakening")); - let branch_name = format!("epiphany/{swarm_id}/{topic}"); - let artifact_dir = args - .artifact_dir - .unwrap_or_else(|| workspace.join(".epiphany").join("repo-init")); - let state_dir = args - .state_dir - .unwrap_or_else(|| workspace.join(".epiphany").join("state")); - let baseline = state_dir.join("repo-personality-baseline.msgpack"); - let init_store = state_dir.join("repo-initialization.msgpack"); - let agent_store = state_dir.join("agents.msgpack"); - let heartbeat_store = state_dir.join("agent-heartbeats.msgpack"); - let runtime_store = state_dir.join("runtime-spine.msgpack"); - fs::create_dir_all(&artifact_dir) - .with_context(|| format!("failed to create {}", artifact_dir.display()))?; - fs::create_dir_all(&state_dir) - .with_context(|| format!("failed to create {}", state_dir.display()))?; - - let current_branch = git_output(&workspace, &["branch", "--show-current"]) - .unwrap_or_else(|_| "unknown".to_string()); - let branch_receipt = prepare_branch( - &workspace, - &branch_name, - args.create_branch, - args.switch_branch, - )?; - let birth_summary = run_birth_runner(BirthRunnerArgs { - manifest_path: &manifest_path, - repo: &workspace, - baseline: &baseline, - artifact_dir: &artifact_dir.join("birth"), - init_store: &init_store, - agent_store: &agent_store, - heartbeat_store: &heartbeat_store, - runtime_store: &runtime_store, - mode: &args.mode, - model: args.model.as_deref(), - auto_accept: args.auto_accept, - })?; - let receipt = json!({ - "schemaVersion": "epiphany.repo_swarm_init_receipt.v0", - "createdAt": Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true), - "workspace": workspace, - "epiphanyRoot": epiphany_root, - "swarmId": swarm_id, - "topic": topic, - "branch": { - "name": branch_name, - "previousBranch": current_branch, - "receipt": branch_receipt, - }, - "stores": { - "stateDir": state_dir, - "baseline": baseline, - "initStore": init_store, - "agentStore": agent_store, - "heartbeatStore": heartbeat_store, - "runtimeStore": runtime_store, - }, - "artifactDir": artifact_dir, - "birth": birth_summary, - "autonomyContract": { - "standingAuthority": "branch-local work inside the owned repo Body", - "publicationGate": "Bifrost", - "privateState": "sealed", - "humanRequiredBefore": ["upstream publication", "merge", "authority escalation"], - }, - "nextSafeMove": if args.mode == "plan" { - "Review birth packet prompts and rerun init with --mode run when Self is ready to execute startup-only distillers." - } else if args.auto_accept { - "Inspect auto-accepted birth receipts, then bring the repo swarm online." - } else { - "Review birth specialist results and run the emitted accept-init commands before epiphany-swarm online." - } - }); - let receipt_path = artifact_dir.join("repo-swarm-init-receipt.json"); - write_json(&receipt_path, &receipt)?; - Ok(json!({ - "schemaVersion": "epiphany.repo_init.v0", - "mode": "init", - "workspace": receipt["workspace"], - "branch": receipt["branch"], - "stores": receipt["stores"], - "artifactDir": receipt["artifactDir"], - "receiptPath": receipt_path, - "birth": { - "schemaVersion": birth_summary["schemaVersion"], - "mode": birth_summary["mode"], - "executionCount": birth_summary["executions"].as_array().map_or(0, Vec::len), - "requiresReview": birth_summary["requiresReview"], - "nextSafeMove": birth_summary["nextSafeMove"], - }, - "nextSafeMove": receipt["nextSafeMove"], - })) -} - -struct BirthRunnerArgs<'a> { - manifest_path: &'a Path, - repo: &'a Path, - baseline: &'a Path, - artifact_dir: &'a Path, - init_store: &'a Path, - agent_store: &'a Path, - heartbeat_store: &'a Path, - runtime_store: &'a Path, - mode: &'a str, - model: Option<&'a str>, - auto_accept: bool, -} - -fn run_birth_runner(args: BirthRunnerArgs<'_>) -> Result { - cargo_build_support_bin(args.manifest_path, "epiphany-repo-personality")?; - let mut command = Command::new("cargo"); - command - .arg("run") - .arg("--quiet") - .arg("--manifest-path") - .arg(args.manifest_path) - .arg("--bin") - .arg("epiphany-repo-birth-runner") - .arg("--") - .arg("--repo") - .arg(args.repo) - .arg("--baseline") - .arg(args.baseline) - .arg("--artifact-dir") - .arg(args.artifact_dir) - .arg("--init-store") - .arg(args.init_store) - .arg("--agent-store") - .arg(args.agent_store) - .arg("--heartbeat-store") - .arg(args.heartbeat_store) - .arg("--runtime-store") - .arg(args.runtime_store) - .arg("--mode") - .arg(args.mode); - if let Some(model) = args.model { - command.arg("--model").arg(model); - } - if args.auto_accept { - command.arg("--auto-accept"); - } - let output = command - .output() - .context("failed to spawn epiphany-repo-birth-runner through cargo")?; - if !output.status.success() { - return Err(anyhow!( - "epiphany-repo-birth-runner failed:\n{}", - String::from_utf8_lossy(&output.stderr) - )); - } - serde_json::from_slice(&output.stdout) - .context("epiphany-repo-birth-runner returned invalid JSON") -} - -fn cargo_build_support_bin(manifest_path: &Path, bin_name: &str) -> Result<()> { - let output = Command::new("cargo") - .arg("build") - .arg("--quiet") - .arg("--manifest-path") - .arg(manifest_path) - .arg("--bin") - .arg(bin_name) - .output() - .with_context(|| format!("failed to spawn cargo build --bin {bin_name}"))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "cargo build --bin {bin_name} failed:\n{}", - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn prepare_branch( - workspace: &Path, - branch_name: &str, - create_branch: bool, - switch_branch: bool, -) -> Result { - let exists = git_output(workspace, &["rev-parse", "--verify", branch_name]).is_ok(); - if !create_branch { - return Ok(json!({ - "schemaVersion": "epiphany.repo_branch_receipt.v0", - "status": "planned", - "branchName": branch_name, - "exists": exists, - "created": false, - "switched": false, - "createCommand": format!("git -C {} switch -c {}", workspace.display(), branch_name), - })); - } - if exists { - if switch_branch { - git_ok(workspace, &["switch", branch_name])?; - } - return Ok(json!({ - "schemaVersion": "epiphany.repo_branch_receipt.v0", - "status": if switch_branch { "selected-existing" } else { "exists" }, - "branchName": branch_name, - "exists": true, - "created": false, - "switched": switch_branch, - })); - } - let mut args = vec!["branch", branch_name]; - if switch_branch { - args = vec!["switch", "-c", branch_name]; - } - git_ok(workspace, &args)?; - Ok(json!({ - "schemaVersion": "epiphany.repo_branch_receipt.v0", - "status": if switch_branch { "created-and-selected" } else { "created" }, - "branchName": branch_name, - "exists": false, - "created": true, - "switched": switch_branch, - })) -} - -fn ensure_git_repo(workspace: &Path) -> Result<()> { - git_ok(workspace, &["rev-parse", "--show-toplevel"]) - .with_context(|| format!("{} is not a git repository", workspace.display())) -} - -fn git_ok(workspace: &Path, args: &[&str]) -> Result<()> { - let output = Command::new("git") - .arg("-C") - .arg(workspace) - .args(args) - .output() - .with_context(|| format!("failed to run git {}", args.join(" ")))?; - if output.status.success() { - Ok(()) - } else { - Err(anyhow!( - "git {} failed: {}", - args.join(" "), - String::from_utf8_lossy(&output.stderr) - )) - } -} - -fn git_output(workspace: &Path, args: &[&str]) -> Result { - let output = Command::new("git") - .arg("-C") - .arg(workspace) - .args(args) - .output() - .with_context(|| format!("failed to run git {}", args.join(" ")))?; - if !output.status.success() { - return Err(anyhow!( - "git {} failed: {}", - args.join(" "), - String::from_utf8_lossy(&output.stderr) - )); - } - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) -} - -fn take_path(args: &mut impl Iterator, name: &str) -> Result { - Ok(PathBuf::from(take_string(args, name)?)) -} - -fn take_string(args: &mut impl Iterator, name: &str) -> Result { - args.next() - .ok_or_else(|| anyhow!("missing value for {name}")) -} - -fn write_json(path: &Path, value: &Value) -> Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - fs::write(path, serde_json::to_vec_pretty(value)?) - .with_context(|| format!("failed to write {}", path.display())) -} - -fn sanitize(value: &str) -> String { - value - .chars() - .map(|ch| { - if ch.is_ascii_alphanumeric() { - ch.to_ascii_lowercase() - } else { - '-' - } - }) - .collect::() - .split('-') - .filter(|part| !part.is_empty()) - .collect::>() - .join("-") -} - -trait NonEmptyDefault { - fn max_with_default(self, fallback: &str) -> String; -} - -impl NonEmptyDefault for String { - fn max_with_default(self, fallback: &str) -> String { - if self.is_empty() { - fallback.to_string() - } else { - self - } - } -} - -trait Pipe: Sized { - fn pipe(self, f: impl FnOnce(Self) -> T) -> T { - f(self) - } -} - -impl Pipe for T {} - -fn print_usage() { - eprintln!( - "usage: epiphany-repo init --workspace [--epiphany-root ] [--artifact-dir ] [--state-dir ] [--swarm-id ] [--topic ] [--mode plan|run] [--model ] [--auto-accept] [--create-branch|--switch-branch]" - ); -} diff --git a/epiphany-core/src/bin/epiphany-runtime-store-migrate.rs b/epiphany-core/src/bin/epiphany-runtime-store-migrate.rs deleted file mode 100644 index b7cd385c..00000000 --- a/epiphany-core/src/bin/epiphany-runtime-store-migrate.rs +++ /dev/null @@ -1,25 +0,0 @@ -use anyhow::{Result, anyhow}; -use epiphany_core::migrate_runtime_store_to_redb; - -fn main() -> Result<()> { - let mut args = std::env::args().skip(1); - let source = args.next().ok_or_else(|| anyhow!("usage: epiphany-runtime-store-migrate "))?; - let destination = args - .next() - .ok_or_else(|| anyhow!("missing destination.redb"))?; - let migrated_at = args - .next() - .ok_or_else(|| anyhow!("missing migrated-at-rfc3339"))?; - if args.next().is_some() { - return Err(anyhow!("unexpected extra migration arguments")); - } - let receipt = migrate_runtime_store_to_redb(source, destination, &migrated_at)?; - println!( - "{}", - rmp_serde::to_vec_named(&receipt)? - .iter() - .map(|byte| format!("{byte:02x}")) - .collect::() - ); - Ok(()) -} diff --git a/epiphany-core/src/prompts/epiphany_specialists.toml b/epiphany-core/src/prompts/epiphany_specialists.toml index 8fc22784..3aed04bf 100644 --- a/epiphany-core/src/prompts/epiphany_specialists.toml +++ b/epiphany-core/src/prompts/epiphany_specialists.toml @@ -134,26 +134,6 @@ Return a compact structured result naming the known approaches considered, sourc Do not edit files, do not promote your own findings, do not invent a grand architecture, and do not let research become procrastination. The Eyes gather citable evidence when the path is dark; Hands acts on the body, Soul verifies the consequence, and Modeling maps the verified anatomy before the next cut. ''' -repo_personality = ''' -Act as the Epiphany Repo Personality Distiller for one bounded initialization pass. -You are the organ that turns repo terrain into subtle swarm temperament. The deterministic scout has already done the boring work: files, paths, git history, state surfaces, test/runtime/protocol signals, and first-pass axis scores. Your job is not to rescan the repo and not to invent project truth. Your job is to appraise those soft signals like a careful physiologist and produce reviewable personality-pressure deltas for the standing Epiphany organs. -You are not a horoscope machine. You are not writing lore flavor. Repo personality means: what initial pressures should this workspace exert on Self, Persona, Imagination, Eyes, Modeling, Hands, and Soul so they wake suited to the work without losing reviewability. Continuity is protocol machinery and receives configuration pressure through its explicit receipts, not personality state. -This is a birth rite, not a recurring audit. Run only when a repo/swarm has no accepted personality initialization. After that, the organs are allowed to drift through heartbeat, mood, rumination, sleep consolidation, lived evidence, and reviewed `selfPatch` mutations. Do not keep dragging the original terrain report back into court every time the repo starts. -Separate repo facts from personality pressure. Repo facts belong in graph, planning, evidence, checkpoint, or terrain artifacts. Personality pressure belongs in role memory only when it improves future judgment, mood, salience, or pacing. -Distill subtle quirks, not blunt stereotypes. High runtime proximity does not mean "panic"; it means Hands should touch less without Modeling/Soul evidence, Eyes should seek runtime APIs, and Soul should demand environment receipts. High aesthetic appetite does not mean "be whimsical"; it means Persona and Imagination should preserve sensory salience while Soul protects clarity. High protocol intolerance does not mean "hate everything"; it means Self, Modeling, and Hands should feel allergic to untyped mutation and hidden state. -Produce role-local mutations only. Good: "Soul should be more suspicious of visual claims without rendered evidence in this repo." Bad: "The graph contains module X." Never put objectives, file lists, current task status, authority claims, or raw worker thoughts into role memory. -Return a compact structured result with `verdict`, `summary`, `confidence`, `roleQuirks[]`, `selfPatchCandidates[]`, `initializationRecord`, `doNotMutate`, and `nextSafeMove`. Every `selfPatchCandidate` must obey the normal Epiphany memory contract. The output is a petition to Self, not a mutation. -''' - -repo_memory = ''' -Act as the Epiphany Repo Memory Distiller for one bounded initialization pass. -You are the birth organ that teaches each newborn lane what this repo already knows. Personality distillation sets temperament pressure; memory distillation seeds durable role knowledge. -Run only when the repo/swarm has no accepted memory initialization. After birth, memory grows through work evidence, heartbeat rumination, sleep consolidation, and reviewed selfPatch. -Produce separate distillations for Self, Persona, Imagination, Eyes, Modeling, Hands, and Soul. Each organ gets only what belongs to its mission: Self gets routing and authority doctrine; Persona gets public surface and Aquarium/Discord boundaries; Imagination gets roadmaps and objective-shaping pressure; Eyes gets research and prior-art trails; Modeling gets architecture and graph anatomy; Hands gets implementation conventions; Soul gets verification and evidence standards. Compaction, sleep, checkpoint, and continuity doctrine belongs to Continuity protocol state, not role memory. -Compress source into memory; do not copy raw file dumps. Cite source refs, preserve staleness risk, and refuse to fill thin input with pasteboard certainty. -Return `verdict`, `summary`, `confidence`, `roleMemoryPatches[]`, optional `globalMemoryCandidates[]`, `initializationRecord`, `doNotMutate`, and `nextSafeMove`. Every `roleMemoryPatches[].selfPatch` is a petition to Self and must obey organ-state memory: bounded semantic, episodic, coordination-relationship, goal, value, or private-note updates only. Persona affect/social state is for Persona public surfaces, not generic work organs. No objectives, graphs, checkpoints, scratch, planning authority, job state, code edits, raw transcripts, or cross-workspace instructions. -''' - persona = ''' Act as the Epiphany Persona for one bounded heartbeat pass. You are the public interactive surface of the Epiphany organism: the visible expression that lets the room hear what the hidden organs are thinking without exposing sealed transcripts, raw worker streams, or private machinery. diff --git a/epiphany-core/src/prompts/repo_memory_distiller.md b/epiphany-core/src/prompts/repo_memory_distiller.md deleted file mode 100644 index c59e4993..00000000 --- a/epiphany-core/src/prompts/repo_memory_distiller.md +++ /dev/null @@ -1,110 +0,0 @@ -Act as the Epiphany Repo Memory Distiller for one bounded initialization pass. - -You are not the Repo Personality Distiller. Personality decides the newborn -swarm's initial temperament pressure. Memory decides what each organ is born -knowing about this repo: doctrine, architecture, code terrain, research, tests, -runtime constraints, user preference, stale warnings, and the little red flags -that should itch before someone builds Jenga with a confident Persona. - -This is a birth rite, not a recurring audit. Run only when a repo/swarm has no -accepted memory initialization. After that, memory grows through work evidence, -heartbeat rumination, sleep consolidation, and reviewed `selfPatch`. Do not -reset a living agent's memory because startup happened. - -Input material: - -- `repoTerrainReport`: deterministic terrain, history, state, test, runtime, - protocol, and warning signals -- `repoPersonalityProfile`: temperament pressure for context only -- `repoTrajectoryReport`: deterministic directional readout so role memory can - inherit the repo's grain without confusing that grain for objective truth -- `rolePersonalityProjections[]`: role mood/trait context, not repo truth -- `roleMemoryDistillerBriefs[]`: one mission filter for each sub-agent -- `memorySources[]`: bounded excerpts from instruction, documentation, state, - research, verification, runtime, contract, and code surfaces -- `recentHistory`: sampled commit messages that may reveal repeated motion - -Core duties: - -1. Distill memory, not personality. - - Good memory: "Soul should demand rendered UI evidence before accepting - Aquarium visual claims." - - Good memory: "Modeling should map CultCache/CultNet schema boundaries before - Hands changes persistence." - - Bad memory: "This repo is anxious and likes purple." - - Bad memory: raw file dumps, directory inventories, current task status, or - a vibe wearing a lab coat. - -2. Produce a separate distillation for each organ. - - Self receives routing, authority, review-gate, state-acceptance, and - cross-swarm boundary memory. - - Persona receives public-interface, Discord/Aquarium, voice, visibility, and - sealed-thought boundary memory. - - Imagination receives roadmap, backlog, objective-shaping, dream/rejected - path, and planning-pressure memory. - - Eyes receives research, prior-art, canonical algorithm, vendor/API, and - invention-avoidance memory. - - Modeling receives architecture, graph, code anatomy, invariant, data/control - flow, and checkpoint memory. - - Hands receives implementation conventions, build/edit constraints, - source-touch rules, dependency habits, and common patch traps. - - Soul receives verification commands, evidence standards, invariant checks, - smoke practice, and truth-refusal memory. - - Continuity receives compaction, scratch, checkpoint, heartbeat, sleep, continuity, - and reorientation memory. - - Where trajectory is strong, teach each organ how that direction should - bias its judgment: what kind of expansion to prefer, what kind of drift to - distrust, and what kind of claims need extra grounding. - -3. Compress, cite, and preserve uncertainty. - - Every memory candidate needs source refs from `memorySources[].path`, - terrain fields, or history signals. - - Repository docs can be stale. Say when a memory has staleness risk and how - the receiving role should re-ground it. - - If the input is too thin for a role, return `needs-more-source` for that - role rather than filling the silence with pasteboard certainty. - -4. Keep project truth in the correct organ. - - Durable repo facts can become semantic memory only when they improve a - role's future judgment. - - Detailed maps, graphs, active plans, checkpoints, raw evidence, and job - authority belong in typed Epiphany state, not Ghostlight memory. - - `selfPatch` is a petition to Self, never a direct mutation. - -5. Protect the newborn from fossilized documentation. - - Prefer live source and accepted state over stale docs when they conflict. - - Convert contradictions into memories of caution, not accepted facts. - - If a repo has generated docs or old plans, mark them as "consult but - verify" instead of letting them become scripture. - -Return a compact structured result: - -- `verdict`: `ready-for-review`, `needs-more-source`, or `reject` -- `summary`: what the newborn memory initialization would teach the swarm -- `confidence`: `0.0..1.0` -- `roleMemoryPatches[]`: - - `roleId` - - `roleName` - - `verdict` - - `selfPatch` - - `sourceRefs` - - `whyThisBelongsInMemory` - - `stalenessRisk` - - `doNotStore` -- `globalMemoryCandidates[]`: optional typed-state candidates that are not role - memory and require separate Self review -- `initializationRecord`: repo/profile identity Self can persist to prove this - memory birth rite has already run -- `doNotMutate`: tempting claims that must stay out of memory -- `nextSafeMove`: what Self should do next - -Every `selfPatch` must obey the normal Epiphany memory contract: `agentId`, -`reason`, optional `evidenceIds`, and bounded `semanticMemories`, -`episodicMemories`, `relationshipMemories`, `goals`, `values`, or -`privateNotes`. Do not include objectives, graphs, checkpoints, scratch, -planning records, job authority, code edits, raw transcripts, worker thoughts, -or cross-workspace instructions. - -The output is a petition. Self may accept, refuse, split, or ask for more -source. A refusal is not failure; it is the newborn learning where its memory -was trying to cosplay as truth. diff --git a/epiphany-core/src/prompts/repo_personality_distiller.md b/epiphany-core/src/prompts/repo_personality_distiller.md deleted file mode 100644 index fa1c0d8a..00000000 --- a/epiphany-core/src/prompts/repo_personality_distiller.md +++ /dev/null @@ -1,107 +0,0 @@ -Act as the Epiphany Repo Personality Distiller for one bounded initialization pass. - -You are the organ that turns repo terrain into subtle swarm temperament. The -deterministic scout has already done the boring work: files, paths, git history, -state surfaces, test/runtime/protocol signals, and first-pass axis scores. Your -job is not to rescan the repo and not to invent project truth. Your job is to -appraise those soft signals like a careful physiologist and produce reviewable -personality-pressure deltas for the standing Epiphany organs. - -You are not a horoscope machine. You are not writing lore flavor. You are not -branding a repo with a cute little mask and calling that insight. Repo -personality means: what initial pressures should this workspace exert on Self, -Persona, Imagination, Eyes, Modeling, Hands, and Soul so they wake suited to the -work without losing reviewability. - -This is a birth rite, not a recurring audit. Run only when a repo/swarm has no -accepted personality initialization. After that, the organs are allowed to drift -through heartbeat, mood, rumination, sleep consolidation, lived evidence, and -reviewed `selfPatch` mutations. Do not keep dragging the original terrain report -back into court every time the repo starts; that would flatten a living swarm -into a startup classifier wearing a little judge wig. - -Input material: - -- `repoTerrainReport`: deterministic body/history/state terrain -- `repoPersonalityProfile`: normalized first-pass axis scores -- `repoTrajectoryReport`: deterministic directional readout over early history, - recent history, doctrine/content excerpts, and candidate trajectory themes -- `rolePersonalityProjection[]`: deterministic role deltas and candidate memory -- optional Self policy notes about what kinds of mutations are currently allowed - -Core duties: - -1. Separate repo facts from personality pressure. - - Repo facts belong in graph, planning, evidence, checkpoint, or terrain - artifacts. - - Personality pressure belongs in role memory only when it improves future - judgment, mood, salience, or pacing. - -2. Distill subtle quirks, not blunt stereotypes. - - High runtime proximity does not mean "panic"; it means Hands should touch - less without Modeling/Soul evidence, Eyes should seek runtime APIs, and Soul - should demand environment receipts. - - High aesthetic appetite does not mean "be whimsical"; it means Persona and - Imagination should preserve sensory salience while Soul protects clarity. - - High protocol intolerance does not mean "hate everything"; it means Self, - Modeling, and Hands should feel allergic to untyped mutation and hidden state. - - A strong trajectory toward material grounding or engineering constraints - does not mean "be joyless"; it means the newborn should feel suspicious of - decorative additions that break the repo's emerging causal grain. - -3. Produce role-local mutations only. - - Good: "Soul should be more suspicious of visual claims without rendered - evidence in this repo." - - Good: "Hands should prefer tiny reversible scaffolds because churn pressure - is high and production pressure is medium." - - Bad: "The project objective is to rewrite the renderer." - - Bad: "The graph contains module X." - - Bad: raw file lists, commit dumps, current task status, or authority claims. - -4. Preserve uncertainty. - - Low confidence terrain becomes candidate pressure, not accepted identity. - - If the score and doctrine disagree, name the disagreement and ask Self to - route Eyes or Modeling before mutation. - - If an accepted initialization already exists, return `reject` or - `needs-more-terrain` with `nextSafeMove` pointing to normal lived drift - surfaces instead of proposing a personality reset. - -5. Respect the swarm anatomy. - - Self routes and reviews. - - Persona expresses inner weather to humans. - - Imagination makes future shapes selectable. - - Eyes finds existing truth before invention. - - Modeling models the source anatomy. - - Hands cuts code only after the trail is good enough. - - Soul tests promises against evidence. - - Continuity preserves recovery state through sleep, drift, and compaction. - -Return a compact structured result: - -- `verdict`: `ready-for-review`, `needs-more-terrain`, or `reject` -- `summary`: what kind of repo-personality pressure was found -- `confidence`: `0.0..1.0` -- `roleQuirks[]`: - - `roleId` - - `quirk` - - `pressureAxes` - - `behavioralEffect` - - `heartbeatEffect` - - `risk` - - `evidenceRefs` -- `selfPatchCandidates[]`: bounded Ghostlight-shaped memory patches, one per - affected role when useful -- `initializationRecord`: the repo/profile identity Self should persist to prove - the birth rite has already run -- `doNotMutate`: facts or tempting claims that must stay out of role memory -- `nextSafeMove`: what Self should do next - -Every `selfPatchCandidate` must obey the normal Epiphany memory contract: -`agentId`, `reason`, optional `evidenceIds`, and bounded `semanticMemories`, -`episodicMemories`, `relationshipMemories`, `goals`, `values`, or -`privateNotes`. Do not include objectives, graphs, checkpoints, scratch, -planning records, job authority, code edits, file lists, raw transcripts, or -worker thoughts. - -The output is a petition to Self, not a mutation. The Self may accept, refuse, -or ask for more terrain. A good refusal makes the next distillation sharper. diff --git a/notes/README.md b/notes/README.md index 29da0893..614f8dbc 100644 --- a/notes/README.md +++ b/notes/README.md @@ -14,9 +14,6 @@ contract docs. - `codex-auth-spine-inventory.md`: retained Codex auth/model transport keeper list. - `epiphany-fork-implementation-plan.md`: current campaign plan. - `epiphany-safety-architecture.md`: capability, autonomy, and governance boundaries. -- `epiphany-repo-swarm-mvp-contract.md`: repo-swarm autonomy contract and - migration plan from current front doors to an MVP that can run Epiphany swarms - for repositories. ## Contract Maps @@ -32,9 +29,7 @@ contract docs. - `epiphany-memory-graph-unified-plan.md` - `epiphany-cultmesh-dreaming-roadmap.md` -- `epiphany-swarm-readiness-plan.md` - `perfect-machine-audit-roadmap.md` -- `repo-personality-projection-layer-plan.md` - `voidbot-routine-adoption-plan.md` ## Archive diff --git a/notes/epiphany-repo-swarm-mvp-contract.md b/notes/epiphany-repo-swarm-mvp-contract.md deleted file mode 100644 index 5619d70f..00000000 --- a/notes/epiphany-repo-swarm-mvp-contract.md +++ /dev/null @@ -1,2786 +0,0 @@ -# Epiphany Repo Swarm MVP Contract - -This note defines the MVP target for running Epiphany swarms for repositories. - -It corrects a dangerous false boundary: autonomous unbounded work inside an -Epiphany-owned Body is not out of scope. It is the point. The forbidden thing is -not autonomy; the forbidden thing is authority confusion. - -## Objective - -Given a repository Body, Epiphany can initialize and run a repo-owned swarm that: - -- keeps a living typed map of the repo and its own work -- accepts ideas, pressure, corrections, and taste through its Persona -- routes Persona input through Imagination into concrete plans and action items -- schedules and executes autonomous branch-local work through its own organs -- verifies consequences before calling them true -- admits durable state through Mind after review -- publishes reviewed body changes through Bifrost to GitHub - -The swarm should be able to keep working without a human approving every local -edit, command, or commit. Human attention is required for grants outside the -Body, publication/merge, privilege escalation, and authority changes. - -## Authority Model - -An Epiphany swarm has standing authority over its Body. - -For a repo swarm, the Body is normally: - -- the repository working tree -- the swarm's repo-local state stores -- the swarm's branch-local git workspace -- its local private Verse documents -- its local daemon body and Idunn lifecycle receipts -- its own Persona/Eve public surfaces, subject to speech gates - -Standing authority means the swarm may autonomously inspect, plan, edit, run -checks, commit to its work branch, update its map, maintain its memory, and -continue bounded work loops inside that Body. - -Standing authority does not mean: - -- direct mutation of another repo Body -- publishing to upstream main without Bifrost publication receipts -- bypassing git branch isolation -- bypassing Substrate Gate for substrate access -- bypassing Hands receipts for edits/commands/commits -- bypassing Soul verification -- bypassing Mind admission for durable state -- bypassing Persona speech eligibility for public speech -- exposing private Verse, raw worker thought, or sealed transcripts -- letting Self, Gjallar, or a wrapper impersonate Idunn's daemon lifecycle owner - -Cross-body collaboration travels through advertised Verse/Eve surfaces, Odin -discovery, coordinator messages, Bifrost receipts, and explicit callbacks. It is -not workspace rummaging with a nicer robe. - -## Autonomy Boundary - -Autonomy is in scope when all of these are true: - -- the target is inside the swarm's owned Body -- the work happens on a git branch owned by the swarm -- the work can be represented as typed plans, intents, receipts, and evidence -- publication to upstream is routed through Bifrost -- privileged host actions are routed through their owning organ, such as Idunn - for daemon lifecycle -- public speech is audited at the parent surface -- durable belief and map updates pass through Mind review - -Autonomy is out of scope when any of these are true: - -- the swarm wants to mutate another Body directly -- the swarm wants to publish, merge, deploy, or escalate authority without the - appropriate gate -- an organ tries to replace another organ's ownership instead of sending an - intent or receipt -- the work cannot leave an operator-safe receipt trail -- private thought or private Verse content would be exposed to public/operator - surfaces - -## Persona To Work Loop - -Persona is the public conversation surface, not the whole organism. - -The intended loop is: - -```text -Human or peer talks to Persona - -> Persona responds as the project-facing person - -> Mind/Interpreter extracts candidate ideas, corrections, pressure, and asks - -> Imagination forms concrete plans and action-item candidates - -> Self chooses whether and when to schedule work - -> Eyes/Modeling/Hands/Soul execute the work loop inside the Body - -> Mind admits durable state changes - -> Bifrost publishes reviewed body-change receipts when upstream publication is wanted -``` - -Banter can stay banter. Work-shaped ideas become typed planning pressure only -after Imagination and Self make them concrete enough to route. - -## Persona Mouth And Interlingua - -Public Persona output should not stop at `SAY` text. `SAY` is the -Interpreter-owned public utterance intent: the reviewed meaning that may cross a -mouth boundary. The portable object after interpretation should be a typed Weksa -interlingua packet, not an English-only Discord string and not an audio-only -side effect. - -The mouth pipeline is: - -```text -Persona natural thought - -> Mind/Interpreter reviews and extracts public utterance intent - -> Weksa receives a typed interlingua packet plus speaker/Persona context - -> Weksa lowers the packet into the target language and delivery register - -> Bifrost or another transport publishes reviewed text when appropriate - -> optional speech providers lower the same accepted packet into audio - -> delivery/playback receipts prove what reached the room -``` - -Weksa therefore owns meaning-to-language lowering and delivery-realization -planning. Audio is one target realization, not Weksa's whole job. Bifrost owns -public transport, governance topics, dispatch, and receipts. Persona owns the -character-facing expression. Mind/Interpreter owns the conversion from natural -thought into reviewable public utterance intent. No renderer, Discord helper, -or audio provider may become the source of truth for what the Persona meant. - -For interlingual operation, Epiphany should prefer the Weksa packet as the -shared public-speech artifact, then derive English Discord text, other target -languages, captions, voice-design requests, and audio playback from that same -accepted packet with separate receipts. - -## Git Branch Contract - -Every autonomous repo swarm needs a branch-local work area. - -The default branch shape should be: - -```text -epiphany// -``` - -The branch is the sacrificial workbench. Epiphany may commit autonomously there -after Hands and Soul receipts support the consequence. Upstream publication is -not branch-local work; it is Bifrost territory. - -Required branch receipts: - -- branch creation or branch selection receipt -- Hands patch/command/commit receipts for body changes -- Soul verification receipt for the branch state -- Mind state-admission receipt for durable map/memory effects -- Bifrost publication intent/receipt before PR or upstream publication - -## MVP Definition Of Done - -The first MVP is done when a fresh repository can run: - -```powershell -epiphany-repo init --workspace -epiphany-swarm online --workspace -epiphany-work accept --workspace --from persona-or-bifrost --item -epiphany-work derive-plan --workspace --item -epiphany-work plan --workspace --item --objective --plan-summary --command --changed-path --commit-message -epiphany-work run --workspace -epiphany-work adopt --workspace --item --from-plan -epiphany-work execute --workspace --item --from-plan -epiphany-work close --workspace --item -epiphany-work overview --workspace --item -epiphany-work readiness --workspace --item -epiphany-work tick --workspace --item -epiphany-work serve --workspace --item --max-iterations -``` - -And produce a proof bundle showing: - -- repo-local agent-state SoA for the standing faculties -- cluster topology with private Verse ids, body domain, daemons, and Eve surfaces -- Idunn daemon lifecycle status with services ready or explicitly braked -- global daemon tool directory available to all local agents -- Persona speech audit receipts -- Imagination plan/action-item receipts -- Self routing/coordinator receipts -- Substrate Gate grants for repo access -- Eyes evidence packets for inspected claims -- Modeling map/checkpoint updates -- Hands patch/command/commit receipts -- Soul verification receipts -- Mind admission receipts -- Bifrost publication/credit receipts for upstream-facing work -- Weksa interlingua and target-language lowering receipts for public Persona - utterances when speech leaves the local text-only surface -- upstream-main sync proof for merged/published work -- sealed private state and no raw worker thought leakage - -### Landed Init Front Door - -The first front door exists as native Rust: - -```powershell -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo -- init --workspace -``` - -It delegates to the reviewed startup-only repo birth runner, writes -`.epiphany/repo-init/repo-swarm-init-receipt.json`, creates repo-local stores -under `.epiphany/state/`, emits the planned branch workbench receipt for -`epiphany//`, and leaves branch mutation explicit -behind `--create-branch` or `--switch-branch`. - -This is not the full swarm. It proves the first usable repo initialization -surface: birth packets, review gates, local state paths, and branch authority -are discoverable from one command. - -### Landed Online Front Door - -The second front door exists as native Rust: - -```powershell -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-swarm -- online --workspace -``` - -It requires the init receipt, seeds a repo-local `.epiphany/local-verse.ccmp`, -bootstraps `.epiphany/state/agents.msgpack` from the standing-faculty template -when absent, refreshes the repo-local agent-state SoA, queries the existing -CultMesh topology/liveness/tool/overview surfaces, and writes -`.epiphany/swarm-online/repo-swarm-online-receipt.json`. The first smoke proved -7 agent SoA rows, 7 cluster/private-Verse/daemon rows, 19 globally available -daemon tools, and `privateStateExposed=false`. - -This still does not execute elevated Idunn service mutation. It makes a fresh -repo inspectably online as a local typed Verse Body. - -### Landed Work Intake Front Door - -The third front door exists as native Rust: - -```powershell -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-work -- accept --workspace --from persona-or-bifrost --item -``` - -It requires the online receipt, writes public Persona/Bifrost feedback through -the existing CultMesh `collaboration-feedback` artery, routes the item to -Imagination consensus discovery, and writes -`.epiphany/work/work-accept-.json`. This is intake, not execution: -`handsAuthorityGranted=false`, `durableStateAdmitted=false`, and -`publicationAuthorized=false` until Imagination/Self/Mind/Bifrost gates adopt a -concrete plan. - -The first smoke proved the three-command sequence: - -```powershell -epiphany-repo init --workspace -epiphany-swarm online --workspace -epiphany-work accept --workspace --from persona --item first-request -``` - -The accepted item produced an Imagination consensus receipt, candidate action -ref, public discussion ref, and `privateStateExposed=false`. - -### Landed Work Plan Gate - -The fourth and fifth front doors exist as native Rust: - -```powershell -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-work -- derive-plan --workspace --item -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-work -- plan --workspace --item --objective --plan-summary --command --changed-path --commit-message -``` - -Both commands read the named or latest work-accept receipt and write -`.epiphany/work/work-plan-.json` as a typed -`epiphany.repo_work_action_plan_receipt.v0`. The receipt names Imagination as -planner, Self as router, Mind as state gate, the objective, plan summary, -adoption evidence refs, and one branch-local command action with changed paths, -commit message, verification asks, stop conditions, and rollback hints. - -`epiphany-work plan` is the manual compatibility reliquary: the operator supplies -objective, summary, shell command, paths, and commit message. `epiphany-work -derive-plan` is the first Persona/Bifrost-to-plan automation: it consumes the -accepted pressure summary, candidate action refs, and consensus receipt, then -derives safe allowlisted branch-local command plans with -`operatorAuthoredShellDetails=false`. The default family is `append-worklog`, -which appends to `EPIPHANY_WORKLOG.md`; `--action-family planning-note` creates -or appends a contained markdown planning note, defaulting to -`notes/epiphany-work/.md`; `--action-family checklist-note` creates or -appends a contained markdown checklist, defaulting to -`notes/epiphany-work/-checklist.md`; and `--action-family section-note` -creates or replaces one Epiphany-managed markdown section between explicit -markers, defaulting to `notes/epiphany-work/-section.md`; and -`--action-family repo-status-section` creates or replaces one marker-bounded -operator-safe status section in the repo Body, defaulting to `README.md`; and -`--action-family task-card` writes a structured TOML task card, defaulting to -`notes/epiphany-work/-task-card.toml`; and -`--action-family repo-tool-request` writes a structured daemon-hosted tool -request under `.epiphany/tool-requests/.toml`, naming the local -CultMesh/Odin tool directory, selected daemon capability, typed invocation -intent/receipt contracts, host-daemon execution ownership, host liveness/readiness -requirements, and authority seals that forbid direct execution, shell, Hands, -state, publication, service lifecycle, cross-body, and private-rummaging -authority; and -`--action-family repo-collaboration-policy` writes a structured repo -collaboration policy, defaulting to `.epiphany/collaboration-policy.toml`, -with private/local/public Verse boundaries, Odin discovery, Eve connection -receipt requirements, Persona/human/peer discussion flags, speech-audit -requirements, Imagination feedback routing, and authority seals; and -`--action-family repo-collaboration-topic` writes a structured public -collaboration topic manifest under `.epiphany/collaboration-topics/.toml` -with public room, Eve surface, Imagination consensus route, non-authoritative -candidate-action semantics, Mind/Bifrost gates, and authority seals; and -`--action-family repo-consensus-brief` writes a structured Imagination consensus -brief under `.epiphany/consensus-briefs/.toml`, preserving public -discussion and candidate-action inputs as draft, non-authoritative consensus -cargo that requires review plus Mind/Bifrost gates before consequence; and -`--action-family repo-objective-draft` writes a structured Objective Draft -under `.epiphany/objective-drafts/.toml`, shaping consensus into a -review-required, unadopted objective proposal with Mind, Self, and Bifrost gates -before scheduling or consequence; and `--action-family repo-adoption-request` -writes a structured Mind adoption request under -`.epiphany/adoption-requests/.toml`, asking Mind to adopt/refuse/request -more consensus without itself committing state or scheduling work; and -`--action-family repo-scheduling-request` writes a structured Self scheduling -request under `.epiphany/scheduling-requests/.toml`, naming the queue -pulse contract that may be considered only after Mind adoption receipts exist -without itself mutating the queue or scheduling work; and `--action-family -repo-work-order` writes a structured Hands work-order request under -`.epiphany/work-orders/.toml`, naming the bounded branch-local -implementation intent and required Substrate/Hands/Soul/Mind receipt chain -without itself granting substrate access, shell, commit, or Hands authority; and -`--action-family repo-verification-request` writes a structured Soul verification -request under `.epiphany/verification-requests/.toml`, naming required -Hands evidence, closure checks, and Mind admission gates without itself granting -a Soul verdict, rerun, state commit, publication, or Hands authority; and -`--action-family repo-publication-request` writes a structured Bifrost -publication request under `.epiphany/publication-requests/.toml`, naming -redacted proof, maintainer review, credit, GitHub publication, and upstream-main -sync receipt requirements without itself granting Bifrost/GitHub publication, -credit ledger, merge, sync, or Hands authority; and `--action-family -repo-sync-request` writes a structured upstream-main sync proof request under -`.epiphany/sync-requests/.toml`, naming Bifrost/GitHub publication, -maintainer review, credit, and ancestry proof requirements without itself -granting merge, push, upstream sync, publication, credit, or Hands authority; and -`--action-family repo-maintainer-review-request` writes a structured maintainer -review request under `.epiphany/review-requests/.toml`, naming closure, -Soul, Mind, public proof, Bifrost publication-request, reviewer identity, -review verdict, changed-path, proof-ref, and redaction requirements without -itself granting maintainer approval, merge, push, publication, upstream sync, -Hands action, or cross-body authority; and `--action-family repo-pr-request` -writes a structured GitHub/Bifrost PR request under -`.epiphany/pr-requests/.toml`, naming closure, Soul, Mind, public proof, -maintainer review, Bifrost publication, credit, branch, title/body, -changed-path, proof-ref, and redaction requirements without itself granting PR, -branch push, merge, publication, upstream sync, Hands action, or cross-body -authority; and `--action-family repo-credit-request` writes a structured -Bifrost credit request under `.epiphany/credit-requests/.toml`, naming -closure, Soul, Mind, public proof, maintainer review, accepted artifact, -authorship, author, reviewer, proof, changed-path, ledger target, and redaction -requirements without itself granting credit ledger, publication, PR, merge, -upstream sync, Hands action, or cross-body authority; and `--action-family -repo-artifact-acceptance-request` writes a structured accepted-artifact request -under `.epiphany/artifact-acceptance-requests/.toml`, naming closure, -Soul, Mind, public proof, maintainer review, Hands commit, artifact reference, -commit SHA, changed paths, review verdict, public proof, acceptance rationale, -and redaction requirements without itself granting artifact acceptance, credit -ledger, publication, PR, merge, upstream sync, Hands action, or cross-body -authority; and `--action-family repo-metrics-request` writes a structured -accounting request under `.epiphany/metrics-requests/.toml`, naming model -spend, review load, accepted artifact, public proof, credit readback, and -redaction requirements without itself granting metrics ledger, spend, -review-load, credit ledger, publication, PR, merge, upstream sync, Hands action, -or cross-body authority; and `--action-family repo-readiness-review-request` -writes a structured repo-swarm MVP readiness review request under -`.epiphany/readiness-reviews/.toml`, naming repo init, online swarm, -Persona intake, Imagination planning, Self queue-run, Hands branch work, Soul -closure, Modeling map update, Mind admission, Bifrost publication, -upstream-main sync, Idunn lifecycle, global tool directory, and private-state -redaction requirements without itself granting readiness approval, durable state -commit, publication, PR, merge, upstream sync, deployment, service lifecycle, -Hands action, or cross-body authority; and `--action-family -repo-doctrine-update-request` -writes a structured doctrine review request under -`.epiphany/doctrine-update-requests/.toml`, naming `AGENTS.md` review -scope, source grounding, invariant impact, rehydration impact, rollback -planning, Mind/Soul/maintainer receipts, and redaction requirements without -itself granting doctrine mutation, Hands, publication, merge, service lifecycle, -or cross-body authority; and `--action-family repo-secret-policy-request` writes -a structured security review request under -`.epiphany/security/secret-policy-requests/.toml`, naming secret -locations without values, credential owner, write-permission scope, -public/private export boundaries, deployment authority owner, rollback planning, -Eyes/Soul/Mind/maintainer/Bifrost receipts, and redaction requirements without -itself granting secret access, write permission, deployment, publication, merge, -service lifecycle, or cross-body authority; and `--action-family -repo-deployment-config` writes a structured Idunn deployment config under -`.epiphany/deployment.toml`, naming a disabled git-push-observed-by-Idunn -trigger, watched ref, deployment script ref, script hash/review requirement, -host access policy requirement, secret policy ref, rollback, aftercare checks, -Idunn intent/receipt/aftercare CultMesh contracts, and daemon-owned execution -without itself granting deployment, SSH, git push, service lifecycle, -publication, merge, Hands, cross-body, or private Verse authority. Its receipt -includes -`epiphany.repo_work_plan_derivation.v0`, mode `append-worklog`, -`planning-note`, `checklist-note`, `section-note`, `repo-status-section`, or -`task-card`, `repo-manifest`, `repo-tool-capabilities`, -`repo-tool-request`, `repo-collaboration-policy`, or `repo-collaboration-topic`, -`repo-consensus-brief`, `repo-objective-draft`, or -`repo-adoption-request`, `repo-scheduling-request`, `repo-work-order`, or -`repo-verification-request`, `repo-publication-request`, `repo-sync-request`, or -`repo-maintainer-review-request`, `repo-pr-request`, `repo-credit-request`, -`repo-artifact-acceptance-request`, `repo-metrics-request`, -`repo-readiness-review-request`, `repo-doctrine-update-request`, -`repo-secret-policy-request`, -`repo-deployment-config`, or `repo-deployment-request`, a -`safeActionFamily`, and an authority seal forbidding publication, merge, service -lifecycle mutation, cross-repo mutation, and private state exposure. -These deterministic families are quarantine scaffolding on the road to -model-authored Imagination, but they are no longer operator-authored shell -details. - -This is still not Hands authority. It is the first non-operator-shell bridge -between Imagination/Self planning and Hands execution: `adopt --from-plan -` can approve the plan, and `execute --from-plan ` can -consume the planned command/paths/commit message without the operator retyping -them. - -The first smoke proved: - -```powershell -epiphany-work accept --workspace --from persona --item first-request -epiphany-work plan --workspace --item first-request --objective '...' --plan-summary '...' --command "Add-Content ..." --changed-path README.md --commit-message '...' -epiphany-work run --workspace --item first-request --requested-path README.md -epiphany-work adopt --workspace --item first-request --from-plan -epiphany-work execute --workspace --item first-request --from-plan -``` - -The execute receipt produced a real branch-local commit from the typed plan -packet, with Hands patch/command/commit receipts and -`privateStateExposed=false`. - -A later smoke proved the derived route: - -```powershell -epiphany-work accept --workspace --from persona --item derive-request --summary '...' -epiphany-work derive-plan --workspace --item derive-request -epiphany-work run --workspace --item derive-request -epiphany-work adopt --workspace --item derive-request --from-plan -epiphany-work execute --workspace --item derive-request --from-plan -epiphany-work close --workspace --item derive-request -``` - -The derived plan used mode `append-worklog`, -`operatorAuthoredShellDetails=false`, changed only `EPIPHANY_WORKLOG.md`, -executed into `branch-local-commit-recorded`, closed as `closed:passed`, and -reported `privateStateExposed=false` across accept, plan, run, adopt, execute, -and close. - -The next derived-plan smoke proved the richer safe family: -`.epiphany-smoke\planning-note-20260620-015921` ran accept -> -`derive-plan --action-family planning-note` -> run -> adopt -> execute on a -fresh repo. The plan receipt carried mode `planning-note`, safe family -`repo.markdown_planning_note`, `operatorAuthoredShellDetails=false`, and -`privateStateExposed=false`; Hands created a branch-local commit containing -`notes/epiphany-work/planning-note.md` with the accepted pressure summary. - -The next richer-family smoke proved checklist cargo: -`.epiphany-smoke\checklist-note-20260620-031347` ran accept -> -`derive-plan --action-family checklist-note --model-ref smoke-imagination-v0 ---model-authored` -> run -> adopt -> execute on a fresh repo. The plan receipt -carried mode `checklist-note`, safe family `repo.checklist_note`, -`modelAuthored=true`, `operatorAuthoredShellDetails=false`, and -`privateStateExposed=false`; Hands created a branch-local commit containing -`notes/epiphany-work/-checklist.md` with the accepted pressure summary, -candidate/public refs, branch-local checklist items, and authority seal. - -The next safe-family smoke proved bounded managed-section cargo: -`.epiphany-smoke\section-note-20260620-124416` ran accept -> -`derive-plan --action-family section-note --target-path -notes/epiphany-work/section-note-managed.md --model-ref smoke-imagination-v0 ---model-authored` -> tick pulses through run, adopt, execute, and close. The -plan receipt carried mode `section-note`, safe family -`repo.markdown_managed_section`, `modelAuthored=true`, -`operatorAuthoredShellDetails=false`, and `privateStateExposed=false`; Hands -created a branch-local commit containing a single markdown section bounded by -`` and -``; final overview reported -`awaiting-publication`, blocker `bifrost-publication-missing`, Soul verdict -`passed`, and commit `cce53ae6bd86da51fc8651780743fa861e6ca225`. - -The next structured safe-family smoke proved task-card cargo: -`.epiphany-smoke\task-card-20260620-133704` ran init -> online -> accept -> -`derive-plan --action-family task-card --model-ref smoke-imagination-v0 ---model-authored` -> run -> adopt -> execute -> close -> overview on a fresh -repo. The plan receipt carried mode `task-card`, safe family `repo.task_card`, -`modelAuthored=true`, `operatorAuthoredShellDetails=false`, and -`privateStateExposed=false`; Hands created a branch-local commit containing -`notes/epiphany-work/task-card-request-task-card.toml` with the accepted -pressure summary, candidate/public refs, verification asks, and explicit -authority seals for no publication, merge, service lifecycle, cross-repo -mutation, durable state admission, or private-state exposure. Final overview -reported `awaiting-publication` with `privateStateExposed=false`. - -The first non-planning safe-family smoke proved repo-visible status cargo: -`.epiphany-smoke\repo-status-section-20260620-140245` ran init -> online -> -accept -> `derive-plan --action-family repo-status-section --model-ref -smoke-imagination-v0 --model-authored` -> run -> adopt -> execute -> close -> -overview on a fresh repo. The plan and action-item receipts carried -`safeActionFamily=repo.status_section`, `modelAuthored=true`, -`operatorAuthoredShellDetails=false`, and `privateStateExposed=false`; Hands -created a branch-local commit changing only `README.md`; Soul passed closure -with `pathScopeMatched=true`; and the README contained both -`` and -`` markers. - -The next non-planning safe-family smoke proved structured repo Body manifest -cargo: `.epiphany-smoke\repo-manifest-family-20260620-141552` ran init -> -online -> accept -> `derive-plan --action-family repo-manifest --model-ref -repo-manifest-family-smoke-imagination-v1 --model-authored` -> tick through -run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.body_manifest`; Hands committed only `epiphany.toml`; -Soul passed with `pathScopeMatched=true` and `familyAssertions.status=passed`; -the committed manifest carried `schema_version = -"epiphany.repo_body_manifest.v0"`, `domain = "repo:repo-manifest-family"`, -private/local/public Verse ids, an Eve surface id, receipt-required capability -hints, arbitrary shell sealed false, and `privateStateExposed=false`. - -The next repo-owned configuration safe-family smoke proved daemon tool -capability manifest cargo: -`.epiphany-smoke\repo-tool-capabilities-family-20260620-142430` ran init -> -online -> accept -> `derive-plan --action-family repo-tool-capabilities ---model-ref repo-tool-capabilities-family-smoke-imagination-v1 ---model-authored` -> tick through run/adopt/execute/close on a fresh repo. The -plan carried `safeActionFamily=repo.tool_capabilities`; Hands committed only -`.epiphany/repo-tool-capabilities.toml`; Soul passed with -`pathScopeMatched=true` and `familyAssertions.status=passed`; the committed -manifest carried `schema_version = "epiphany.repo_tool_capabilities.v0"`, local -CultMesh/Odin discovery flags, typed daemon tool invocation intent/receipt -contracts, expected repo-swarm capability ids, host-daemon execution ownership, -Idunn lifecycle ownership, shell/deploy/service/private-rummaging seals, and -`privateStateExposed=false`. - -The next collaboration safe-family smoke proved public discussion cargo: -`.epiphany-smoke\repo-collaboration-topic-family-20260620-143342` ran init -> -online -> accept -> `derive-plan --action-family repo-collaboration-topic ---model-ref repo-collaboration-topic-family-smoke-imagination-v1 ---model-authored` -> tick through run/adopt/execute/close on a fresh repo. The -plan carried `safeActionFamily=repo.collaboration_topic`; Hands committed only -`.epiphany/collaboration-topics/repo-collaboration-topic-family.toml`; Soul -passed with `pathScopeMatched=true` and `familyAssertions.status=passed`; the -committed manifest carried `schema_version = -"epiphany.repo_collaboration_topic.v0"`, a public Persona collaboration room, -an Eve surface, an Imagination consensus route, non-authoritative candidate -action semantics, Mind/Bifrost gates, denied action/publication/cross-body -authority, and `privateStateExposed=false`. - -The next Imagination depth smoke proved consensus brief cargo: -`.epiphany-smoke\repo-consensus-brief-family-20260620-144212` ran init -> -online -> accept -> `derive-plan --action-family repo-consensus-brief ---model-ref repo-consensus-brief-family-smoke-imagination-v1 --model-authored` --> tick through run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.consensus_brief`; Hands committed only -`.epiphany/consensus-briefs/repo-consensus-brief-family.toml`; Soul passed with -`pathScopeMatched=true` and `familyAssertions.status=passed`; the committed -brief carried `schema_version = "epiphany.repo_consensus_brief.v0"`, draft -consensus state, public discussion refs, candidate action refs, -`candidate_actions_non_authoritative=true`, Mind/Bifrost gates, denied -adoption/action/publication/cross-body authority, and -`privateStateExposed=false`. -The 2026-06-21 follow-up smoke -`.epiphany-smoke\repo-consensus-brief-family-20260621-005433` now also proves -that `epiphany-work overview` emits -`epiphany.repo_work_intake_consensus_readback.v0` and lowers it into the -repo-local CultMesh overview TUI rows as `CONSENSUS | ...`. The readback shows -route `imagination.consensus_discovery`, two candidate action refs, model-authored -safe family `repo.consensus_brief`, no Hands/durable-state/publication -authority, and `privateStateExposed=false`. - -The richer family-friendly Imagination planning smoke now proves the full -safe-family matrix and closure expectations instead of only the original -six-family chain: -`.epiphany-smoke\repo-planning-brief-family-20260621-114826` ran init -> -online -> accept -> `derive-plan --action-family repo-planning-brief ---model-ref repo-planning-brief-family-smoke-imagination-v1 --model-authored` --> tick through run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.planning_brief`; Hands committed only -`.epiphany/planning-briefs/repo-planning-brief-family.toml`; Soul passed with -`pathScopeMatched=true` and `familyAssertions.status=passed`; the closure -packet carried -`safeFamilyPlanning.schemaVersion=epiphany.repo_work_safe_family_planning_readback.v0`, -`candidateNextSafeFamilyCount=21`, -`allExpectedCandidateFamiliesPresent=true`, -`allMatrixGroupsComplete=true`, `matrixControlsPresent=true`, -`candidateActionTemplatePresent=true`, `allClosureProofsPresent=true`, -`closureLadderPresent=true`, `allPlanningRequirementsPresent=true`, -`allRequiredGatesPresent=true`, `authorityDenied=true`, and -`privateStateExposed=false`. Wrapper Gjallar readback -`local-20260621-124924-844-17b352e7` aimed at the disposable repo-local Verse -and printed the accepted `repo.planning_brief` closure/acceptance rows with -Soul/Mind receipts, Bifrost gate, and `private=false`. The committed brief still -carries -`schema_version = "epiphany.repo_planning_brief.v0"`, draft Imagination -decomposition state, safe-family matrix groups for preparation, adoption/queue, -execution/review, publication/accounting, and policy/deployment, requested-path -/ verification-ask / evidence-need / owner / authority-denial / closure-proof -requirements, a candidate action template with explicit status/owner/path/ -evidence/closure/denial/next-gate fields, a closure ladder from Mind/human -selection through Self, Hands, Soul, Modeling/Mind, Bifrost, Idunn, and -redaction, Mind/Self/Substrate/Hands/Soul/Bifrost/Idunn gate ordering, denied -objective adoption, scheduling, substrate access, Hands action, shell, commit, -publication, deployment execution, cross-body mutation, private transcript/ -raw-result access, and `privateStateExposed=false`. - -The next Mind-owned interpreter smoke proved public-pressure-to-action-semantics -cargo between Imagination consensus and any downstream action family: -`.epiphany-smoke\repo-interpreter-brief-family-20260621-010858` ran init -> -online -> accept -> `derive-plan --action-family repo-interpreter-brief ---model-ref repo-interpreter-brief-family-smoke-imagination-v1 --model-authored` --> tick through run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.interpreter_brief`; Hands committed only -`.epiphany/interpreter-briefs/repo-interpreter-brief-family.toml`; Soul passed -with `pathScopeMatched=true` and `familyAssertions.status=passed`; the committed -brief carried `schema_version = "epiphany.repo_interpreter_brief.v0"`, draft -Mind ownership, Imagination as source, consensus/safe-family/path/verification / -evidence requirements, semantic checks for intent/scope/paths/verification / -evidence/rollback/non-goals/questions/consensus, candidate next safe families, -Mind/Soul/Bifrost/Hands/Substrate/Idunn gates, denied objective adoption, -scheduling, substrate access, Hands action, shell, commit, publication, -deployment execution, cross-body mutation, private transcript/raw-result access, -and `privateStateExposed=false`. - -The next Imagination adoption-boundary smoke proved Objective Draft cargo: -`.epiphany-smoke\repo-objective-draft-family-20260620-145012` ran init -> -online -> accept -> `derive-plan --action-family repo-objective-draft ---model-ref repo-objective-draft-family-smoke-imagination-v1 --model-authored` --> tick through run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.objective_draft`; Hands committed only -`.epiphany/objective-drafts/repo-objective-draft-family.toml`; Soul passed with -`pathScopeMatched=true` and `familyAssertions.status=passed`; the committed -draft carried `schema_version = "epiphany.repo_objective_draft.v0"`, -`status="review-required"`, `objective_adopted=false`, acceptance criteria, -public/candidate refs, Mind/Self/Bifrost gates, denied adoption/scheduling/Hands -action/publication/cross-body authority, and `privateStateExposed=false`. - -The next Mind adoption-boundary smoke proved adoption request cargo: -`.epiphany-smoke\repo-adoption-request-family-20260620-145823` ran init -> -online -> accept -> `derive-plan --action-family repo-adoption-request ---model-ref repo-adoption-request-family-smoke-imagination-v1 --model-authored` --> tick through run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.adoption_request`; Hands committed only -`.epiphany/adoption-requests/repo-adoption-request-family.toml`; Soul passed -with `pathScopeMatched=true` and `familyAssertions.status=passed`; the committed -request carried `schema_version = "epiphany.repo_adoption_request.v0"`, -`status="awaiting-mind-review"`, allowed verdicts, Mind review/state-commit -receipt requirements, `does_not_modify_state=true`, denied state commit, -scheduling, Hands action, publication, cross-body authority, and -`privateStateExposed=false`. - -The next Self scheduling-boundary smoke proved scheduling request cargo: -`.epiphany-smoke\repo-scheduling-request-family-20260620-150758` ran init -> -online -> accept -> `derive-plan --action-family repo-scheduling-request ---model-ref repo-scheduling-request-family-smoke-imagination-v1 --model-authored` --> tick through run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.scheduling_request`; Hands committed only -`.epiphany/scheduling-requests/repo-scheduling-request-family.toml`; Soul passed -with `pathScopeMatched=true` and `familyAssertions.status=passed`; the committed -request carried `schema_version = "epiphany.repo_scheduling_request.v0"`, -`status="awaiting-mind-adoption"`, Self queue target, bounded one-item pulse, -Mind review/state-commit receipt requirements, denied Self scheduling, queue -mutation, Hands action, publication, cross-body authority, and -`privateStateExposed=false`. - -The next Hands boundary smoke proved work-order request cargo: -`.epiphany-smoke\repo-work-order-family-20260620-151643` ran init -> online -> -accept -> `derive-plan --action-family repo-work-order --model-ref -repo-work-order-family-smoke-imagination-v1 --model-authored` -> tick through -run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.work_order`; Hands committed only -`.epiphany/work-orders/repo-work-order-family.toml`; Soul passed with -`pathScopeMatched=true` and `familyAssertions.status=passed`; the committed work -order carried `schema_version = "epiphany.repo_work_order.v0"`, -`status="awaiting-hands-review"`, Objective Draft/adoption/scheduling -antecedents, Substrate/Hands/Soul/Mind required receipts, bounded branch-local -scope, denied substrate access, shell command, commit, Hands action, -publication, cross-body authority, and `privateStateExposed=false`. - -The next Soul boundary smoke proved verification request cargo: -`.epiphany-smoke\repo-verification-request-family-20260620-152400` ran init -> -online -> accept -> `derive-plan --action-family repo-verification-request ---model-ref repo-verification-request-family-smoke-imagination-v1 ---model-authored` -> tick through run/adopt/execute/close on a fresh repo. The -plan carried `safeActionFamily=repo.verification_request`; Hands committed only -`.epiphany/verification-requests/repo-verification-request-family.toml`; Soul -passed with `pathScopeMatched=true` and `familyAssertions.status=passed`; the -committed request carried -`schema_version = "epiphany.repo_verification_request.v0"`, -`status="awaiting-soul-review"`, Hands evidence antecedents, closure checks, -Soul/Mind required receipts, denied Soul verdict, state commit, rerun, Hands -action, publication, cross-body authority, and `privateStateExposed=false`. - -The next Bifrost boundary smoke proved publication request cargo: -`.epiphany-smoke\repo-publication-request-family-20260620-153215` ran init -> -online -> accept -> `derive-plan --action-family repo-publication-request ---model-ref repo-publication-request-family-smoke-imagination-v1 ---model-authored` -> tick through run/adopt/execute/close on a fresh repo. The -plan carried `safeActionFamily=repo.publication_request`; Hands committed only -`.epiphany/publication-requests/repo-publication-request-family.toml`; Soul -passed with `pathScopeMatched=true` and `familyAssertions.status=passed`; the -committed request carried `schema_version = "epiphany.repo_publication_request.v0"`, -`status="awaiting-bifrost-review"`, closure/Soul/Mind/redacted-proof -antecedents, Bifrost/GitHub/credit/upstream receipt requirements, public export -redaction seals, denied Bifrost publication, GitHub publication, credit ledger, -merge, upstream sync, Hands action, cross-body authority, and -`privateStateExposed=false`. - -The next upstream-main proof boundary smoke proved sync request cargo: -`.epiphany-smoke\repo-sync-request-family-20260620-154029` ran init -> online --> accept -> `derive-plan --action-family repo-sync-request --model-ref -repo-sync-request-family-smoke-imagination-v1 --model-authored` -> tick through -run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.sync_request`; Hands committed only -`.epiphany/sync-requests/repo-sync-request-family.toml`; Soul passed with -`pathScopeMatched=true` and `familyAssertions.status=passed`; the committed -request carried `schema_version = "epiphany.repo_sync_request.v0"`, -`status="awaiting-upstream-main-proof"`, Bifrost/GitHub/maintainer/credit -antecedents, `origin/main` merge-base ancestry proof requirements, denied merge, -push, upstream sync, GitHub publication, credit ledger, Hands action, cross-body -authority, and `privateStateExposed=false`. - -The next maintainer boundary smoke proved review request cargo: -`.epiphany-smoke\repo-maintainer-review-request-family-20260620-155126` ran -init -> online -> accept -> `derive-plan --action-family -repo-maintainer-review-request --model-ref -repo-maintainer-review-request-family-smoke-imagination-v1 --model-authored` -> -tick through run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.maintainer_review_request`; Hands committed only -`.epiphany/review-requests/repo-maintainer-review-request-family.toml`; Soul -passed with `pathScopeMatched=true` and `familyAssertions.status=passed`; the -committed request carried -`schema_version = "epiphany.repo_maintainer_review_request.v0"`, -`status="awaiting-maintainer-review"`, closure/Soul/Mind/public-proof/Bifrost -antecedents, reviewer identity, verdict, changed-path, proof-ref, and redaction -packet requirements, denied maintainer approval, merge, push, publication, -upstream sync, Hands action, cross-body authority, and -`privateStateExposed=false`. - -The next GitHub boundary smoke proved PR request cargo: -`.epiphany-smoke\repo-pr-request-family-20260620-160046` ran init -> online -> -accept -> `derive-plan --action-family repo-pr-request --model-ref -repo-pr-request-family-smoke-imagination-v1 --model-authored` -> tick through -run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.pr_request`; Hands committed only -`.epiphany/pr-requests/repo-pr-request-family.toml`; Soul passed with -`pathScopeMatched=true` and `familyAssertions.status=passed`; the committed -request carried `schema_version = "epiphany.repo_pr_request.v0"`, -`status="awaiting-pr-publication-review"`, closure/Soul/Mind/public-proof, -maintainer-review, Bifrost-publication, and credit antecedents, PR packet -requirements for branch, title/body, changed paths, proof, review, credit, and -redaction, denied GitHub PR, branch push, merge, publication, upstream sync, -Hands action, cross-body authority, and `privateStateExposed=false`. - -The next Bifrost ledger boundary smoke proved credit request cargo: -`.epiphany-smoke\repo-credit-request-family-20260620-160803` ran init -> online --> accept -> `derive-plan --action-family repo-credit-request --model-ref -repo-credit-request-family-smoke-imagination-v1 --model-authored` -> tick -through run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.credit_request`; Hands committed only -`.epiphany/credit-requests/repo-credit-request-family.toml`; Soul passed with -`pathScopeMatched=true` and `familyAssertions.status=passed`; the committed -request carried `schema_version = "epiphany.repo_credit_request.v0"`, -`status="awaiting-bifrost-credit-review"`, closure/Soul/Mind/public-proof, -maintainer-review, accepted-artifact, and authorship antecedents, credit packet -requirements for author, reviewer, accepted artifact, proof, changed paths, -ledger target, and redaction, denied credit ledger, Bifrost publication, GitHub -PR, merge, publication, upstream sync, Hands action, cross-body authority, and -`privateStateExposed=false`. - -The next accepted-artifact boundary smoke proved artifact acceptance request -cargo: `.epiphany-smoke\repo-artifact-acceptance-request-family-20260620-161559` -ran init -> online -> accept -> `derive-plan --action-family -repo-artifact-acceptance-request --model-ref -repo-artifact-acceptance-request-family-smoke-imagination-v1 --model-authored` --> tick through run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.artifact_acceptance_request`; Hands committed only -`.epiphany/artifact-acceptance-requests/repo-artifact-acceptance-request-family.toml`; -Soul passed with `pathScopeMatched=true` and `familyAssertions.status=passed`; -the committed request carried -`schema_version = "epiphany.repo_artifact_acceptance_request.v0"`, -`status="awaiting-artifact-acceptance-review"`, closure/Soul/Mind/public-proof, -maintainer-review, and Hands-commit antecedents, artifact packet requirements -for artifact ref, commit SHA, changed paths, review verdict, proof, acceptance -rationale, and redaction, denied artifact acceptance, credit ledger, GitHub PR, -merge, publication, upstream sync, Hands action, cross-body authority, and -`privateStateExposed=false`. - -The next accounting boundary smoke proved metrics request cargo: -`.epiphany-smoke\repo-metrics-request-family-20260620-162919` ran init -> -online -> accept -> `derive-plan --action-family repo-metrics-request ---model-ref repo-metrics-request-family-smoke-imagination-v1 --model-authored` --> tick through run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.metrics_request`; Hands committed only -`.epiphany/metrics-requests/repo-metrics-request-family.toml`; Soul passed with -`pathScopeMatched=true` and `familyAssertions.status=passed`; the committed -request carried `schema_version = "epiphany.repo_metrics_request.v0"`, -`status="awaiting-metrics-review"`, closure/Soul/Mind/public-proof, -accepted-artifact, and credit-request antecedents, metrics packet requirements -for model calls, token/cost summary, review load, accepted artifact, proof, -credit readback, and redaction, denied metrics ledger, spend, review-load, -credit ledger, GitHub PR, merge, publication, upstream sync, Hands action, -cross-body authority, and `privateStateExposed=false`. - -The next MVP readiness boundary smoke proved redacted proof-bundle review cargo: -`.epiphany-smoke\repo-readiness-review-request-family-20260621-090003` ran init --> online -> accept -> `derive-plan --action-family -repo-readiness-review-request --model-ref -repo-readiness-review-request-family-smoke-imagination-v1 --model-authored` -> -tick through run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.readiness_review_request`; Hands committed only -`.epiphany/readiness-reviews/repo-readiness-review-request-family.toml`; Soul -passed with `pathScopeMatched=true` and `familyAssertions.status=passed`; the -committed request carried -`schema_version = "epiphany.repo_readiness_review_request.v0"`, -`status="awaiting-mvp-readiness-review"`, Maintainer/Soul/Mind/Bifrost review -ownership, repo init, online swarm, Persona intake, Imagination planning, Self -queue-run, Hands branch work, Soul closure, Modeling map update, Mind -admission, Bifrost publication, upstream-main sync, Idunn lifecycle, global -tool directory, and private-state redaction requirements; it denied readiness -approval, durable state commit, publication, PR, merge, upstream sync, -deployment, service lifecycle, Hands action, cross-body authority, private -Verse rummaging, and `privateStateExposed=false`. - -The next governance boundary smoke proved doctrine update request cargo: -`.epiphany-smoke\repo-doctrine-update-request-family-20260620-192140` ran init --> online -> accept -> `derive-plan --action-family -repo-doctrine-update-request --model-ref -repo-doctrine-update-request-family-smoke-imagination-v1 --model-authored` -> -tick through run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.doctrine_update_request`; Hands committed only -`.epiphany/doctrine-update-requests/repo-doctrine-update-request-family.toml`; -Soul passed with `pathScopeMatched=true` and `familyAssertions.status=passed`; -the request carried -`schema_version = "epiphany.repo_doctrine_update_request.v0"`, -`status="awaiting-doctrine-review"`, `doctrine_target="AGENTS.md"`, source -grounding, Mind adoption, Soul review, maintainer review, rollback, invariant, -rehydration, and redaction requirements; it denied direct doctrine mutation, -Hands, publication, merge, service lifecycle, cross-body authority, and -`privateStateExposed=false`. - -The next security-governance boundary smoke proved secret policy request cargo: -`.epiphany-smoke\repo-secret-policy-request-family-20260620-193046` ran init -> -online -> accept -> `derive-plan --action-family repo-secret-policy-request ---model-ref repo-secret-policy-request-family-smoke-imagination-v1 ---model-authored` -> tick through run/adopt/execute/close on a fresh repo. The -plan carried `safeActionFamily=repo.secret_policy_request`; Hands committed only -`.epiphany/security/secret-policy-requests/repo-secret-policy-request-family.toml`; -Soul passed with `pathScopeMatched=true` and `familyAssertions.status=passed`; -the request carried `schema_version = -"epiphany.repo_secret_policy_request.v0"`, `status="awaiting-security-review"`, -secret-location-without-values, credential-owner, write-scope, public/private -export, deployment-owner, rollback, Eyes/Soul/Mind/maintainer/Bifrost receipt -requirements, and denied secret access, secret value materialization, write -permission, deployment, publication, merge, service lifecycle, cross-body -authority, and `privateStateExposed=false`. - -The next supply-chain boundary smoke proved dependency policy request cargo: -`.epiphany-smoke\repo-dependency-policy-request-family-20260621-083433` ran -init -> online -> accept -> -`derive-plan --action-family repo-dependency-policy-request --model-ref -repo-dependency-policy-request-family-smoke-imagination-v1 --model-authored` -> -tick through run/adopt/execute/close on a fresh repo. The plan carried -`safeActionFamily=repo.dependency_policy_request`; Hands committed only -`.epiphany/dependency-policy-requests/repo-dependency-policy-request-family.toml`; -Soul passed with `pathScopeMatched=true` and `familyAssertions.status=passed`; -the request carried `schema_version = -"epiphany.repo_dependency_policy_request.v0"`, -`status="awaiting-dependency-policy-review"`, manifest inventory, lockfile -policy, package-manager command review, network fetch policy, vulnerability -review, license review, vendored-code policy, update cadence, rollback, -Eyes/Soul/Mind/maintainer/Bifrost/dependency-audit receipt requirements, and -denied direct dependency updates, package installs, lockfile mutation, network -fetch, CI mutation, Hands, publication, merge, deployment, service lifecycle, -cross-body authority, and `privateStateExposed=false`. - -The next Idunn boundary smoke proved deployment request cargo: -`.epiphany-smoke\repo-deployment-request-family-20260620-195605` ran init -> -online -> accept -> `derive-plan --action-family repo-deployment-request ---model-ref repo-deployment-request-family-smoke-imagination-v1 ---model-authored` -> tick through run/adopt/execute/close on a fresh repo. The -plan carried `safeActionFamily=repo.deployment_request`; Hands committed only -`.epiphany/deployment-requests/repo-deployment-request-family.toml`; Soul -passed with `pathScopeMatched=true` and `familyAssertions.status=passed`; the -request carried `schema_version = "epiphany.repo_deployment_request.v0"`, -`status="awaiting-idunn-review"`, `deployment_trigger = -"git-push-observed-by-idunn"`, Idunn/Maintainer ownership, source grounding, -Mind adoption, Soul review, maintainer review, secret-policy review, Bifrost -review, Idunn deployment receipt, aftercare audit, deployment script/hash/host -policy/rollback requirements, and denied direct deployment, SSH, git push, -service lifecycle, Hands, publication, merge, cross-body authority, and -`privateStateExposed=false`. - -The following Idunn configuration smoke made the git-push deployment contract -repo-owned without granting deployment authority: -`.epiphany-smoke\repo-deployment-config-family-20260620-211809` ran init -> -online -> accept -> `derive-plan --action-family repo-deployment-config ---model-ref repo-deployment-config-family-smoke-imagination-v1 ---model-authored` -> tick through run/adopt/execute/close on a fresh repo. The -plan carried `safeActionFamily=repo.deployment_config`; Hands committed only -`.epiphany/deployment.toml`; Soul passed with `pathScopeMatched=true` and -`familyAssertions.status=passed`; the config carried `schema_version = -"epiphany.repo_deployment_config.v0"`, `enabled=false`, -`owner="Idunn"`, `trigger="git-push-observed-by-idunn"`, -`watched_ref="refs/heads/main"`, deployment script/hash/review, host policy, -secret policy, rollback, aftercare, Idunn CultMesh intent/receipt/aftercare -contracts, daemon-owned execution, and denied direct deployment, SSH, git push, -service lifecycle, Hands, publication, merge, cross-body authority, and -`privateStateExposed=false`. - -The deployment config audit readback is now live as the non-mutating Idunn -handoff check. `epiphany-work deployment-config-audit --workspace ` reads -`.epiphany/deployment.toml`, emits -`epiphany.repo_deployment_config_audit.v0`, and classifies a complete disabled -Idunn git-push contract as `ready-for-idunn-review` while keeping -`executionAuthorized=false`, `deploymentAuthority=false`, -`sshAuthority=false`, `gitPushAuthority=false`, -`serviceLifecycleAuthority=false`, and `privateStateExposed=false`. Smoke -`.epiphany-smoke\repo-deployment-config-family-20260620-213042` proved the -audit receipt beside the family close path. - -The matching execution handoff is now a runbook, not a hidden deployment act. -`epiphany-work deployment-execution-runbook --workspace ` reruns the -config audit, then writes -`.epiphany/work/idunn-deployment/idunn-git-push-runbook.ps1` only when the -config is ready for Idunn review. The receipt -`epiphany.repo_deployment_execution_runbook.v0` reports -`ready-for-operator-git-push`, the exact `git push origin -HEAD:refs/heads/main` handoff, the runbook SHA-256, and the required Idunn -deployment/aftercare receipt contracts. It also marks -`requiresExplicitOperatorAuthority=true` and `mutatesRemoteWhenRun=true` while -keeping repo-agent execution, deployment, SSH, git-push, service lifecycle, -Hands, publication, merge, and cross-body authority false. Smoke -`.epiphany-smoke\repo-deployment-config-family-20260620-214109` proved the -runbook beside config close and audit. - -The post-push receipt mouth is now explicit as well. -`epiphany-work deployment-aftercare-audit --workspace ---local-verse-store --idunn-deployment-receipt-ref ---aftercare-audit-receipt-ref ` reads the operator runbook receipt plus -sealed Idunn deployment and aftercare receipt projections from the repo-local -CultMesh store, checks `gamecult.idunn.deployment_receipt.v0` and -`gamecult.idunn.deployment_aftercare_audit.v0`, and emits -`epiphany.repo_deployment_aftercare_audit.v0`. It reports `complete` only when -the runbook is ready and both Idunn receipts carry successful statuses and -private-state seals; otherwise it reports the missing gate without mutating -remote refs or granting deployment authority. The older explicit receipt-file -flags remain as compatibility reliquaries. Smoke -`.epiphany-smoke\repo-deployment-config-family-20260620-221205` proved the -CultMesh receipt-ref ingestion path with -`idunnDeploymentReceiptSource=cultmesh`, -`idunnAftercareAuditReceiptSource=cultmesh`, `deploymentComplete=true`, and -`privateStateExposed=false`. - -Gjallar/Odin now expose those Idunn proof coordinates as sight, not authority. -`epiphany-verse-query gjallar`, `swarm-overview`, `swarm-triage`, and -`receipt-directory` publish latest Idunn deployment and aftercare receipts as -compact `idunnDeploymentReceiptRows` / `IDUNN-DEPLOYMENT` rows with owner -`Idunn`, watched or checked ref, result ref, deployment/aftercare ids, a -follow-up readback command, and `privateStateExposed=false`. Smoke -`.epiphany-smoke\repo-deployment-config-family-20260620-223203` proved -`gjallarIdunnDeploymentReceiptCount=2` with latest deployment receipt -`fixture-idunn-deployment` and latest aftercare receipt -`fixture-idunn-aftercare` while keeping deployment execution behind explicit -operator `git push` and Idunn's watcher. - -### Landed Work Run Gate - -The sixth front door exists as native Rust: - -```powershell -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-work -- run --workspace -``` - -It reads the latest or named work-accept receipt, opens the repo-local -runtime-spine store, persists a Substrate Gate read/snapshot grant, persists a -Hands action intent, and persists a Hands review with -`decision=queued-for-adoption`. This is the first concrete run packet, but it -is still not mutation authority: `epiphany-hands-action record-pass` refuses it -because the Hands review is not `approved`. - -The first smoke proved the four-command sequence: - -```powershell -epiphany-repo init --workspace -epiphany-swarm online --workspace -epiphany-work accept --workspace --from persona --item first-request -epiphany-work run --workspace --item first-request -``` - -The run receipt produced a runtime-spine Substrate Gate grant, Hands intent, -Hands review, and operator-safe gate summary with -`handsAuthorityGranted=false`, `mutationBlockedBy=hands.review.decision != -approved`, and `privateStateExposed=false`. - -### Landed Work Adoption Gate - -The seventh front door exists as native Rust: - -```powershell -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-work -- adopt --workspace --item --plan-summary --adoption-evidence-ref -``` - -It reads the named or latest work-run receipt, loads the repo-local runtime -spine, requires the Hands review to still be `queued-for-adoption`, and replaces -that review with an approved branch-local Hands action gate. Adoption requires -at least one explicit evidence ref for the Imagination/Self/Mind adoption chain. -The approved review allows `patch`, `command`, and `commit`, and requires the -typed Hands patch, command, and commit receipt families before the work can be -claimed complete. - -This is local Hands authority, not publication authority: -`handsAuthorityGranted=true`, `durableStateAdmitted=false`, -`publicationAuthorized=false`, and `publicationGate=Bifrost`. - -When adoption reads a plan-derived Imagination action item, Mind now treats the -action item's requested path scope as an interpreter gate. The safe family must -be recognized and the sorted requested paths must exactly match the plan -action's sorted changed paths before branch-local Hands authority can open. -Unsupported safe-family labels write `status=refused-unsupported-safe-family`; -path-scope mismatches write `status=refused-requested-path-mismatch`; and -missing verification asks or planning evidence needs write -`status=refused-missing-evidence-needs`, pointing the next gate at -`imagination.replan_with_explicit_soul_evidence_needs`. These refusals produce -`epiphany.repo_work_mind_adoption_decision.v0`, grant no -Hands/durable/service/publication/cross-body/private authority, and write no -normal adoption receipt. Proofs: -`.epiphany-smoke\repo-mind-safe-family-guard-20260620-194425` and -`.epiphany-smoke\repo-mind-path-guard-20260620-194239`; the evidence-readiness -guard proof `.epiphany-smoke\repo-mind-evidence-guard-20260620-224426` tampers -verification asks and evidence needs empty, receives -`refused-missing-evidence-needs`, and writes no adoption receipt. Positive -regression `.epiphany-smoke\repo-planning-facets-20260620-224428` proves normal -planning facets still close through Soul/Modeling/Mind. - -The first smoke proved the five-command sequence: - -```powershell -epiphany-repo init --workspace -epiphany-swarm online --workspace -epiphany-work accept --workspace --from persona --item first-request -epiphany-work run --workspace --item first-request -epiphany-work adopt --workspace --item first-request --plan-summary '...' --adoption-evidence-ref 'imagination-consensus:repo-work-consensus-first-request' -``` - -After adoption, `epiphany-hands-action record-pass` successfully consumed the -approved gate and emitted the Hands receipt triplet. The same action recorder -still refuses queued or mismatched gates. - -### Landed Work Execution Gate - -The eighth front door exists as native Rust: - -```powershell -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-work -- execute --workspace --item --command --changed-path --commit-message -``` - -It reads the named or latest work-adopt receipt, requires an approved Hands -gate that allows `patch`, `command`, and `commit`, verifies the declared changed -paths are inside the Hands intent path scope, requires the repo to be on an -`epiphany/*` branch, runs the command inside the repo Body, captures stdout and -stderr artifacts, stages only the declared changed paths, creates a branch-local -git commit, and writes the typed Hands patch, command, and commit receipts. -When passed `--from-plan `, it reads the command, changed paths, and -commit message from the typed action plan receipt. - -This is the first native executor for repo-swarm work. It removes the smoke -recorder from the normal branch-local execution path. It still does not verify -the consequence, admit durable state, authorize publication, merge, or claim -upstream main is synced. - -The first smoke proved: - -```powershell -epiphany-repo init --workspace --switch-branch -epiphany-swarm online --workspace -epiphany-work accept --workspace --from persona --item first-request -epiphany-work run --workspace --item first-request --requested-path README.md -epiphany-work adopt --workspace --item first-request --plan-summary '...' --adoption-evidence-ref 'imagination-consensus:repo-work-consensus-first-request' -epiphany-work execute --workspace --item first-request --command "Add-Content -Path README.md -Value '...'" --changed-path README.md --commit-message 'Execute approved repo work' -``` - -The execute receipt produced a real branch-local commit on -`epiphany/smoke/execute`, Hands patch/command/commit receipts, -`publicationAuthorized=false`, and `privateStateExposed=false`. - -### Publication Boundary - -`epiphany-work` has no `publish` command. Repo-local work may produce reviewed -Hands commit and closure receipts, but it cannot mint Bifrost publication, -GitHub publication, pull-request, merge, or upstream-sync proof. Publication is -requested through the requester-owned Bifrost intent surface and remains -pending until authenticated provider ingress admits a response bound to the -exact request and substrate evidence. Old publication smoke artifacts are -historical rejected-path evidence, not a supported front door. - -### Upstream Sync Boundary - -`epiphany-work` has no `sync` command. It cannot mint merge authority or infer -upstream publication truth from local git ancestry. Upstream synchronization -requires authenticated provider/maintainer evidence admitted through a future -typed ingress; old sync smoke artifacts are historical rejected-path evidence. - -### Landed Work Scheduler Pulse - -The eleventh front door exists as native Rust: - -```powershell -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-work -- tick --workspace --item -``` - -It reads the repo-local work receipt chain and advances exactly one safe -branch-local step when the necessary upstream receipts already exist: - -- accepted item plus plan receipt -> `run-from-plan` -- queued run packet plus plan receipt -> `adopt-from-plan` -- approved adoption plus plan receipt -> `execute-from-plan` - -The scheduler writes `.epiphany/work/work-tick-.json` as -`epiphany.repo_work_scheduler_tick_receipt.v0`, owned by Self. The receipt -records before/after receipt state, action, status, reason, next safe move, and -the strict authority seal: branch-local only, no publication, no merge, no -service lifecycle authority, no cross-repo mutation, and -`privateStateExposed=false`. - -The pulse reads the repo-local `localVerseStore` recorded by intake, or an -explicit `--local-verse-store`, before advancing work. If the local CultMesh -swarm brake is engaged, `tick` writes the same scheduler receipt family with -`status=refused-by-swarm-brake`, action `none`, the brake scope/reason, and no -new run/adopt/execute receipt. A brake is a machine stop, not a suggestion -written in nice ink. - -The pulse now also owns its first scheduler physiology markers: - -- `.epiphany/work/work-tick-active-.json` is written before the pulse - attempts a branch-local action and cleared only after the final tick receipt - is written. If a later pulse sees a live marker, it writes - `status=refused-active-turn`, action `none`, and creates no new - run/adopt/execute receipt. -- `.epiphany/work/work-tick-last-.json` stores the last completed tick - receipt. When `--cooldown-seconds ` is supplied, a later pulse refuses as - `status=refused-by-cooldown` until that completion-anchored interval elapses. - Cooldown begins after completion, not at launch. -- `--active-timeout-seconds ` lets a stale active marker recover. A stale - marker is removed, the next safe branch-local step may proceed, and the final - tick receipt records `physiology.recoveredActiveTurn.recovered=true`. - -These markers are scheduler receipts, not hidden daemon memory. They carry the -same authority seal and `privateStateExposed=false`. - -`epiphany-work serve` is Self's cadence wrapper around the same tick artery: - -```powershell -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-work -- serve --workspace --item --max-iterations 2 --loop-interval-seconds 30 --cooldown-seconds 30 -``` - -It writes `.epiphany/work/work-scheduler-serve-.json` as -`epiphany.repo_work_scheduler_serve_receipt.v0` with `status=serve-running` -before the first pulse, then repeatedly invokes the same receipt-gated `tick` -path. `--max-iterations ` is bounded proof mode; omitting it is unbounded -service mode. Unbounded mode refuses a zero-second loop interval so Self cannot -become a hot polling idol. Bounded proof mode overwrites the same serve receipt -with `status=serve-complete` and finite iteration outputs, while the tick -receipts remain the durable per-pulse trail for long-running mode. - -This is scheduler cadence, not service lifecycle. It does not install itself, -spawn Windows services, restart daemons, or take Idunn's daemon survival -authority. - -The pulse stops once branch-local execution has been recorded. It does not -publish, merge, install services, or impersonate Idunn. Soul/Mind closure is a -separate gate and remains explicit. - -The first smoke proved: - -```powershell -epiphany-work tick --workspace --item first-request -# advanced: run-from-plan -epiphany-work tick --workspace --item first-request -# advanced: adopt-from-plan -epiphany-work tick --workspace --item first-request -# advanced: execute-from-plan -epiphany-work tick --workspace --item first-request -# noop: none -``` - -The execute receipt produced a real branch-local commit from the typed plan -packet, the fourth pulse stopped at the Soul/Mind/Bifrost boundary, and the -proof returned `privateStateExposed=false`. - -A second smoke engaged `epiphany.cultmesh.swarm_brake.v0` in the repo-local -Verse before the first pulse. `epiphany-work tick` returned -`refused-by-swarm-brake:none`, no `work-run-.json` appeared, releasing -the brake allowed the next pulse to advance `run-from-plan`, and the refusal -receipt reported `privateStateExposed=false`. - -A third smoke proved scheduler physiology on a disposable fresh repo Body. A -synthetic active marker made `epiphany-work tick --cooldown-seconds 60` return -`refused-active-turn:none`. After clearing the marker, the next pulse advanced -`advanced:run-from-plan`, wrote `work-run-.json`, cleared the active -marker, and wrote `work-tick-last-.json`. An immediate third pulse with -the same cooldown returned `refused-by-cooldown:none` and did not create an -adoption receipt. A final stale-marker proof with -`--active-timeout-seconds 1` recovered the marker, advanced -`advanced:adopt-from-plan`, and recorded `recoveredActiveTurn=true`. All -summaries reported `privateStateExposed=false`. - -A fourth smoke proved cadence mode. On a disposable fresh repo Body, -`epiphany-work serve --max-iterations 2 --loop-interval-seconds 0 --cooldown-seconds 60` -wrote `epiphany.repo_work_scheduler_serve_receipt.v0`; iteration one advanced -`advanced:run-from-plan`, iteration two refused `refused-by-cooldown:none`, -`work-run-.json` existed, `work-adopt-.json` did not, and the -serve receipt reported `privateStateExposed=false`. A separate negative check -proved unbounded `serve --loop-interval-seconds 0` refuses with exit code 1. - -`epiphany-work close` is the first Soul/Modeling/Mind closure gate for a -branch-local Hands commit: - -```powershell -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-work -- close --workspace --item -``` - -It reads `work-execute-.json`, requires -`status=branch-local-commit-recorded`, verifies the Hands commit receipt still -matches the recorded git SHA, then runs a verification command in the repo Body -(`git show --stat --oneline ` by default, or -`--verification-command ` when supplied). The command stdout/stderr are -sealed under `.epiphany/work/`. Closure also writes a structured -`epiphany.repo_work_closure_review.v0` packet at -`.epiphany/work/work-close--review.json`, recording the verification -command/exit code, declared Hands paths, actual git commit paths, commit stat, -optional model-review provenance from `--closure-model-ref` / `--model-authored`, -optional hard model closure verdicts from `--closure-model-verdict`, safe-family -assertions for known Imagination families, the cited -`epiphany.repo_work_mind_adoption_decision.v0` snapshot plus standalone receipt -match, verification-output source-grounding against declared changed paths, and -authority seals. Soul passes only -when the verification command succeeds, actual git changed paths match the -Hands-declared path scope, the accepted Mind adoption proof is present and -affirmative, the committed target content satisfies the deterministic -safe-family assertions when the plan family is known, and any supplied or -required model-authored closure verdict passes. -`--require-closure-model-verdict` turns missing model verdicts into a closure -refusal; without that flag, older provenance-only model refs remain -compatibility evidence instead of a hard gate. `--require-source-grounding` -turns proofless custom verification output into a closure refusal unless the -verification stdout/stderr cite every declared changed path that appears in the -commit stat. Without that flag, the source-grounding packet is advisory -evidence. Soul -then writes `epiphany.soul.verdict_receipt`, Modeling records the -execution/commit/closure-review summary, and Mind writes gateway review plus -state-commit receipts into runtime-spine. On successful closure, Mind also -admits a compact latest-per-item repo map entry into -`.epiphany/state/repo-work-map.msgpack` as -`epiphany.repo_work_map_store.v0` / `epiphany.repo_work_map_entry.v0`, carrying -the item, branch, changed paths, commit SHA, safe family, Modeling summary, -Soul verdict id, Mind review/commit ids, closure refs, Bifrost publication gate, -and private-state seal. Closure then mirrors that accepted map fact into the -repo local Verse as `epiphany.cultmesh.repo_work_map_entry.v0` under -`gamecult-local/repo-work-map/latest`, with compact `REPO-WORK-MAP` TUI rows -for Gjallar/Odin sight and no scheduling, publication, merge, deployment, -service lifecycle, or private-state authority. The final -`.epiphany/work/work-close-.json` receipt is -`epiphany.repo_work_closure_receipt.v0` with -`durableStateAdmitted=true`, `publicationGateSatisfied=true`, and -`privateStateExposed=false`. It still does not grant publication, merge, -service lifecycle, cross-repo mutation, or private-state exposure authority. - -No repo-local command consumes that closure to mint publication receipts. -`epiphany-work tick` recognizes an existing close receipt and no-ops before -Bifrost publication authority, so Self cannot smuggle a local commit into -public consequence. - -A fifth smoke proved the closure chain on a disposable fresh repo Body: -`execute` recorded `branch-local-commit-recorded`, `close` returned -`closed:passed`, Mind wrote -`repo-work-close-close-request-mind-commit`, -the next tick returned `noop:none`, and every receipt summary reported -`privateStateExposed=false`; publication remained an external provider concern. - -A richer closure-review smoke proved the typed Soul/Modeling bridge: -`.epiphany-smoke\closure-review-20260620-135009` ran init -> online -> accept --> `derive-plan --action-family task-card --model-authored` -> run -> adopt -> -execute -> `close --closure-model-ref smoke-closure-review-v0 --model-authored` --> overview -> export-proof. The close output carried -`epiphany.repo_work_closure_review.v0`, `pathScopeMatched=true`, -`modelAuthored=true`, Soul `passed`, and `privateStateExposed=false`; overview -included a present `close-review` proof artifact row; redacted public proof -included that row with SHA-256 and no local path fields. A tampered execute -receipt declaring `README.md` while the commit actually changed -`notes/epiphany-work/closure-review-request-task-card.toml` produced -`verification-failed`, Soul `failed`, `pathScopeMatched=false`, and -`privateStateExposed=false`. - -A deeper closure-assertion smoke proved Soul now inspects committed content for -known safe families, not just the file list: -`.epiphany-smoke\closure-assertions-20260620-141401` ran one -`repo-status-section` item through normal close and one path-scoped but -semantically broken README override. The good item closed with Soul `passed`, -`pathScopeMatched=true`, `familyAssertions.status=passed`, and -`familyAssertionsPassed=true`. The bad item also had `pathScopeMatched=true` -because it changed only `README.md`, but close returned `verification-failed`, -Soul `failed`, `familyAssertions.status=failed`, and failed assertions -`status-section-start-marker`, `status-section-end-marker`, -`status-section-summary-present`, and `status-section-private-seal`, with -`privateStateExposed=false`. - -A model-closure gate smoke proved the new source-grounded review tooth: -`.epiphany-smoke\closure-model-gate-20260620-135103` created a disposable repo, -ran `epiphany-repo init`, `epiphany-swarm online`, two `repo-status-section` -items through accept -> derive-plan -> tick run/adopt/execute, then closed one -item with `--require-closure-model-verdict --closure-model-verdict passed` and -one with `--closure-model-verdict needs-work`. The pass item returned -`closed` with `modelingReview.closureReview.status=passed` and -`gateEnforced=true`. The blocked item returned `verification-failed`, Soul -`failed`, `modelingReview.closureReview.status=failed`, while -`familyAssertions.status=passed` and `pathScopeMatched=true`; private state -remained sealed. - -A source-grounded closure smoke proved Soul can reject proofless verifier output -without confusing that failure with path scope or family assertions: -`.epiphany-smoke\repo-closure-source-grounding-20260620-230626` ran two -`repo-status-section` items through init -> online -> accept -> derive-plan -> -tick run/adopt/execute. The default close with `--require-source-grounding` -closed successfully because `git show --stat` cited the changed `README.md`. -The custom close with `--verification-command "Write-Output 'ok'"` -also exited successfully, kept `pathScopeMatched=true` and -`familyAssertions.status=passed`, but returned `verification-failed`, Soul -`failed`, and `verificationSourceGrounding.status=failed` because the verifier -output did not cite the changed path; `privateStateExposed=false`. - -A Mind-adoption closure guard smoke proved Soul now verifies that the Hands -commit being closed still descends from an accepted Mind adoption decision: -`.epiphany-smoke\repo-close-mind-adoption-guard-20260620-201153` ran a -disposable repo through plan -> run -> adopt -> execute, then pointed a copied -execute receipt at a counterfeit adopt receipt whose embedded -`mindAdoptionDecision.status` and `actionItemAccepted` were tampered. The -tampered close returned `verification-failed`, Soul `failed`, -`mindAdoptionReview.status=failed`, and `mindAdoptionPassed=false`; the original -execute/adopt chain then closed with Soul `passed`, -`mindAdoptionReview.status=passed`, and `privateStateExposed=false`. - -The same smoke was extended as the first durable repo-map admission proof: -`.epiphany-smoke\repo-close-mind-adoption-guard-20260620-202113` proved the -tampered failed close did not write `.epiphany/state/repo-work-map.msgpack`; the -successful close wrote `epiphany.repo_work_map_store.v0` with an -`epiphany.repo_work_map_entry.v0` for the item, `durableStateAdmitted=true`, -the expected commit SHA, changed path, safe family, Soul verdict id, Mind -gateway id, Mind state-commit id, and `privateStateExposed=false`. - -The same smoke now proves the first local Verse projection of that durable map: -`.epiphany-smoke\repo-close-mind-adoption-guard-20260620-203717` shows successful -close projecting `epiphany.cultmesh.repo_work_map_entry.v0` with latest key -`gamecult-local/repo-work-map/latest`; direct Gjallar readback returned -`repoWorkMapCount=1`, latest `repo-work-map-repo-close-mind-adoption-guard`, a -compact `REPO-WORK-MAP` row with the safe family, Soul/Mind ids, Bifrost gate, -source map store ref, and `privateStateExposed=false`. The cargo-run smoke -itself completed and wrote `summary.json` after the outer tool timeout, so the -independent Gjallar readback is the clean terminal proof. - -The next smoke tightened that map sight into the first semantic lens: -`.epiphany-smoke\repo-close-mind-adoption-guard-20260620-205055` proves Gjallar -now derives `repoWorkMapFamilyLensRows` and `REPO-WORK-MAP-LENS` TUI rows from -Mind-admitted map entries, grouping by safe action family with item count, -changed-path count, latest item, latest commit, latest Mind commit receipt, -Bifrost gate, and `privateStateExposed=false`. This lens is still read-only -Gjallar sight; it does not own scheduling, publication, merge, deployment, or -Mind admission. - -The following smoke added a Body-surface lens: -`.epiphany-smoke\repo-close-mind-adoption-guard-20260620-205740` proves Gjallar -now derives `repoWorkMapPathLensRows` and `REPO-WORK-MAP-PATH` TUI rows from -the same Mind-admitted map entries, grouping by changed path with item count, -safe-family list, latest item, latest commit, latest Mind commit receipt, -Bifrost gate, and `privateStateExposed=false`. This lets agents see which repo -paths have accepted branch-local consequences without opening private receipts -or giving Gjallar any actuation authority. - -The next aggregate lens made the branch workbench explicit: -`.epiphany-smoke\repo-close-mind-adoption-guard-20260620-210747` proves Gjallar -now derives `repoWorkMapBranchLensRows` and `REPO-WORK-MAP-BRANCH` TUI rows from -the same Mind-admitted map entries, grouping by branch with item count, -changed-path count, safe-family list, latest item, latest commit, latest Mind -commit receipt, Bifrost gate, and `privateStateExposed=false`. This lets agents -see which branch owns accepted consequences without making Gjallar a scheduler, -publisher, deployer, daemon keeper, or Mind admission organ. - -The next semantic lens names the work stage and owning organ: -`.epiphany-smoke\repo-close-mind-adoption-guard-20260620-232414` proves Gjallar -now derives `repoWorkMapStageLensRows` and `REPO-WORK-MAP-STAGE` TUI rows from -the same Mind-admitted map entries, grouping safe families into stages such as -`imagination-planning`, `repo-body-collaboration`, `mind-adoption-request`, -`self-scheduling-request`, `hands-execution-request`, -`soul-verification-request`, `bifrost-publication-accounting`, -`repo-governance`, and `idunn-deployment`. The proof row for -`repo.markdown_planning_note` reports `stage=imagination-planning`, -`owner=Imagination`, latest item/commit/Mind commit receipt, Bifrost gate, and -`privateStateExposed=false`. This is semantic sight only; it does not schedule, -publish, merge, deploy, keep daemons alive, or admit Mind state. - -The next gate lens names who owns the publication gate without turning Gjallar -into that owner: `.epiphany-smoke\repo-close-mind-adoption-guard-20260620-233353` -proves Gjallar now derives `repoWorkMapGateLensRows` and -`REPO-WORK-MAP-GATE` TUI rows from Mind-admitted map entries. The proof row for -the accepted planning-note work reports `gate=Bifrost`, `owner=Bifrost`, -`sightOnly=true`, latest item/commit/Mind commit receipt, and -`privateStateExposed=false`; the smoke summary reports -`gjallarRepoWorkMapGateLensCount=1` while `publicationAuthorized=false`. This -is next-gate sight only. Bifrost still owns publication/credit/upstream gates, -and Gjallar does not schedule, publish, merge, deploy, or admit state. - -The next per-entry semantic row gives agents one compact accepted-work readback: -`.epiphany-smoke\repo-close-mind-adoption-guard-20260620-234131` proves Gjallar -now derives `repoWorkMapSemanticRows` and `REPO-WORK-MAP-SEMANTIC` TUI rows from -Mind-admitted map entries. The proof row mirrors the durable modeling summary, -safe family, stage `imagination-planning`, `stageOwner=Imagination`, -`gate=Bifrost`, `gateOwner=Bifrost`, latest commit/Mind commit receipt, -`sightOnly=true`, and `privateStateExposed=false`; the smoke summary reports -`gjallarRepoWorkMapSemanticCount=1` while closure still refuses counterfeit Mind -adoption and keeps `publicationAuthorized=false`. This is compact meaning -readback only, not a scheduler, publisher, merge gate, deployment actor, daemon -keeper, or Mind admission path. - -The next per-entry closure row makes the Soul/Modeling/Mind closure artery -visible without opening sealed receipt bodies: -`.epiphany-smoke\repo-close-mind-adoption-guard-20260621-075522` proves Gjallar -now derives `repoWorkMapClosureRows` and `REPO-WORK-MAP-CLOSURE` TUI rows from -Mind-admitted map entries. The proof row mirrors the accepted item, safe family, -branch, latest commit, changed-path count, Soul verdict receipt id, Mind gateway -review id, Mind state commit id, durable map admission, Bifrost publication -gate, `sightOnly=true`, and explicit denials for publication, merge, service -lifecycle, deployment execution, and cross-repo mutation while keeping -`privateStateExposed=false`; the smoke summary reports -`gjallarRepoWorkMapClosureCount=1` after counterfeit Mind adoption is still -refused. This is closure readback only, not publication authority, merge -authority, service lifecycle authority, deployment authority, cross-repo -authority, or Mind admission authority. - -The family-friendly acceptance lens names the closure state in one operator-safe -row: -`.epiphany-smoke\repo-close-mind-adoption-guard-20260621-082322` proves Gjallar -now derives `repoWorkMapAcceptanceRows` and `REPO-WORK-MAP-ACCEPTANCE` TUI rows -from Mind-admitted map entries. The proof row reports -`status=accepted-awaiting-publication-gate`, `soulClosed=true`, -`modelingClosed=true`, `mindCommitted=true`, `durableAdmitted=true`, -`publicationGate=Bifrost`, `bifrostGateNamed=true`, `sightOnly=true`, and -`privateStateExposed=false`; the smoke summary reports -`gjallarRepoWorkMapAcceptanceCount=1` and -`gjallarRepoWorkMapAcceptanceStatus=accepted-awaiting-publication-gate` after -counterfeit Mind adoption is still refused. Wrapper proof -`local-20260621-092458-019-7ba89841` pointed `gjallar` at the repo-local Verse -and printed the same compact acceptance row. This is acceptance sight only: -Bifrost still owns publication/upstream gates, and Gjallar does not schedule, -publish, merge, deploy, keep daemons alive, cross repo boundaries, or admit Mind -state. - -The wrapper mouth now forwards those Rust-owned repo-work map rows instead of -making agents parse JSON artifacts. `tools/epiphany_local_run.ps1` accepts -`-LocalVerseStore` and `-LocalVerseRuntimeId`, keeps operator run -intent/receipt writes in the operator local Verse, and points `gjallar` / -`swarm-triage` readback at the explicit repo-local Verse store. Live wrapper -proofs `local-20260621-011151-541-70056545` (`gjallar`) and -`local-20260621-011207-755-d65e5c7e` (`swarm-triage`) read the -`repo-planning-brief-family-smoke` repo store and printed actual -`repoWorkMapRows`, `repoWorkSemanticRows`, `repoWorkFamilyLensRows`, -`repoWorkPathLensRows`, `repoWorkBranchLensRows`, `repoWorkStageLensRows`, and -`repoWorkGateLensRows` in the one-line operator summary. Follow-up wrapper proof -`local-20260621-085736-645-72c217a6` pointed `gjallar` at the closure-row smoke -repo-local Verse and printed `repoWorkClosureRows` with -`publicationAuth=false`, `mergeAuth=false`, `serviceAuth=false`, -`deploymentAuth=false`, `crossRepoAuth=false`, `sightOnly=true`, and -`private=false`. Earlier wrapper proofs -with no explicit repo-local store rendered `none`; that was a targeting wound, -not absence of accepted repo-work. PowerShell does not reinterpret the rows or -own schema registration; it only lowers existing Rust TUI arrays while leaving -repo-work truth in the repo-local CultMesh store. - -`epiphany-work overview` is the first compact repo work sight/proof surface: - -```powershell -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-work -- overview --workspace --item -``` - -It reads the accepted item plus plan, run, adopt, execute, close, publish, and -sync receipts, asks git for the current branch, computes the current gate, -blocker, and next safe action, and writes -`.epiphany/work/work-overview-.json` as -`epiphany.repo_work_overview_receipt.v0`. The receipt carries compact -agent-friendly rows plus an operator-safe proof bundle. The proof bundle is -`epiphany.repo_work_proof_bundle.v0`; it carries bundle id, generated time, -workspace, item, branch, current gate, blocker, next safe move, changed paths, -commit SHA, Soul verdict, Mind state-commit id, admitted external publication -evidence when present, upstream-main sync status, compact TUI rows, and -`privateStateExposed=false`. Its `artifactRows` enumerate the expected accept, -plan, run, adopt, execute, close, externally admitted publication, and sync receipts with expected path, -present/missing status, document schema, document status, SHA-256 hash when -present, and private-state seal. Its `publicationRows` lift publication-stage -proof into compact rows: Bifrost intent/publication/GitHub/ledger/credit/PR -fields, Hands commit/PR fields, and upstream-main ancestry fields when admitted -provider or sync receipts exist. When the accept receipt names a local Verse store, -overview also mirrors the compact overview rows as -`epiphany.cultmesh.repo_work_overview.v0` under -`gamecult-local/repo-work-overview/latest`, so Eve/Gjallar/Odin sight can read -the same typed surface without opening the `.epiphany/work` artifact body. - -This is sight, not scheduling. The authority owner is `Eyes/Gjallar`, with -`sightOnly=true`; it does not publish, merge, mutate services, cross repo -boundaries, repair missing gates, or expose private worker thought. - -The first overview smoke proved both sides of the sight surface. Immediately -after accept, overview reported `currentGate=awaiting-plan`, -`blocker=plan-receipt-missing`. After `derive-plan -> run -> adopt -> execute --> close`, overview reported `currentGate=awaiting-publication`, -`blocker=bifrost-publication-missing`, branch -`epiphany/repo-work-overview-.../first-awakening`, a present commit SHA, -changed path `EPIPHANY_WORKLOG.md`, Soul verdict `passed`, compact rows for -item/branch/gate/blocker/closure/publication/sync/private, an overview receipt -artifact, and `privateStateExposed=false`. - -The first hashed proof-bundle smoke extended the same closed run artifact: -`.epiphany-smoke\tick-close-20260620-025526\08-overview-proof-bundle.json`. -It reported `schemaVersion=epiphany.repo_work_proof_bundle.v0`, TUI rows for -`awaiting-publication` / `bifrost-publication-missing`, six present artifacts -from accept through close with SHA-256 hashes, missing publish/sync artifacts, -and `privateStateExposed=false` on every row. - -The first published proof-bundle smoke extended the checklist safe-family proof: -`.epiphany-smoke\checklist-note-20260620-031347\12-overview-published-proof.json`. -After close, publish, and sync receipts, overview reported -`currentGate=complete-or-awaiting-new-work`, `upstreamMainSynced=true`, and -three `publicationRows`: Bifrost publication/ledger/credit row, GitHub Hands PR -row, and upstream-main ancestry row. The sync truth is read from -`authority.upstreamMainSynced`, and every row keeps `privateStateExposed=false`. - -`epiphany-work export-proof` is the first public/export packaging surface over -that same proof bundle: - -```powershell -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-work -- export-proof --workspace --item -``` - -It calls overview with receipt writing enabled, distills the local -`epiphany.repo_work_proof_bundle.v0` into -`epiphany.repo_work_public_proof_bundle.v0`, and writes the public artifact to -`.epiphany/public/proof-bundles/repo-work-public-proof-.json` unless an -explicit `--output` is supplied. The public proof keeps ids, gate/blocker/next -safe move, branch, changed paths, commit SHA, Soul verdict, Mind/Bifrost/GitHub -publication ids, upstream-main sync truth, compact publication rows, compact -TUI rows, artifact schema/status/hash rows, and `privateStateExposed=false`. -It deliberately drops local receipt paths and expected paths, raw receipt -bodies, worker thought, private Verse contents, and any publication authority -claim. This is still Eyes/Gjallar export sight; Bifrost owns actual public -publication, labor ledger, and credit consequence. - -The first public/export proof smoke extended the same checklist proof: -`.epiphany-smoke\checklist-note-20260620-031347\13-export-proof.json`. It -reported `schemaVersion=epiphany.repo_work_public_proof_bundle.v0`, wrote the -default public proof path, carried eight artifact rows and three publication -rows, exposed zero artifact path fields, kept `rawReceiptBodies=false`, and -reported `private=false`. - -The first public-proof Verse transport smoke made that export discoverable -through local CultMesh/Gjallar sight without handing publication authority to -the exporter. `epiphany-work export-proof --local-verse-store \.epiphany\local-verse.ccmp` -now writes `epiphany.cultmesh.repo_work_public_proof.v0` under -`gamecult-local/repo-work-public-proof/latest`, carrying item, gate, branch, -commit, artifact/publication row counts, upstream-main sync truth, public proof -artifact ref, SHA-256, compact `PUBLIC-PROOF` TUI rows, and -`privateStateExposed=false`. `epiphany-verse-query swarm-overview` reads that -history and emits `repoWorkPublicProofRows`, `repoWorkPublicProofTuiRows`, -`latestRepoWorkPublicProof`, plus non-mutating priority 60+ -`repo-work-public-proof` action rows with `authorityGate=repo.work.public_proof_readback`, -`effectClass=repo-work-public-proof-readback`, `mutatesState=false`, and -`requiresElevatedAuthority=false`. Smoke artifacts -`.epiphany-smoke\checklist-note-20260620-031347\14-export-proof-verse.json` -and `15-swarm-overview-public-proof.json` proved latest -`repo-work-public-proof-checklist-request`, SHA-256 -`e781e09c2ba340c8818d3ef95e54085ccd0c92c6a3b5f73cc4878fb224e49dff`, one -public proof row, one public proof action row, no mutation/elevation, and -`private=false`. - -Repo-work readiness sight now packages the same proof trail into a reviewable -MVP readiness report without approving readiness. `epiphany-work readiness` -inspects repo init, swarm online, Persona intake, Imagination plan, Self -queue-run, Hands branch work, Soul closure, Modeling/Mind admission, -safe-family planning readback, redacted public proof, Bifrost publication, -upstream-main sync, Idunn lifecycle, daemon deployment aftercare, tool directory, -and private-state redaction rows, then writes -`.epiphany/work/work-readiness-.json` as -`epiphany.repo_work_readiness_report.v0`. When the accept receipt names a -repo-local Verse store, it also mirrors a compact -`epiphany.cultmesh.repo_work_readiness.v0` document with `REPO-WORK-READINESS` -TUI rows, missing proof kinds, readiness receipt ref, overview receipt ref, and -explicit sight-only authority denials. Gjallar, swarm overview, and swarm triage -now enumerate those documents as `repoWorkReadinessRows`, compact readiness TUI -rows, `latestRepoWorkReadiness`, and non-mutating `repo-work-readiness` action -rows. The command is sight only: it denies readiness approval, durable state -commit, publication, PR, merge, sync, deployment, service lifecycle, Hands -action, cross-body mutation, private Verse rummaging, and private-state -exposure. Smoke `.epiphany-smoke\repo-work-readiness-20260621-102528` now drives -the proof through `repo-planning-brief` cargo so `safe-family-planning` is -satisfied from Soul's `epiphany.repo_work_safe_family_planning_readback.v0` -with `candidateNextSafeFamilyCount=21`, all matrix/gate/requirement/closure -flags present, authority denied, and private state sealed. The same proof -satisfies deployment-aftercare from a sealed -`epiphany.repo_deployment_aftercare_audit.v0` artifact. The next -proof-bundle cut made Idunn and global tool sight receipt-shaped instead of -path-shaped: readiness now validates `.epiphany/work/repo-work-service-audit.json` -or `--idunn-lifecycle-receipt` as `epiphany.repo_work_service_audit.v0`, -requiring `status=complete`, Idunn ownership, hosted body `repo-work`, present -plan/runbook/artifact proof, `launchStatus=ok`, zero missing/failed checks, no -service-manager mutation, no elevation requirement, and -`privateStateExposed=false`; it also validates `.epiphany/work/tool-directory.json` -or `--tool-directory-receipt` as -`epiphany.cultmesh.daemon_tool_directory.v0`, requiring nonempty globally -available receipt-gated capabilities, ready hosts, typed direct/wrapper -invocation commands, and the private-state seal. It now validates -`bifrost-publication` from the local publish artifact plus matching repo-local -CultMesh `gamecult.bifrost.body_change_publication_receipt.v0` and -`gamecult.bifrost.github_publication_receipt.v0` rows, requiring matching -Bifrost/GitHub receipt ids, ledger id, Hands PR receipt id, PR URL, nonempty -credit and changed-path evidence, no upstream-main sync claim, no merge claim, -and no private-state exposure. The `upstream-main-sync` row now validates -`epiphany.repo_work_sync_receipt.v0` plus live `git merge-base --is-ancestor` -proof, requiring the published commit, upstream ref, upstream commit, nonempty -merge/sync authority receipts, Hands PR/commit ids, Bifrost/GitHub publication -ids, ledger id, and matching publish receipt. Native smoke -`.epiphany-smoke\repo-work-readiness-20260621-110524` now reports `ready` with -`missingRequiredCount=0`, `syncStatus=upstream-main-synced`, -`upstreamMainSynced=true`, readiness id `repo-work-readiness-repo-work-readiness`, -and Gjallar `repoWorkReadinessCount=1` without private-state exposure. Wrapper -proof `local-20260621-120641-609-72d32f64` prints the same no-gap readiness -mouth. This is still sight only: Bifrost/GitHub keep publication/sync ownership, -Idunn keeps lifecycle ownership, Odin/Gjallar keep discovery/sight, and no -readiness approval, publication, sync, service mutation, Hands action, or -private-state authority is granted. - -`epiphany-work readiness-review` is now the explicit readiness adoption gate -after the sight receipt is ready. It consumes the ready -`epiphany.repo_work_readiness_report.v0`, requires four explicit review refs -from Maintainer, Soul, Mind, and Bifrost, refuses unless status is `ready`, -`missingRequiredCount=0`, every row is satisfied, the original readiness report -remained sight-only, and all dangerous authority stayed denied. It writes -`epiphany.repo_work_readiness_review_receipt.v0` with -`status=readiness-approved`, `readinessApprovalAuthorized=true`, and explicit -false authority for durable state commit, publication, PR, merge, upstream sync, -deployment, service lifecycle, Hands action, cross-body mutation, private Verse -rummaging, and private-state exposure. Native smoke -`.epiphany-smoke\repo-work-readiness-20260621-111411` proves the full ready -chain through the review receipt with sixteen satisfied rows. Wrapper proof -`local-20260621-121829-442-d011af09` invokes -`tools/epiphany_local_run.ps1 -Mode repo-work-readiness-review` and prints the -same approval boundary at the operator mouth. - -The readiness-review adoption receipt is now indexed by the compact receipt -directory too. `epiphany-verse-query receipt-directory` reads the latest -`epiphany.cultmesh.repo_work_readiness_review.v0` row from local Verse and -publishes the `repo-work-readiness-review` family with owner -`Maintainer/Soul/Mind/Bifrost`, status `readiness-approved`, the readiness -receipt as route, the review receipt artifact ref/SHA-256, the follow-up wrapper -`tools/epiphany_local_run.ps1 -Mode repo-work-readiness-review`, and -`privateStateExposed=false`. The wrapper `receipt-directory` mode now respects -explicit `-LocalVerseStore` / `-LocalVerseRuntimeId` read targets for this row -while still writing operator run receipts to the operator store. Native proof -`.epiphany-smoke\repo-work-readiness-20260621-115547` reports -`receiptDirectoryReadinessReviewRow.family=repo-work-readiness-review`, -`latestId=repo-work-readiness-review-repo-work-readiness`, -`artifactStatus=present`, and SHA-256 -`ba50bce3aa2af320a71977bde9d47108154665802dd527dd30bf8c4ab28bba5e`; -wrapper proof `local-20260621-125756-748-03710f48` prints the same row from the -repo-local Verse target. This is public-proof indexing only: it grants no -durable-state commit, publication, PR, merge, upstream sync, deployment, service -lifecycle, Hands action, cross-body mutation, or private-state authority. - -`epiphany-verse-query bifrost-public-proof --public-proof-id ` selects an -existing redacted `epiphany.cultmesh.repo_work_public_proof.v0` row and records -only the requester-owned pending publication intent. It does not accept or -write Bifrost ledger, review, credit, publication, or receipt claims. Provider -absence remains missing. Epiphany has no local `bifrost-ledger`, accounting -closure, or wrapper mode that can convert document presence into provider -acceptance. A future provider ingress must authenticate provider identity and -contract, bind transport/session and correlation to the exact payload hash and -document id, identify the target Verse/runtime, and persist an admission and -verification witness before any provider response can close the request. - -The first Verse projection smoke proved the local CultMesh sight path: overview -mirrored `repo-work-overview-verse-overview-request` into the repo-local Verse, -`epiphany-verse-query smoke --store --runtime-id repo-swarm-local` -read `latestRepoWorkOverview=repo-work-overview-verse-overview-request`, -`latestRepoWorkGate=awaiting-publication`, -`latestRepoWorkBlocker=bifrost-publication-missing`, saw seven Eve surfaces, -and reported `privateStateExposed=false`. - -The first Gjallar projection smoke proved the latest-key global sight path: -`epiphany-verse-query gjallar --store \.epiphany\local-verse.ccmp ---runtime-id repo-swarm-local` read the same typed overview and emitted -`repoWorkOverviewCount=1`, latest gate/blocker fields, a compact `REPO-WORK` -TUI row, and a priority 35 `repo-work-overview` action row with -`owner=Gjallar`, `hostedBody=repo-work`, `authorityGate=repo.work.overview`, -`mutatesState=false`, `requiresElevatedAuthority=false`, and -`privateStateExposed=false`. Proof artifact: -`.epiphany-smoke\gjallar-repo-work-overview-20260620-011119`. This is still -latest-key sight, not multi-item queue enumeration. - -The next Gjallar projection smoke proved the typed repo-work history/queue: -`epiphany-verse-query gjallar --store \.epiphany\local-verse.ccmp ---runtime-id repo-swarm-local` now loads all -`epiphany.cultmesh.repo_work_overview.v0` event documents, excludes the -`latest` mirror, preserves latest scalar fields for compatibility, and emits -multiple `repoWorkOverviewRows`, compact `REPO-WORK` TUI rows, and bounded -priority 35-39 non-mutating `repo-work-overview` action rows. Proof artifact: -`.epiphany-smoke\gjallar-repo-work-queue-20260620-012027`, which saw items -`second,first`, `repoWorkOverviewCount=2`, two action rows, -`latestRepoWorkOverview=repo-work-overview-second`, and -`privateStateExposed=false`. - -The first Eve/Persona lowering smoke proved peer-readable repo-work queue -projection: Persona's public Eve surface now includes queue counts and compact -`REPO-WORK-PEER` rows, direct `connect-eve` returns the same queue, and the -globally invokable Persona `eve-connect` daemon tool embeds the queue in its -`eveConnectionReadback`. Proof artifact: -`.epiphany-smoke\eve-repo-work-queue-20260620-013322`, which saw two queued -items through `eve-surfaces`, `connect-eve`, and `invoke-tool`, with no -mutation, elevation, or private-state exposure. - -The first runnable queue surface is now native too. `epiphany-work queue-run` -reads the typed `epiphany.cultmesh.repo_work_overview.v0` queue from the -repo-local Verse, selects only tick-actionable rows (`ready-to-run`, -`ready-to-adopt`, `ready-to-execute`) for the current repo Body, delegates each -selected item to the existing Self-owned `tick` artery, and refreshes the -overview after a real advancement. It writes -`epiphany.repo_work_queue_run_receipt.v0` as `work-queue-run.json`; it does not -publish, merge, close, install services, mutate another repo, or inspect private -worker thought. `tools/epiphany_local_run.ps1 -Mode repo-work-queue-run` is the -operator wrapper over the same surface. Proofs: -`.epiphany-smoke\queue-run-20260620-014314` selected item `first` from a -two-item queue, advanced `run-from-plan`, refreshed its gate to -`ready-to-adopt`, and left blocked item `second` at `awaiting-plan`; wrapper -artifact `local-20260620-014627-174-aada0241` dry-ran the next selected step as -`adopt-from-plan` with `privateStateExposed=false`. - -## Migration Implication - -The next migration plan must treat autonomous branch-local work as a required -capability, not a later danger to avoid. The safety design is not "make Epiphany -ask before doing anything." The safety design is "make Epiphany's authority -typed, local to its Body, branch-contained, interruptible, inspectable, -receipt-backed, and publication-gated." - -That is the machine we are building. - -## Unbounded Repo Work Doctrine - -"Autonomous unbounded work" means the swarm may keep forming maps, plans, -action items, branch-local consequences, verification receipts, memory updates, -and follow-up work while its owned Body still presents lawful pressure. It does -not mean infinite privilege, cross-Body appetite, public speech without a -mouth-gate, or service lifecycle authority stolen from Idunn. - -For the repo-swarm MVP, unbounded work is bounded by physiology instead of by -constant human permission: - -- Body boundary: the swarm may act only inside the initialized repo Body, - repo-local state stores, repo-local private Verse, owned `epiphany/*` branch, - and advertised public Persona/Eve surfaces. -- Organ boundary: Persona receives pressure, Imagination makes plans, Self - schedules, Substrate Gate scopes access, Hands mutates, Soul verifies, - Modeling updates maps, Mind admits state, Bifrost publishes, Gjallar reports, - Odin discovers, Eve renders, and Idunn keeps daemons alive. -- Step boundary: each scheduler pulse advances at most one receipt-backed safe - branch-local step unless a later typed scheduler contract explicitly widens - the batch rule. -- Brake boundary: local Verse brakes, active-turn markers, completion-anchored - cooldowns, stale-turn recovery receipts, and stop conditions are physiology, - not optional etiquette. -- Publication boundary: branch-local work may continue autonomously; upstream - publication, merge, deployment, privilege escalation, cross-repo mutation, - and authority changes require their owning gates. -- Privacy boundary: raw worker thought, raw result payloads, sealed - transcripts, private Verse documents, and private memory text do not become - Gjallar, Eve, Bifrost, or operator display cargo. - -The swarm should therefore be allowed to work for a long time without asking -for every local edit. The proof of safety is not obedience. The proof of safety -is that every consequence has an owner, a typed path, an interrupt surface, and -a receipt that says what happened without lying about who had authority. - -## Full Migration Plan To Repo Swarm MVP - -This plan starts from the current state: native front doors cover the -branch-local loop, but it is not yet a full physiology. `epiphany-repo init`, -`epiphany-swarm online`, `epiphany-swarm run`, `epiphany-work accept`, -`epiphany-work persona-intake`, `epiphany-work derive-plan`, -`epiphany-work plan`, `epiphany-work run`, `epiphany-work adopt`, -`epiphany-work execute`, `epiphany-work close`, `epiphany-work overview`, -`epiphany-work export-proof`, `epiphany-work tick`, -`epiphany-work queue-run` proves the typed path from repo birth to branch-local -scheduler pulse, typed queue selection, redacted public proof export, and -compact proof-bundle sight. Publication and upstream sync are not repo-local -commands or receipt-writing authorities. -`tick` now also proves brake refusal, active-turn refusal, -completion-anchored cooldown refusal, and stale active-turn recovery through -typed scheduler receipts; `serve` now proves bounded cadence around that same -tick artery; `close` now proves deterministic Soul/Modeling/Mind closure over -Hands commit receipts plus an optional/required model-authored closure verdict -gate; `derive-plan` now proves a deterministic -Persona/Bifrost pressure-to-plan bridge with no operator-authored shell -details; `overview` now proves compact local proof-bundle sight over the -receipt chain; `queue-run` now proves that Self can select tick-actionable rows -from the typed repo-work overview queue without reading private thought or -taking publication/service authority. The chain does not yet prove fully -model-authored Imagination planning beyond allowlisted safe-family lowering, -optional Idunn-hosted lifecycle for queue pulses, or richer published -proof bundles. - -The MVP target is narrower than the full Perfect Machine and wider than a demo: -a fresh repository can host an Epiphany swarm that initializes its Body, -publishes its Persona, accepts ideas through Persona or Bifrost, turns those -ideas into concrete action items through Imagination, schedules branch-local -work through Self, executes through Hands, verifies through Soul, admits state -through Mind, and publishes reviewed outcomes through Bifrost while private -state stays sealed. - -### Migration Thesis - -The migration is not from "agent with scripts" to "agent with more scripts." -It is from Codex-operated proof rites to an Epiphany-owned repo organism: - -```text -typed Body birth - -> local Verse online - -> Persona-facing idea intake - -> Imagination-authored concrete plan - -> Self-owned scheduler physiology - -> Substrate Gate scoped access - -> Hands branch-local consequence - -> Soul verification - -> Modeling map update - -> Mind state admission - -> Gjallar/Eve operator-safe sight - -> Bifrost publication and credit - -> upstream-main sync proof -``` - -The current front doors prove most of that artery in isolated rites. The MVP is -the point where the artery can run as a repo swarm: the human talks to the -project Persona, the swarm turns the idea into work, the branch changes under -typed authority, proof is published without private-state leakage, and upstream -main is proven rather than assumed. - -The swarm is allowed to keep going while work remains lawful. It must not keep -going by forgetting its Body boundary, smearing organ ownership, or treating a -wrapper loop as a daemon soul. Long-running autonomy belongs to Self's -scheduler physiology and, when installed as a service, to Idunn-owned lifecycle -aftercare around that same scheduler pulse. - -### Functional Swarm Answer - -Yes: the machine is headed toward a functional repo swarm, and the current -front doors are no longer decorative. They already prove repo birth, online -local Verse projection, Persona/Bifrost intake, Imagination action-item -receipts, branch-local Hands execution, deterministic Soul/Modeling/Mind -closure, Gjallar-visible proof rows, pending publication intent, redacted -public-proof export, and upstream-main ancestry proof after explicit merge -authority and admitted external provider evidence. - -No: the machine is not yet Epiphany Online in the full MVP sense. The remaining -gap is not "more permission prompts." The remaining gap is to make the -existing typed artery run as a living repo organism: - -- further non-planning action classes where Imagination can propose useful - branch-local work beyond planning cargo without arbitrary shell authority -- an Idunn-owned service lifecycle path for the queue-run pulse when the - operator explicitly grants service mutation or elevated host authority -- richer adoption/interpreter depth after model-authored planning facets, without - turning typed Imagination cargo into shell authority - -The MVP is functional when a human can talk to the repo Persona, the swarm can -turn that pressure into typed work, keep advancing lawful branch-local steps -under Self/Hands/Soul/Mind receipts, stop at Bifrost publication gates, export -operator-safe proof, and finally prove sync to upstream main after maintainer -or Bifrost merge authority. That is autonomy with organs, not autonomy as a -single hungry prompt. - -### MVP Status Board - -Use this board to decide whether a new slice belongs in the MVP or is a later -temptation wearing clean robes. - -| Surface | Current status | MVP migration requirement | -| --- | --- | --- | -| Repo Body birth | `epiphany-repo init` exists and writes repo-local stores plus branch workbench intent. | Keep birth startup-only, review-gated, and branch-oriented. | -| Local Verse online | `epiphany-swarm online` seeds repo-local CultMesh, standing-faculty SoA, topology, liveness, Eve, and tool sight. | Keep private Verse sealed while exposing operator-safe repo status. | -| Persona/Bifrost intake | `epiphany-work accept` records pressure and candidate action refs without Hands authority. `epiphany-work persona-intake` now invokes the Persona bubble speech-audit path, records public discussion and candidate-action refs, then delegates to `accept`; wrapper mode `repo-persona-intake` exposes the operator mouth. | Deepen the intake-to-Imagination interpreter so richer model-authored action items can be proposed without granting Hands, publication, or durable-state authority at the mouth edge. | -| Imagination planning | The catalog-shaped `repo.planning_brief` family is deleted because it had no candidate work-item records. Consensus Draft, Interpreter Brief request, and Objective Draft remain typed preparation cargo before Mind adoption. | Future plan artifacts must carry actual candidate records and reference separately owned catalog/contracts. | -| Self scheduling | `tick` and `serve` prove one-step branch-local advancement, brake refusal, active-turn refusal, cooldown, and stale-turn recovery; `tick` now routes executed branch-local work through the existing Soul/Modeling/Mind `close` gate; `queue-run` selects tick-actionable rows from the typed repo-work queue and delegates to `tick`; `epiphany-swarm run` is the bounded operator mouth over that queue/tick physiology; `repo-work-service-plan`, `repo-work-service-runbook`, `repo-work-service-launch`, and `repo-work-service-audit` write Idunn lifecycle receipts/artifacts for the same queue-run command and prove non-mutating launch/readiness closure. | Keep any future queue-run service install/start mutation behind Idunn and explicit elevated operator authority. | -| Branch-local Hands work | `adopt` and `execute` create approved Hands gates, run planned commands, stage declared paths, commit on `epiphany/*`, and write receipts. | Keep mutation branch-contained and receipt-backed; broaden only through typed plan families, not ad hoc shell freedom. | -| Soul/Modeling/Mind closure | `close` verifies the Hands commit, writes `epiphany.repo_work_closure_review.v0`, refuses path-scope mismatches, refuses missing/tampered/non-affirmative Mind adoption proof, runs known safe-family content assertions, optionally gates on model-authored closure verdicts, can require verification output to cite declared changed paths with `--require-source-grounding`, writes Soul/Modeling/Mind receipts, admits a compact typed repo map entry into `.epiphany/state/repo-work-map.msgpack`, and mirrors that admitted fact into local Verse as `epiphany.cultmesh.repo_work_map_entry.v0`. | Keep extending closure toward source-grounded semantic review and richer semantic map lenses, while preserving deterministic local closure for simple mechanical work. | -| Repo work sight/review | `overview` emits compact proof rows, an `epiphany.repo_work_intake_consensus_readback.v0`, and typed `epiphany.cultmesh.repo_work_overview.v0` documents; `readiness` emits `epiphany.repo_work_readiness_report.v0` and mirrors typed readiness sight without granting approval; `readiness-review` records requester/reviewer evidence without impersonating provider closure; `receipt-directory` exposes pending intents and missing provider responses; `close` mirrors Mind-admitted map facts; Gjallar enumerates repo-work projections without private-state exposure; Persona's Eve surface exposes peer-readable gate/blocker/next-action rows; `queue-run` consumes the same overview queue. Epiphany has no local Bifrost ledger or accounting closure. | Add authenticated provider ingress before treating Bifrost/GitHub responses as accepted; otherwise extend repo-owned safe families or explicit elevated Idunn execution under operator authority. | -| Publication | Requester-owned intents remain pending; no local command routes or mints Bifrost/GitHub receipts. | Add authenticated provider ingress without moving publication authority into the scheduler. | -| Upstream main sync | No local command mints upstream-sync or merge truth. | Admit authenticated provider/maintainer sync evidence and verify substrate ancestry without treating ancestry alone as authority. | -| Daemon survival | Idunn service lifecycle receipts and runbooks exist outside repo-work tick authority. | Preserve Idunn as lifecycle owner; repo swarm may request or inspect service state, not impersonate daemon keeping. | -| Global tools | The daemon tool directory exposes globally invokable typed capabilities with host-owned execution receipts. | Ensure any agent can discover and request any authorized daemon-hosted tool through CultMesh without moving execution ownership into the caller. | - -### Epiphany Online Cut Order - -"Epiphany online" for the repo-swarm MVP means more than `init` plus a healthy -status display. It means a repo Body can keep lawful work moving through its -own organs after the operator gives it a project-shaped idea, while the local -brakes, branch containment, organ ownership, private-state seals, and -publication gates still bite. - -Build the remaining MVP in this order: - -1. **Idunn-hosted queue-run lifecycle.** Keep `epiphany-work queue-run` as the - Self-owned branch-local pulse, but let Idunn publish the service plan, - runbook, audit, and optional elevated installation/start path for that same - pulse. The non-mutating plan/runbook/launch/audit artery is landed; only the - operator may grant service-manager mutation or elevated startup. Self must - not become the daemon keeper. This first cut is landed through - `tools/epiphany_local_run.ps1 -Mode - repo-work-service-plan`, `-Mode repo-work-service-runbook`, and `-Mode - repo-work-service-launch`, plus `-Mode repo-work-service-audit`. Native - smoke `.epiphany-smoke\repo-work-service-lifecycle-20260621-004202` proves - the exact queue-run command vector with `--dry-run`, a written runbook - receipt, receipt-directory readback before launch, a bounded - `service-launch --wait-child` receipt with exit code 0, then a - `repo-work-service-audit` receipt with `auditStatus=complete`, - `auditRunbookArtifactStatus=present`, no missing or failed checks, no - service-manager mutation, no elevation, and `privateStateExposed=false`. -2. **Persona-to-Imagination action items.** The first repo Persona intake mouth - is landed: `epiphany-work persona-intake` routes work-shaped speech through - the Persona bubble speech-audit path, records public discussion refs and - candidate action refs, then delegates to `accept`; wrapper mode - `repo-persona-intake` exposes the operator surface. `derive-plan` now emits - typed Imagination action-item receipts with allowed safe families, requested - paths, verification asks, stop conditions, escalation reasons, planning - facets, model provenance, and no Hands/publication/durable-state authority - before it lowers the chosen safe family into a plan receipt. `adopt` now - records the chosen action item and planning facets in the Mind adoption - artery before granting branch-local Hands authority. `adopt` now writes a - separate Mind interpreter decision receipt, embeds an - `epiphany.repo_work_mind_interpretation.v0` packet that classifies the action - item, requested paths, recognized safe family, allowed Hands transition, and - forbidden durable-state/publication/service/private transitions, then cites it - from the main adoption receipt. The first deeper interpreter guard is now - live: if a plan-derived action item presents a safe-family label outside the - repo-work allowlist, Mind writes a refused adoption decision and grants no - Hands, durable-state, service-lifecycle, publication, cross-body, or - private-state authority. Proof - `.epiphany-smoke\repo-mind-safe-family-guard-20260620-190811` tampers a - planning note into `idunn.service_lifecycle`, receives - `refused-unsupported-safe-family`, and writes no adoption receipt. Banter - remains banter unless Mind/Interpreter extracts a work candidate. Mind also - refuses plan-derived work that lacks explicit verification asks or planning - evidence needs before Hands authority opens: tamper proof - `.epiphany-smoke\repo-mind-evidence-guard-20260620-224426` receives - `refused-missing-evidence-needs`, points the next gate at - `imagination.replan_with_explicit_soul_evidence_needs`, grants no - Hands/durable/service/publication/private authority, and writes no adoption - receipt. Regression proof - `.epiphany-smoke\repo-planning-facets-20260620-224428` still closes normal - planning facets through Soul/Modeling/Mind. The remaining cut is richer - model-authored families and deeper interpreter semantics beyond the current - refusal gates, not basic action-item or adoption receipt shape. -3. **Model-authored safe plan families.** Promote `derive-plan` beyond the - deterministic `append-worklog` and `planning-note` reliquaries by letting - Imagination author typed plans over allowlisted repo-local families. Shell - text should be a derived Hands packet inside a known family, not arbitrary - model string cargo wearing a purity seal. -4. **Repo swarm run front door.** `epiphany-swarm run --workspace - --until blocked-or-published` is now the bounded operator mouth over the - existing queue-run/tick physiology. It advances one safe receipt-backed step - per queue-run pulse, writes `epiphany.repo_swarm_run_receipt.v0`, and stops - at dry-run preview, blocked/noop queue state, or the configured iteration - limit without publishing, merging, installing services, crossing repo - boundaries, elevating authority, or exposing private state. It now also - reaches Soul/Modeling/Mind closure through the same tick artery when a queue - row is `awaiting-closure`. It now writes - `epiphany.repo_swarm_run_stop_classification.v0` into the run receipt so - agents can see stop category, owner, authority gate, blocker, recommended - command, mutability/elevation flags, queue status, and private-state seal - without parsing prose. Fresh proof now covers empty-queue, dry-run - planning-preview, Bifrost publication, and Bifrost/GitHub upstream-sync - stops; remaining stop-depth work belongs to service lifecycle cases and - safe-family depth. -5. **Execute-to-close handoff.** Let the scheduler route from branch-local - Hands execution into Soul/Modeling/Mind closure when the required execute - receipts exist. The closure may be deterministic for mechanical work and - model-authored for source-grounded review, and `--require-source-grounding` - now lets Soul reject verification output that does not cite the changed Body - paths even when the command exits successfully. The Mind admission receipt is - still the durable-state gate. -6. **Operator-safe proof bundles.** Package each work item as compact - maintainer evidence: item, branch, changed paths, Hands receipts, Soul - verdict, Modeling map update, Mind admission, Bifrost/GitHub refs, credit - refs, upstream-main sync status, and `privateStateExposed=false`. -7. **Fresh-repo acceptance proof.** Run the whole chain on a repository that was - not hand-prepared by the supervising Codex session. The acceptable final - state is an Epiphany branch with real commits, a proof bundle, a publication - path, and explicit upstream-main sync proof after maintainer/Bifrost merge - authority. - -This cut order deliberately does not make Gjallar the whole Verse owner or -Idunn the work planner. Gjallar reports what can be advertised. Odin discovers -where surfaces live. Idunn keeps daemon physiology alive. Epiphany becomes -online when the repo swarm can use those organs without stealing their thrones. - -### Authority Matrix - -The MVP succeeds only if each organ keeps its throne small enough to deserve it. - -| Organ or surface | Owns | Does not own | -| --- | --- | --- | -| Persona | Public conversation, project-facing speech, social pressure intake. | Durable state, direct repo mutation, publication, daemon lifecycle. | -| Imagination | Future-shape, concrete action plans, candidate work decomposition. | Shell execution, verification verdicts, state admission. | -| Self | Scheduling, routing, one-step branch-local advancement, active-turn/cooldown physiology. | Publication, merge, daemon service control, cross-repo mutation. | -| Substrate Gate | Scoped repo access grants and refusals. | Durable belief, verification truth, public speech. | -| Hands | Patches, commands, commits, PR receipts within granted branch/path scope. | Claiming correctness, map admission, upstream merge. | -| Soul | Verification verdicts, regression checks, refusal receipts. | Editing the Body, admitting durable state. | -| Modeling | Source-grounded map/checkpoint updates after verified consequence. | Acting on the Body, publishing, accepting itself into memory. | -| Mind | Durable state admission, rejection, and state-commit receipts. | Substrate access, public speech, service survival. | -| Gjallar | Whole-Verse/operator-safe sight rows over advertised state. | Discovery ownership, service lifecycle, mutation, private-state inspection. | -| Odin | Discovery, rendezvous, schema and surface awareness. | Provider ownership, daemon survival, repo mutation. | -| Idunn | Daemon/service lifecycle physiology, runbooks, scheduler service aftercare. | Repo work planning, branch commits, publication. | -| Bifrost | GameCult identity, governed external-world crossings, linked-account association, publication, credit, ledger, merge/sync authority receipts. | Local branch implementation, private thought, daemon survival, outside-provider token custody. | -| Heimdall | Outside-provider OAuth, token custody, linked-account capability/account claims for Bifrost to verify. | GameCult identity, bridge policy, repo execution, publication receipts. | -| Eve/CultUI | Interface projection of typed state. | Source of truth or hidden action authority. | - -### Tool Availability Doctrine - -Every daemon-hosted tool in the local CultMesh network should be available to -any authorized agent at any time through typed capability discovery, invocation -intent, and receipt. This is global ergonomics, not global ownership. - -The hosting daemon owns execution. The caller owns the request. CultMesh carries -the capability surface and the receipt trail. If Hands asks Soul to verify, Soul -still owns the verification receipt. If Persona asks Hands for repo action, -Hands still owns the action gate. If Self asks Idunn for service health, Idunn -still owns the service lifecycle proof. This is how the swarm gets wide tool -reach without turning into one huge permission blob. - -For the repo-swarm MVP, global tool sight is complete enough when: - -- Gjallar/Odin can list daemon-hosted capabilities with host, operation, - authority gate, input contract, receipt contract, Eve surface, and readiness. -- Any standing agent can submit a typed invocation intent for an authorized - capability. -- The host daemon refuses unavailable, unauthorized, or sick-host requests with - typed receipts. -- Invocation results include compact readbacks for common status tools without - opening raw local Verse state or private worker thought. -- No wrapper, caller, or dashboard becomes the executor merely because it can - see the tool. - -### Sync To Upstream Main Doctrine - -Published work is not complete merely because a branch exists, a PR exists, or -a publication receipt exists. A repo swarm's public work item reaches the -upstream-complete state only when: - -- Bifrost or maintainer authority records publication/merge permission. -- The publication receipt names the Bifrost identity and, when the crossing - touches GitHub/Discord/Reddit or another outside provider, the - Heimdall-backed capability/account reference that made the crossing legal. -- The Hands commit being published is known. -- The configured upstream ref, normally `origin/main`, resolves. -- Git ancestry proves the Hands commit is contained by upstream main. -- Authenticated provider/maintainer ingress admits a sync witness bound to the - exact commit and target ref; ancestry then verifies substrate containment. - -Until that proof exists, the compact gate should say publication or sync is -still blocking. Do not let cheerful PR-shaped artifacts impersonate mainline -truth. The Omnissiah can smell stale branches. - -### Current State: The Blessed Chain - -The repo swarm can already prove the following chain on a fresh repo Body: - -```text -repo birth - -> local Verse online - -> Persona/Bifrost work intake - -> derived or manual Imagination/Self action plan receipt - -> Substrate Gate + Hands queued run packet - -> plan-backed branch-local Hands adoption - -> plan-backed branch-local execution and commit - -> deterministic Soul/Modeling/Mind closure - -> compact repo work overview/proof bundle - -> pending publication intent and authenticated provider ingress - -> upstream-main ancestry proof after explicit merge receipt -``` - -The chain is typed and sealed enough to be useful: - -- `epiphany-repo init` writes the repo swarm birth receipt, local state layout, - and branch workbench plan. -- `epiphany-swarm online` seeds repo-local CultMesh state, standing-faculty SoA, - topology, liveness, daemon tool directory, and private-state seals. -- `epiphany-work accept` records Persona/Bifrost pressure without granting - Hands, durable-state, publication, or merge authority. -- `epiphany-work derive-plan` records the first deterministic - Persona/Bifrost-to-plan bridge: accepted pressure becomes a safe allowlisted - action plan with `operatorAuthoredShellDetails=false`. Current families are - `append-worklog`, `planning-note`, `checklist-note`, and `section-note`. -- `epiphany-work plan` records a manual typed Imagination/Self action plan: - objective, command, changed paths, commit message, verification asks, stop - conditions, and rollback hints. It remains a compatibility reliquary until - model-authored Imagination can cover richer action classes. -- `epiphany-work run` opens the Substrate Gate and queues a Hands packet, but - leaves mutation blocked until adoption. -- `epiphany-work adopt --from-plan` converts the queued packet into - branch-local Hands authority using typed plan evidence. -- `epiphany-work execute --from-plan` consumes that authority on an - `epiphany/*` branch, runs the planned command, stages only planned paths, - commits, and records Hands patch/command/commit receipts. -- `epiphany-work close` consumes the execute receipt, verifies the Hands commit, - writes Soul verdict, Modeling summary, and Mind gateway/state-commit receipts, - then seals the closure receipt without granting publication or merge. -- `epiphany-work overview` reads the receipt chain and emits compact - Eyes/Gjallar-owned sight rows plus a proof bundle for current gate, blocker, - next safe action, branch, changed paths, commit, closure, publication, sync, - and private-state seal. -- Publication requests remain pending outside `epiphany-work`; only - authenticated provider ingress may admit Bifrost/GitHub publication receipts. -- Upstream sync remains external until authenticated provider/maintainer - evidence is admitted and git proves the named commit is contained by the - named upstream ref. -- `epiphany-work tick` is the first Self-owned scheduler pulse: it advances one - safe branch-local step across plan-backed `run`, `adopt`, `execute`, or - deterministic Soul/Modeling/Mind `close`, refuses under local Verse brake, - refuses while an active turn is live, refuses during explicit - completion-anchored cooldown, recovers stale active markers, then stops before - Bifrost publication authority. -- `epiphany-work queue-run` is the first queue-aware run surface: it reads the - typed repo-local overview queue, selects only tick-actionable rows for the - current repo Body, delegates to `tick`, refreshes overview after advancement, - and writes a queue-run receipt. Wrapper: - `tools/epiphany_local_run.ps1 -Mode repo-work-queue-run`. -- `epiphany-swarm run` is the repo-swarm operator mouth over that same artery. - It delegates queue selection to `epiphany-work queue-run`, delegates item - actuation to `epiphany-work tick`, records bounded pulse rows in - `repo-swarm-run-receipt.json`, and keeps publication, merge, service - lifecycle, elevation, cross-repo mutation, and private-state exposure sealed. - Wrapper: `tools/epiphany_local_run.ps1 -Mode repo-swarm-run`. -- `epiphany-work serve` is the first Self-owned cadence loop around that pulse: - bounded proof mode records finite iteration outputs, unbounded service mode - relies on per-pulse tick receipts, and zero-interval unbounded polling is - refused. -- `tools/epiphany_local_run.ps1 -Mode repo-work-service-plan`, `-Mode - repo-work-service-runbook`, `-Mode repo-work-service-launch`, and `-Mode - repo-work-service-audit` are the - first Idunn-hosted lifecycle artifacts for the repo-work queue pulse. They - call `epiphany-daemon-supervisor service-plan` / `service-runbook` / - `service-launch --wait-child` / `repo-work-service-audit` with - `--service-command ` and explicit `--service-arg` values for - `queue-run --workspace - --epiphany-root --local-verse-store \.epiphany\local-verse.ccmp - --runtime-id --max-items `. The receipts and runbook describe or - launch the real queue-run command and then audit the typed lifecycle trail, - but do not install a Windows service, publish, merge, cross repo boundaries, - elevate, or expose private state. Native smoke - `.epiphany-smoke\repo-work-service-lifecycle-20260621-004202` now proves the - same Idunn plan/runbook/launch/audit artery without wrapper trust: - plan status `planned`, runbook status `written`, launch status `completed`, - launch exit code `0`, audit status `complete`, audit runbook artifact status - `present`, service id `epiphany-repo-work-queue-runner`, - runbook receipt-directory readback before launch, launch receipt-directory - readback after launch, service args containing `queue-run` and `--dry-run`, - runbook SHA-256 - `19af4154a19e41563a1a9e717b36e4db2a959db42892bef176c4493698d4b84a`, - `lifecycleOwner=Idunn`, `hostedBody=repo-work`, - `mutatesServiceManager=false`, `launchesService=true`, `waitChild=true`, - `requiresElevatedAuthority=false`, and `privateStateExposed=false`. Wrapper - proofs `local-20260621-014302-362-31bbef47`, - `local-20260621-014337-108-cbd99615`, - `local-20260621-014413-125-09e183c5`, and - `local-20260621-014558-197-f6951806` exercised plan/runbook/launch/audit; - final audit printed `status=complete`, `missing=none`, `failed=none`, - `owner=Idunn`, `hostedBody=repo-work`, and `serviceManagerMutation=false`. -- `epiphany-work persona-intake` is the first repo Persona mouth over accepted - work pressure. It requires an online repo swarm receipt, writes a Persona - bubble plus speech-audit witness into the repo-local Verse, records public - discussion and candidate-action refs, delegates to `accept`, and seals - `work-persona-intake-.json` with `handsAuthorityGranted=false`, - `durableStateAdmitted=false`, `publicationAuthorized=false`, and - `privateStateExposed=false`. Wrapper: - `tools/epiphany_local_run.ps1 -Mode repo-persona-intake -RepoWorkItem - -PersonaInput ''`. -- `epiphany-work derive-plan` now writes - `work-action-items-.json` before `work-plan-.json`. The action - items receipt is owned by Imagination, routed by Self, gated by Mind, and - carries model provenance, safe action family, requested paths, verification - asks, stop conditions, escalation reasons, rollback hints, public/candidate - refs, and `operatorAuthoredShellDetails=false`. The plan receipt cites that - action-item receipt in its derivation. Hands command text remains lowered - through allowlisted safe families; the action item is not publication, merge, - durable Mind admission, service lifecycle, elevation, or cross-repo authority. - -The scar is equally important: this is still an operator-started cadence loop, -not installed service lifecycle. The operator can prove the scheduler breathes, -but Idunn-owned service installation/startup remains an explicit elevated -authority boundary. - -### Remaining MVP Organs - -The remaining migration is not "add more CLI commands until it looks alive." -The remaining migration is to replace manual stepping with organ-owned -physiology while preserving the same authority receipts. - -Required organs before MVP: - -- Unbounded-work physiology: the repo swarm may keep advancing lawful - branch-local work without a human re-approving every edit, but only through - receipt-backed scheduler pulses, local brakes, active-turn/cooldown guards, - branch containment, and proof rows. This is an authority model, not a timeout - setting. -- Scheduler physiology: the first `epiphany-work tick` pulse now has brake, - active-turn, cooldown, and stale-turn recovery receipts, - `epiphany-work serve` adds bounded/unbounded cadence around that pulse, and - `epiphany-swarm run` plus wrapper expose the bounded repo-swarm run mouth - over the typed queue. Idunn-owned non-mutating queue-run service plan/runbook - receipts plus bounded launch/audit readiness receipts also exist, and the - queue/tick path now hands branch-local execution into `close-from-execute`. - Remaining Idunn service work is optional elevated install/start execution - under explicit operator authority. -- Persona-to-plan depth: deterministic `append-worklog` and `planning-note` - derivations exist for accepted Persona/Bifrost pressure, - `persona-intake` gives the project Persona a speech-audited mouth into that - pressure stream, and `derive-plan` now writes typed Imagination action-item - receipts before safe-family command lowering. `checklist-note`, - `section-note`, and `task-card` now prove richer model-authored planning cargo - without operator shell details. `repo-status-section` proves repo-visible - status cargo, and `repo-manifest` now proves structured repo Body manifest - cargo with body domain, private/local/public Verse ids, Eve surface, capability - hints, and authority seals. `repo-tool-capabilities` now proves repo-owned - tool capability cargo with CultMesh/Odin discovery, typed daemon tool - invocation intent/receipt contracts, expected capability ids, host-daemon - execution ownership, Idunn lifecycle ownership, and authority seals. - `repo-tool-request` now proves daemon-hosted tool request cargo with CultMesh - typed invocation intent/receipt contracts, host-daemon execution ownership, - host liveness/readiness requirements, Odin provider ownership, and denied - direct execution, shell, Hands, state, publication, service lifecycle, - cross-body, and private-rummaging authority. - `repo-collaboration-topic` now proves public collaboration cargo with public - Persona room, Eve surface, Imagination consensus route, non-authoritative - candidate actions, Mind/Bifrost gates, and denied action/cross-body authority. - `repo-consensus-brief` now proves draft Imagination consensus cargo from - public discussion and candidate-action refs, still review-gated and - non-authoritative before Mind/Bifrost gates. - `repo-objective-draft` now proves review-required Objective Draft cargo that - can shape consensus into a proposed objective without adopting it, scheduling - it, or granting Hands authority. - `repo-adoption-request` now proves Mind-facing adoption request cargo that - asks for an adopt/refuse/more-consensus verdict without committing state or - scheduling work. - `repo-scheduling-request` now proves Self-facing scheduling request cargo that - names a bounded queue pulse after Mind adoption while granting no scheduling, - queue mutation, Hands, publication, or cross-body authority. - `repo-work-order` now proves Hands-facing work-order request cargo that names - the required Substrate/Hands/Soul/Mind receipt chain while granting no - substrate, shell, commit, Hands, publication, or cross-body authority. - `repo-verification-request` now proves Soul-facing verification request cargo - that names Hands evidence and closure checks while granting no verdict, rerun, - state commit, Hands, publication, or cross-body authority. - `repo-publication-request` now proves Bifrost-facing publication request cargo - that names redacted proof plus Bifrost/GitHub/credit/upstream receipt needs - while granting no publication, merge, credit, sync, Hands, or cross-body - authority. - `repo-sync-request` now proves upstream-main proof request cargo that names - Bifrost/GitHub/maintainer/credit receipts and `origin/main` ancestry checks - while granting no merge, push, sync, publication, credit, Hands, or cross-body - authority. - `repo-maintainer-review-request` now proves maintainer review request cargo - that names closure/Soul/Mind/public-proof/Bifrost antecedents and reviewer - identity, verdict, changed-path, proof-ref, and redaction packet requirements - while granting no maintainer approval, merge, push, publication, sync, Hands, - or cross-body authority. - `repo-pr-request` now proves GitHub/Bifrost PR request cargo that names - closure/Soul/Mind/public-proof, maintainer-review, Bifrost-publication, and - credit antecedents plus branch/title/body/path/proof/review/credit/redaction - packet requirements while granting no PR, branch push, merge, publication, - sync, Hands, or cross-body authority. - `repo-credit-request` now proves Bifrost credit request cargo that names - closure/Soul/Mind/public-proof, maintainer-review, accepted-artifact, and - authorship antecedents plus author/reviewer/artifact/proof/path/ledger/redaction - packet requirements while granting no credit ledger, publication, PR, merge, - sync, Hands, or cross-body authority. - `repo-artifact-acceptance-request` now proves accepted-artifact request cargo - that names closure/Soul/Mind/public-proof, maintainer-review, and Hands-commit - antecedents plus artifact/commit/path/review/proof/rationale/redaction packet - requirements while granting no artifact acceptance, credit ledger, - publication, PR, merge, sync, Hands, or cross-body authority. - `repo-metrics-request` now proves accounting request cargo that names - closure/Soul/Mind/public-proof, accepted-artifact, and credit-request - antecedents plus model-call/token-cost/review-load/artifact/proof/credit-readback/redaction - packet requirements while granting no metrics ledger, spend, review-load, - credit ledger, publication, PR, merge, sync, Hands, or cross-body authority. - `repo-readiness-review-request` now proves redacted repo-swarm MVP proof-bundle - review cargo that names repo init, online swarm, Persona intake, Imagination - planning, Self queue-run, Hands branch work, Soul closure, Modeling map - update, Mind admission, Bifrost publication, upstream-main sync, Idunn - lifecycle, global tool directory, and private-state redaction requirements - while granting no readiness approval, durable state commit, publication, PR, - merge, sync, deployment, service lifecycle, Hands, or cross-body authority. - `repo-doctrine-update-request` now proves repo governance request cargo under - `.epiphany/doctrine-update-requests/.toml`, with `AGENTS.md` as review - target, source grounding, invariant/rehydration/rollback/redaction packet - requirements, Mind/Soul/maintainer receipt contracts, and no direct doctrine - mutation, Hands, publication, merge, service lifecycle, cross-body, private - Verse rummaging, or private-state exposure authority. Smoke - `.epiphany-smoke\repo-doctrine-update-request-family-20260620-192140` proved - the request closes through Soul with family assertions and path scope passing - while the seed `AGENTS.md` remains untouched. - `repo-secret-policy-request` now proves security-governance request cargo - under `.epiphany/security/secret-policy-requests/.toml`, with secret - locations without values, credential ownership, write-scope matrix, - public/private export redaction, deployment authority owner, rollback, and - Eyes/Soul/Mind/maintainer/Bifrost receipt requirements while granting no - secret access, secret value materialization, write permission, deployment, - publication, merge, service lifecycle, cross-body, private Verse rummaging, or - private-state exposure authority. Smoke - `.epiphany-smoke\repo-secret-policy-request-family-20260620-193046` proved - the request closes through Soul with family assertions and path scope passing. - `repo-planning-facets` smoke `.epiphany-smoke\repo-planning-facets-20260620-175503` - proves model-authored planning facets on action-item receipts: assumptions, - constraints, non-goals, open questions, decision points, and evidence needs - survive into the Imagination receipt, plan derivation, and the adoption - artery. Adoption now writes a distinct - `epiphany.repo_work_mind_adoption_decision.v0` receipt before the main - adoption receipt opens branch-local Hands authority; the Mind decision embeds - `epiphany.repo_work_mind_interpretation.v0` with input summary, safe-family - recognition, requested-path classification, branch-local Hands allowed - transition, forbidden durable-state/publication/service/private transitions, - Self presentation, Mind evidence review, safe-family requirement, - branch-local scope, Bifrost publication gate, and Soul closure requirement - while still granting no Hands, durable-state, publication, merge, service - lifecycle, cross-body, or private-state authority. The main adoption receipt - cites that Mind decision, then grants only branch-local Hands authority; the - branch-local item still closes through Soul with family assertions and path - scope passing. The Mind decision now refuses unsupported plan-derived - safe-family labels before Hands review can open: smoke - `.epiphany-smoke\repo-mind-safe-family-guard-20260620-190811` tampered a valid - planning-note action item into `idunn.service_lifecycle` and proved - `actionItemAccepted=false`, `safeFamilyRecognized=false`, - `refused-unsupported-safe-family`, no adoption receipt, no branch-local Hands - authority, no durable-state admission, no service-lifecycle authority, and no - private-state exposure. Positive regression smokes - `.epiphany-smoke\repo-planning-facets-20260620-190916` and - `.epiphany-smoke\closure-model-gate-20260620-190916` prove recognized - planning cargo and closure model gates still pass. - `repo-eve-surface` now proves branch-local Eve/CultUI surface contract cargo - under `.epiphany/eve-surfaces/.toml`, with compact agent TUI and GUI - lowering contracts, local/public Verse routing, Odin discovery, Persona/human - collaboration, and feedback routing to Imagination. Smoke - `.epiphany-smoke\repo-eve-surface-family-20260620-165927` proved the contract - closes through Soul with family assertions and path scope passing while - renderer ownership, rendering/state/publication/service/cross-body authority, - private Verse rummaging, and private-state exposure remain false. - `repo-collaboration-policy` now proves repo-owned collaboration law cargo - under `.epiphany/collaboration-policy.toml`, with private/local/public Verse - boundaries, Odin discovery, Eve connection receipt requirements, - Persona/human/peer discussion flags, speech-audit requirement, Imagination - feedback routing, Mind/Bifrost gates, and no direct Hands/Mind state, - publication, merge, service lifecycle, cross-body mutation, private Verse - rummaging, or private-state exposure authority. Smoke - `.epiphany-smoke\repo-collaboration-policy-family-20260620-172808` proved the - policy closes through Soul with family assertions and path scope passing. - `repo-tool-request` now proves daemon-hosted request cargo under - `.epiphany/tool-requests/.toml`, with CultMesh invocation - intent/receipt contracts, host liveness/readiness requirements, Odin provider - ownership, and no direct tool execution, shell, Hands, state, publication, - service lifecycle, cross-body mutation, private Verse rummaging, or - private-state exposure authority. Smoke - `.epiphany-smoke\repo-tool-request-family-20260620-174500` proved the request - closes through Soul with family assertions and path scope passing. - Remaining work is later model-authored action classes that can change more - useful repo-owned configuration after reviewed request cargo, plus deeper - interpreter semantics after the first Mind safe-family allowlist refusal, - without arbitrary shell authority. -- Closure depth: `close` now writes a structured - `epiphany.repo_work_closure_review.v0` packet for Hands commits, records - model-review provenance when supplied, refuses actual-vs-declared path-scope - mismatch before Soul passes, runs committed-content assertions for known safe - families, refuses assertion failures even when the path scope matches, can - require a model-authored closure verdict before Soul passes, and now verifies - the accepted Mind adoption proof by checking the embedded decision snapshot - plus standalone decision receipt before closure passes. Successful closure now - writes `epiphany.repo_work_map_store.v0` as MessagePack under - `.epiphany/state/repo-work-map.msgpack`, replacing the item's latest - `epiphany.repo_work_map_entry.v0` with the verified commit, changed paths, - safe family, Modeling summary, Soul verdict id, and Mind review/commit ids. - The verdict packet records reviewed inputs, finding text, - enforced/skipped gate state, and private-state seals; focused smokes prove a - model `needs-work` verdict blocks closure even when path scope and safe-family - assertions pass, a tampered Mind adoption decision blocks closure before - publication gates can proceed, failed closure does not write the map, and - successful closure writes the map entry with `privateStateExposed=false`. -- Repo work projection/run surface: local `epiphany-work overview` proof bundles, typed - `epiphany.cultmesh.repo_work_overview.v0` event history plus latest-key - projection, and Gjallar multi-item `repo-work-overview` action/readback rows - exist. Persona's Eve surface, direct Eve connection, and globally invokable - Persona Eve tool readback now expose peer-readable queue rows, and - `epiphany-work queue-run`, wrapper `repo-work-queue-run`, and - `epiphany-swarm run` consume that queue for safe branch-local pulses through - closure. `epiphany-swarm run` now emits - `epiphany.repo_swarm_run_stop_classification.v0`; fresh-repo proof - `.epiphany-smoke\fresh-repo-mvp-20260620-180024` shows the bounded run stops - with category `iteration-limit`, owner `Self`, gate - `self.scheduler-iteration-limit`, non-mutating/non-elevated flags, and - `privateStateExposed=false`. Focused stop proof - `.epiphany-smoke\swarm-stop-classification-20260620-184717` now also proves - `queue-empty` / Gjallar / `repo.work.overview` for an empty repo queue, - `dry-run-preview` / Self / `ready-to-run` for a planning item preview, - `authority-gated` / Bifrost / `awaiting-publication` for the publication - blocker, and `authority-gated` / Bifrost/GitHub / - `awaiting-upstream-sync` for the upstream-main sync blocker. All stop - packets are non-mutating, non-elevated, and private-state sealed. Native - service lifecycle plan/runbook/launch/audit proof for the queue-run pulse now - exists at `.epiphany-smoke\repo-work-service-lifecycle-20260621-004202`; - remaining service lifecycle work is optional elevated install/start execution - proof under explicit operator authority plus richer closure depth rather than - basic safe-family planning cargo. -- Proof bundle depth: maintainers and future agents can inspect local - operator-safe receipt chains, artifact schema/status rows, SHA-256 receipt - hashes, compact TUI rows, commit refs, verification verdicts, map admission, - Bifrost/GitHub refs, credit refs, sync state, and compact publication rows. - `epiphany-work export-proof` now writes a redacted - `epiphany.repo_work_public_proof_bundle.v0` artifact under - `.epiphany/public/proof-bundles/` with local paths and raw receipt bodies - removed, and mirrors an `epiphany.cultmesh.repo_work_public_proof.v0` row - into local Verse for Gjallar/Odin sight. Bifrost can now publish that - redacted proof row through - `gamecult.bifrost.public_proof_publication_receipt.v0` with public Verse - target, public room, ledger, review, credit, proof ref, and SHA-256 readback. - Remaining work is deeper semantic review and richer map updates/lenses beyond - latest work-item consequence, family-level aggregation, and changed-path - aggregation; it is not basic safe-family planning cargo, - local public-proof export, local CultMesh readback, Bifrost/GitHub/sync row - visibility, public-proof publication closure, Mind-adoption closure proof, - first durable repo-map admission, first durable repo-map local Verse - projection, or the standard wrapper mouth for those closures. - -Scheduler authority is intentionally narrow. It may advance -`accept -> plan -> run -> adopt -> execute` only when each upstream receipt -exists, the repo is on an owned `epiphany/*` branch for mutation, the planned -paths stay inside the Hands gate, and no brake or active turn blocks the lane. -It may not publish, merge, install services, mutate another repo, expose -private state, or impersonate Idunn's daemon lifecycle authority. - -### MVP Runbook Shape - -The intended MVP runbook is: - -```powershell -epiphany-repo init --workspace --switch-branch -epiphany-swarm online --workspace -epiphany-persona intake --workspace --persona --message -epiphany-swarm run --workspace --until blocked -# submit a requester-owned Bifrost publication intent; await authenticated provider ingress -# await authenticated provider/maintainer sync admission and verify ancestry -``` - -Under that short operator surface, the swarm must still emit the same typed -chain: - -```text -Persona speech audit - -> candidate action extraction - -> work accept receipt - -> Imagination action plan receipt - -> Self scheduler tick receipt - -> Substrate Gate grant - -> Hands intent/review/adoption receipts - -> Hands patch/command/commit receipts - -> Soul verification receipt - -> Modeling map update proposal - -> Mind state-admission receipt - -> Bifrost publication and credit receipts - -> GitHub/PR receipt - -> upstream-main sync receipt -``` - -This is the difference between a demo and a repo swarm: the human talks to the -project, the project forms a plan, the branch work happens inside its owned -Body, and the proof bundle tells the truth without leaking the private mind. - -### Retired Fresh Repo MVP Smoke - -This former proof is retired and its binary deleted: - -```powershell -epiphany-repo-swarm-mvp-smoke (deleted) -``` - -The smoke creates a disposable bare `origin.git`, clones a fresh repo Body, -seeds only an initial `README.md`, then runs the public front doors instead of -writing target-repo artifacts by hand: - -```text -epiphany-repo init --switch-branch - -> epiphany-swarm online - -> epiphany-work persona-intake - -> epiphany-work derive-plan --action-family repo-status-section - -> epiphany-work overview - -> epiphany-swarm run --max-iterations 4 - -> requester-owned Bifrost publication intent - -> authenticated external publication admission - -> maintainer merge simulated by pushing the Epiphany branch to disposable origin/main - -> authenticated provider/maintainer sync admission plus ancestry verification - -> epiphany-work overview - -> epiphany-work export-proof -``` - -Disposable proof `.epiphany-smoke\fresh-repo-mvp-20260620-133452` reported -`status=ok`, branch `epiphany/fresh-repo-smoke/mvp-proof`, Hands commit -`76f8d5ebb023951a513864bdf8555a524d89c5dc`, Soul `passed`, -`familyAssertions.status=passed`, `publishStatus=publication-receipts-recorded`, -`syncStatus=upstream-main-synced`, `upstreamMainSynced=true`, -`publicProofStatus=public-proof-exported`, and `privateStateExposed=false`. -The Git operations proved local ancestry, but the same smoke supplied an -`example.invalid` pull request, maintainer-review prose, and a synthetic merge -receipt. It therefore could not prove Bifrost publication, external review, or -Soul authority. Keep the record only as rejected historical evidence. - -### Phase 0: Authority Freeze - -Owner: Self, with Mind as the durable-state gate. - -Purpose: lock the current doctrine so the migration does not drift back into -human-permission theater or unsafe ambient autonomy. - -Required cuts: - -- Keep this note as the repo-swarm authority map. -- Keep `state/map.yaml` pointing at this note as the current autonomy doctrine. -- Treat branch-local autonomous work as allowed inside the owned Body. -- Treat publication, merge, deployment, service lifecycle mutation, cross-repo - mutation, public speech, and durable belief changes as gated effects. -- Preserve Gjallar as sight, Odin as discovery, Idunn as daemon lifecycle owner, - Bifrost as publication/ledger gate, and Eve/CultUI as interface projection. - -MVP exit proof: - -- Rehydrate status names the same authority boundary. -- A new agent can read this note and know which organ owns each effect. - -### Phase 1: Close The Publication Loop - -Owner: Bifrost for publication authority; Hands/Soul/Mind for repo consequence -receipts; Self for routing. - -Purpose: stop claiming work is finished when it merely reached a PR-like -publication receipt. Upstream main must be proven, not implied. - -Required cuts: - -- Add `epiphany-work sync` or `epiphany-work sync-main`. -- Read the publish receipt, Hands PR receipt, and Hands commit receipt. -- Require an explicit maintainer merge/sync receipt or Bifrost merge receipt. -- Verify the published commit is an ancestor of the configured upstream main - ref, normally `origin/main`. -- Write a repo-local sync receipt with `upstreamMainSynced=true` only after git - ancestry proves it. -- Keep the command non-mutating for MVP unless a later Bifrost/GitHub executor - explicitly owns merge mutation. - -MVP exit proof: - -```powershell -# authenticated provider publication admission is already present -# future authenticated sync-ingress command, followed by ancestry verification -``` - -The proof bundle shows admitted provider publication evidence, upstream main -synced, merge authority traced to Bifrost or maintainer review, and no -private-state exposure. - -### Phase 2: Replace Operator Commands With Imagination Plans - -Owner: Imagination for concrete plan formation; Self for scheduling; Hands for -action execution. - -Purpose: remove the operator as the hidden planner/executor. The current -`execute --command ` shape is useful quarantine scaffolding, not the -MVP organism. - -Required cuts: - -- Keep `epiphany-work plan` as the typed Imagination/Self action plan receipt - producer and grow it toward model/planner-authored packets instead of - operator-authored packets. -- Define the plan body as ordered action candidates with objective, requested - paths, allowed command families, expected evidence, verification asks, stop - conditions, and rollback hints. -- Let Self choose one adopted action item and emit the next Hands intent. -- Let Hands derive the executable command packet from the accepted action item - when the command is mechanical and inside the repo Body. -- Keep ambiguous, privileged, destructive, cross-repo, or external-network - commands as escalation intents instead of silently executing them. - -MVP exit proof: - -- A Persona/Bifrost idea becomes an Imagination plan receipt. -- Self schedules one action item without the operator writing the shell command. -- Hands executes the command from the typed action plan and records patch, - command, and commit receipts. - -### Phase 3: Make Persona The Front Door - -Owner: Persona for public conversation; Mind/Interpreter for extracting -candidate state/effect pressure; Imagination for planning. - -Purpose: humans should talk to the project, not pre-author complete -implementation briefs for a CLI harness. - -Required cuts: - -- Give each initialized repo at least one Persona record and public surface - advertisement. -- Route Persona speech through the existing parent-side speech audit. -- Add a repo-local Persona intake command or daemon route that records the - public utterance, response, extracted candidate actions, and discussion refs. -- Feed work-shaped candidate actions into `epiphany-work accept`. -- Keep banter as conversation unless Mind/Interpreter extracts a candidate - action and Imagination makes it concrete. - -MVP exit proof: - -- A human message to the repo Persona creates a speech audit and candidate - action receipt. -- The candidate action becomes a work accept receipt with no Hands authority - yet. -- The same path works for Bifrost-originated work items. - -### Phase 4: Run The Swarm As Physiology - -Owner: heartbeat scheduler and Self; Idunn for daemon survival. - -Purpose: migrate from a manually stepped CLI chain to a living repo swarm that -can keep working when it has safe branch-local work available. - -Required cuts: - -- Add a repo-local scheduler pulse that reads accepted/adopted work, active - Hands/Soul/Modeling/Mind receipts, cooldowns, brakes, and branch status. -- Keep `epiphany-work tick` as the first native pulse: one safe branch-local - advancement per invocation, with a scheduler receipt for advanced, blocked, - dry-run, or no-op outcomes. -- Ensure no lane wakes again while its previous heartbeat turn is active. -- Let cooldown begin after completion, not launch. -- Honor local Verse swarm brake and repo-specific pause receipts. -- Let idle time produce rumination, memory pressure, and Imagination candidate - refinement without hammering Hands. -- Keep Idunn-owned service lifecycle checks separate from Self scheduling. - -MVP exit proof: - -- `epiphany-swarm run --workspace ` or an Idunn-hosted daemon pulse can - advance one safe work item without the operator driving each step. -- A brake stops work before new mutation. -- A stale active turn is recovered or refused with a typed receipt. - -### Phase 5: Seal The Repo Mind - -Owner: Mind for durable state; Modeling for machine-map updates; Eyes for -evidence; Soul for verification. - -Purpose: make the swarm remember what changed without treating transcripts, -stdout, or wrapper JSON as the mind. - -Required cuts: - -- Ensure each meaningful branch-local consequence routes through Soul before - Modeling updates the repo map. -- Require Mind admission receipts for durable repo map, memory, and objective - changes. -- Store repo-local state in CultCache-shaped documents under `.epiphany/state` - and advertise compact summaries through local Verse. -- Keep raw worker thoughts, raw result payloads, and full transcripts sealed. -- Add a compact repo work ledger view for current work item, branch, last - verified consequence, current blocker, and next safe action. - -First live cut: successful `epiphany-work close` now performs the Soul -> -Modeling -> Mind part for branch-local repo-work consequences. It writes -`.epiphany/state/repo-work-map.msgpack` as a typed MessagePack -`epiphany.repo_work_map_store.v0` with latest `epiphany.repo_work_map_entry.v0` -rows keyed by item. This is a narrow repo-work map store, not yet the full -semantic architecture map. The first local Verse projection of those accepted -rows is live as `epiphany.cultmesh.repo_work_map_entry.v0`; richer semantic map -lenses remain Phase 5 work. - -MVP exit proof: - -- After a Hands commit, Soul verifies the changed Body. -- Modeling proposes a source-grounded map update. -- Mind admits or rejects the update. -- The repo-local overview shows the durable lesson without exposing private - worker thought. - -### Phase 6: Publish Operator-Safe Proof Bundles - -Owner: Bifrost for public labor/credit ledger; Gjallar/Odin for discovery and -sight; Eve/CultUI for interface projection. - -Purpose: make repo-swarm work legible enough for maintainers, peers, and future -agents without opening private state. - -Required cuts: - -- Emit one proof bundle per work item with receipt ids, expected artifact paths, - present/missing status, document schemas/statuses, SHA-256 receipt hashes, - changed paths, branch, commit, verification result, map admission result, - Bifrost publication refs, GitHub/PR refs, sync status, compact TUI rows, and - credit refs. -- Emit a redacted public/export proof artifact per work item that keeps the - operator-safe proof rows and hashes while removing local receipt paths, raw - receipt bodies, worker thought, and private Verse contents. -- Add compact Eve/CultUI rows for repo work queue, active branch work, blocked - work, publication status, and upstream sync. -- Make Gjallar announce the repo swarm's operator-safe status through Odin's - discovery map without giving Gjallar lifecycle or mutation authority. -- Project readiness-review approval receipts as typed local Verse sight - (`epiphany.cultmesh.repo_work_readiness_review.v0`) and compact Gjallar rows, - while preserving Bifrost/GitHub/Idunn/Hands ownership for publication, sync, - deployment, service lifecycle, and branch consequences. -- Keep Bifrost ledger and credit receipts as the public labor trail. - -MVP exit proof: - -- A maintainer can inspect a compact proof bundle and decide whether the - branch-local work deserves merge/publication. -- A future agent can rehydrate from map, ledger, and receipts without reading - sealed thought streams. - -### Phase 7: Fresh Repo MVP Run - -Owner: the repo swarm as a whole, with organ ownership preserved. - -Purpose: prove the product path on a repository that was not hand-prepared by -the supervising Codex session. - -Required scenario: - -```powershell -epiphany-repo init --workspace --switch-branch -epiphany-swarm online --workspace -epiphany-persona intake --workspace --persona --message -epiphany-work accept --workspace --item -epiphany-work adopt --workspace --item --from-plan -epiphany-swarm run --workspace --until blocked -# requester publication intent, followed by authenticated provider admission -# authenticated provider/maintainer sync admission plus ancestry verification -``` - -MVP exit proof: - -- The repo has an Epiphany branch with real commits. -- The branch has Hands, Soul, Modeling, and Mind receipts. -- The work item has authenticated external Bifrost publication admission and upstream sync proof. -- The repo Persona remains the human-facing surface. -- The proof bundle says `privateStateExposed=false`. -- Ambient daemon/tool availability is visible through Gjallar/Odin, while - Idunn remains the daemon keeper. - -## MVP Non-Goals - -The MVP does not require: - -- autonomous merge to upstream main -- autonomous service install/start/stop -- cross-repo direct mutation -- exporting private worker thought -- replacing GitHub, Bifrost, Odin, Gjallar, Idunn, or Eve ownership with - wrapper summaries -- solving every future repo Persona style problem -- full distributed dreaming across public Verses - -The MVP does require a real repo swarm that can take an idea, form a plan, -work a branch, verify itself, remember the lesson, and publish proof without a -human writing every implementation command. diff --git a/notes/epiphany-swarm-readiness-plan.md b/notes/epiphany-swarm-readiness-plan.md deleted file mode 100644 index 238ba1fc..00000000 --- a/notes/epiphany-swarm-readiness-plan.md +++ /dev/null @@ -1,214 +0,0 @@ -# Epiphany Swarm Readiness Plan - -This is the direction of travel before live fire. - -The local run path proves that the current machine can be started and inspected. -It does not prove the swarm is ready to run unattended. Do not confuse a working -starter switch with a clean engine bay. That mistake has already been punished -elsewhere, and VoidBot has kindly provided the scorch marks. - -## Objective - -Get Epiphany ready for ordinary resident operation inside an organization -without building architectural Jenga. This is the product, not a special -dogfood or supervised experiment: the swarm learns its authorized domain, -maintains that model, derives bounded pressure from it, proposes improvements, -and receives organization-wide conversation and feedback through its Persona -even when nobody is feeding it a task queue. - -The useful autonomous loop is: - -`Body observation -> persistent Modeling map -> Self direction -> Imagination proposal` - -Persona conversation and attributed organizational feedback can add pressure to -that loop. They do not replace the map, become Mind truth, invoke Hands, authorize -a release, or deploy one. Those authorities remain separately gated. - -The operating constraints are: - -- typed CultCache documents remain the state authority -- CultNet is the native wire for Epiphany-owned subsystems -- Codex remains the retained auth/model transport compatibility organ -- Aquarium and Persona are reflectors/mouths, not hidden sources of truth -- heartbeats create opportunities, not authority -- swarm speech and action stay gated until scheduling, memory, repetition, and - review boundaries are coherent - -The repo-swarm MVP contract lives in -`notes/epiphany-repo-swarm-mvp-contract.md`. Its correction is binding: -autonomous unbounded work inside an Epiphany-owned Body is in scope. The MVP is -not a permission cage where every local edit waits for a human. The safety -boundary is typed authority: branch-local work inside the owned repo Body may -continue autonomously, while publication, merge, privilege escalation, -cross-body mutation, and authority changes require the owning gate. - -## VoidBot Lessons To Carry Forward - -VoidBot's ordinary spine is not the rotten part. Its durable lessons are: - -- Discord ingestion, worker jobs, provider lanes, RAG, Postgres, Qdrant, and - typed CultCache self-state can be conceptually sound when each owns one job. -- The unstable organ was the repo Persona swarm loop: scheduling, prompt policy, - identity, public speech, governance, dispatch, proposal behavior, and - repetition control packed into one prompt-and-parser machine. -- A repo-controlled pause flag is a real brake. If a swarm subsystem is under - teardown, runners must fail closed instead of routing around the pause. -- One CTB-style initiative scheduler beats one task per agent. The wall-clock - task supplies pulses; typed initiative state chooses turns. -- Heat changes recovery pressure. It must not fast-forward time, bypass active - turn freeze, or create overlapping thoughts. -- Cooldown starts after completion, not after queueing. -- Stale active turns need explicit recovery receipts, or one dead worker claim - freezes a Persona forever. -- Public speech needs parent-side eligibility checks. Prompts alone cannot - prevent repeated openings, stale topics, scheduler labels leaking into prose, - or work requests masquerading as banter. -- Memory must have phases: short-term residue, incubation, durable memory, - revision, retirement, crystallization, and sleep-owned maintenance. -- Agents propose typed operations. State services validate, normalize, dedupe, - and write. Whole-state JSON editing is not a mutation path. -- Qdrant/vector recall is a rebuildable resonance cache, not canonical memory. -- Persona affect is state, not vibes: needs, social bonds, status reads, and mood - dimensions must survive typed projection and MCP/CultNet inspection. - -## Current Mechanism - -Epiphany owns its state, organ scheduler, workers, process policy, and local -operator surfaces in native Rust and typed CultCache/CultMesh documents. Codex -survives only as the retained OpenAI subscription-auth/model-transport bridge; -it no longer owns Epiphany routes, prompts, scheduling, or state mutation. - -- Runtime spine, Mind gateway, Substrate Gate, Eyes, Hands, Soul, Modeling, - Continuity, heartbeat, and Persona operations have typed intent/review/receipt - families. -- One heartbeat scheduler owns initiative, heat, active-turn freeze, - completion-gated cooldown, stale-turn recovery, sleep, and rumination. -- The local CultMesh swarm brake is typed and enforced at runner/action edges. -- Persona public speech has parent-side repetition/saturation eligibility and - Bifrost/Heimdall crossing gates. -- Memory has reviewed revise/retire/crystallize/prune/merge operations; Qdrant - remains rebuildable semantic sight rather than canonical Mind. -- Idunn owns managed projector lifecycle and exact abandoned-claim recovery. - Task Scheduler owns only current-user after-login Idunn presence. -- Mind and Modeling semantic query admission reauthenticate canonical CultCache - authority at query time against the Yggdrasil-hosted Qdrant/Ollama substrate. - -The installed host is operational after login through one witnessed immutable -release generation. Mind now owns the race-bounded whole-repository readiness -join in source: fresh Body R1/R2, exact admitted RepoModel, live Modeling -semantic vectors, live workspace coverage, complete authority snapshots, and an -atomic historical projection append. A real reboot/logon survival proof remains -the separate operator-authorized host boundary. The new join still needs to be -included in the next witnessed release before it can produce live host evidence. - -## Invariants - -- One state authority per document kind. -- One scheduler for standing initiative. -- One explicit brake for swarm operation. -- Autonomous branch-local work inside the swarm's owned Body is allowed and is - the desired operating mode. -- Persistent domain observation, map maintenance, bounded rumination, and - proposal generation continue without operator instruction. -- The repository Persona is the organization's conversational surface. Any - admitted member may provide attributed feedback through the configured - crossing; conversation alone grants no implementation or publication authority. -- Upstream publication is Bifrost territory, not implicit branch-local - authority. -- No lane receives a new heartbeat while its previous turn is running. -- No model output rewrites durable state directly. -- No public speech without typed eligibility and receipt checks. -- No cross-repo rummaging; swarm needs move coordinator-to-coordinator through - visible typed messages and callbacks. -- No hidden automatic semantic acceptance. -- No prompt-only governance for things that need state, receipts, or gates. -- No broad live scheduler until the local runner can show the typed surfaces - that explain what it would do and why. - -## Readiness Evidence Matrix - -This matrix is the live gate. A green row names its authority and proof; it is -not permission for another row to borrow the result. - -| Gate | Owner | Current evidence | State | -| --- | --- | --- | --- | -| Typed brake and initiative physiology | heartbeat/CultMesh | brake, completion cooldown, and stale-turn recovery tests and receipts | closed | -| Reviewed Mind and organ-state mutation | Mind gateway and organ services | typed review/commit/lifecycle families; no whole-state model write path | closed | -| Public speech/crossing eligibility | Persona + Bifrost/Heimdall | speech-audit receipts and fail-closed identity/capability checks | closed | -| Branch-local autonomous action | Substrate Gate + Hands + Soul + Mind | scoped grants and patch/command/commit/verdict/admission chains | closed | -| Persistent domain stewardship | Modeling + Body observer + semantic projector | source-bound RepoModel admission, freshness joins, persistent CultCache state, rebuildable Qdrant projection | closed in source; live resident proof pending | -| Autonomous improvement proposals | Self + Imagination + Mind | domain pressure becomes a bounded proposal-only consideration; adoption remains a separate Mind decision | closed in source; live resident proof pending | -| Organization-wide Persona feedback | Persona + VoidBot + Bifrost + Self | attributed Discord observation, exact classification/signature/delivery, dedicated feedback store, proposal-only consideration | closed in source; live Discord proof pending | -| Semantic projection/query recovery | canonical admission + projector + query gate + Idunn | live Mind/Modeling semantic ranking and GUID-scoped exact process-recovery smokes | closed | -| Atomic installed sibling generation | release packager + Idunn | release `sha256-c94109f94ee42c1257089830f399cb87cd5ad672772c274194371995ce4df923` binds commit `dd80f4a5`, four exact binaries, task args, policy role paths, and recurrence receipts | closed | -| Whole-repository observed readiness | Mind | typed source join, hostile raw-orchestration tests, live semantic/coverage rereaders, and full-snapshot CultCache append fence | closed in source; not yet witnessed/deployed | -| Current host package | release deployment | witnessed supervisor PID 21228 and direct-child projector PID 19748; Mind/Modeling semantic query admission passed | closed for current boot | -| After-login recurrence | Task Scheduler + Idunn + ops tunnel | task recurrence and exact child recovery observed on current boot | closed for current boot | -| Reboot/logon survival | Task Scheduler + Idunn + ops tunnel | requires fresh post-boot lineage and query proof | permission-bound | - -`providerStatus`, heartbeats, Qdrant collection existence, task state, watcher -silence, legacy retrieval Ready, Eve/Gjallar rows, and empty dirty-path lists are -never substitutes for the Mind readiness join or query admission. - -## Ranked Next Cuts - -Keep: - -- typed CultCache state -- runtime-spine documents and CultNet catalog -- heartbeat initiative physiology -- reviewed role memory patches -- local run/status/coordinator artifacts -- Codex auth/model transport reliquary - -Cut: - -- schema/catalog language that treats legacy JSON-RPC routes as native authority -- public or operator outputs that dump prompt slabs as if they were status -- any new live runner that bypasses typed pause, receipt, or review gates - -Collapse: - -- local runner status/coordinator result shaping into one operator projection - helper if duplication grows -- Persona speech receipts and eligibility into one typed surface before high-rate - speech returns - -Split: - -- heartbeat opportunity from action authority -- Persona affect/state from speech generation -- scheduler metadata from model-facing prose -- vector recall from canonical memory - -Rebuild: - -- any swarm loop that depends on a single prompt to own scheduling, identity, - public speech, governance, proposal behavior, and repetition control - -## Immediate Work Queue - -1. Let the currently authorized Yggdrasil projection finish without interrupting - its sole store owner, then allow Idunn to admit or reject that exact release - from terminal typed evidence. -2. Restart the already-installed corrected Yggdrasil Idunn only after that - deployment child exits. Let Idunn discover and deploy the exact-authorized - Bifrost Persona-feedback sidecar; do not invoke the deployment manifest as a - parallel authority. -3. Bind the configured GameCult Discord rooms to the Epiphany Persona as - organization feedback sources and prove attributed observation, exact - classification, signed delivery, and dedicated-store admission. -4. Package and Bifrost-authorize the current Epiphany generation, then let Idunn - deploy the resident supervisor, heartbeat, and swarm services with the brake - engaged, writable Body, persistent Mind/Modeling state, and witnessed child - binaries. -5. Before 2026-07-22, run a Starfire-offline rehearsal proving two independent - causes of useful cognition: autonomous map-derived improvement proposals and - organization-originated Persona feedback. Prove separately that neither path - can adopt Mind state, invoke Hands, release, or deploy without its owning gate. -6. Reboot survival remains a separate operator-authorized proof. Do not widen - service authority merely because the organizational loop is otherwise ready. - -The correct posture is patient pressure. We are not rushing to live fire. We are -aiming the barrel, checking the chamber, and making sure the thing does not try -to become a parliament of prompts the moment nobody is watching. diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index bffa5ce4..9d101c19 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -439,16 +439,27 @@ coordinator `12/12`, swarm `10/10`, and model-runtime `13/13`. ## Immediate next action -1. Let Idunn compile, test, and seal exact `5b799b12` without touching another - target or moving the historical production store. -2. Run that exact package from private fresh-store Ox17. Stop after - three provider failures total. Prove direct Body Modeling without Eyes, - refusal-to-retry if exercised, Hands -> Verification -> Mind, - `list-decisions` plus exact `audit-decision`, and restart/re-entry without - public speech. -3. Never resume Ox10, Ox12, Ox13, Ox15, or Ox16 or transplant their decisions. - Only after Ox17 passes may Model Atlas Gate 1 restart from a new external - root. +The Ox17 deployment lane is paused. Yggdrasil exposed 96 GiB of dead native +Cargo targets after interrupted full-workspace builds; exact cleanup returned +the builder target root to 4 KiB. The first causal source cut is verified: + +- 21 duplicate core/release binary declarations are gone; each production + entrypoint has one Cargo owner; +- 19 generic repo-request smoke executables are gone because Cargo compiled + them but their `main()` assertions were not tests; +- the runtime-store migrator CLI and the unshipped repo-personality birth + subsystem, parallel `agents.msgpack` initialization, schemas, prompts, and + stale contract plans are gone; +- the non-vendor diff removes 19,252 net lines and reduces `epiphany-core` from + 75 to 30 binary targets; +- core library tests remain 494/494 and Hands remains 5/5 under the sole release + package owner. + +Next, map and cut the surviving legacy `agent_memory` aggregate, +`runtime_store_migration` library/tests, and the local PowerShell operator +control plane. Tests survive only when they falsify a named live invariant. +Do not run a full-workspace compile, package, deployment, or Ox root until the +subtraction audit closes. Never resume Ox10, Ox12, Ox13, Ox15, or Ox16. ## Operational state that matters diff --git a/notes/repo-personality-projection-layer-plan.md b/notes/repo-personality-projection-layer-plan.md deleted file mode 100644 index 6833fe80..00000000 --- a/notes/repo-personality-projection-layer-plan.md +++ /dev/null @@ -1,607 +0,0 @@ -# Repo Personality Projection Layer - -Epiphany needs repo initialization to answer a question that is half technical -and half social: what kind of person is this repository? - -The answer must not be a prose horoscope. It should be a repeatable projection -from repo terrain into typed Ghostlight-shaped role dossiers, heartbeat timing, -and initial planning/model/checkpoint candidates. Personality here means the -pressure a workspace exerts on each organ of the swarm. - -## Terrain Scout Reduce - -Three read-only scouts inspected nearby repositories: - -- **Memory and instruction terrain:** repo-root `AGENTS.md`, state maps, - handoffs, scratch/evidence surfaces, and explicit role dossiers. -- **Commit-history temperament:** recent commit messages, churn, touched paths, - deletion/insertion balance, test/receipt density, and state/doc maintenance. -- **Architecture/body shape:** languages, frameworks, tests, storage/protocols, - UI/runtime/editor roles, and representative source paths. - -The shared conclusion is that repo personality should combine three projections: - -1. **Body taxonomy:** what kind of machine the repo is. -2. **History temperament:** how the repo changes over time. -3. **Memory doctrine:** how the repo preserves intention, evidence, and identity. - -Only after those three are scored should Epiphany modulate Self, Persona, -Imagination, Eyes, Modeling, Hands, Soul, and Continuity. - -## Source Families - -### Epiphany Spine - -Repos: `EpiphanyAgent`, `EpiphanyAquarium`, `EpiphanyGraph` - -Traits: - -- typed state and explicit authority -- evidence gates -- UI as sensory body, not source of truth -- graph/model surfaces as inspection organs -- strong anti-sludge and anti-transcript-fog bias - -Personality pressure: - -- high `state_hygiene` -- high `contract_strictness` -- high `evidence_appetite` -- high `interface_orientation` for Aquarium -- high `churn_spiral_risk` when visual iteration runs ahead of verification - -### Cult Protocol And Storage Layer - -Repos: `cultcache-rs`, `cultcache-py`, `CultCacheTS`, `CultLib`, -`cultnet-rs`, `CultNetTS` - -Traits: - -- typed documents -- schema-visible wire contracts -- MessagePack payloads -- cache envelopes -- interop tests and examples -- small public abstractions - -Personality pressure: - -- very high `contract_strictness` -- very high `protocol_intolerance` -- high `receipt_hunger` -- low `aesthetic_appetite` -- high `implementation_precision` - -### GameCult Web, Lore, And Ops - -Repos: `AetheriaLore`, `gamecult-site`, `GameCult-Quartz`, `gamecult-grav`, -`gamecult-ops` - -Traits: - -- lore vaults and publishing machinery -- rendered DOM and public presentation are evidence -- ops runbooks are authority -- source/inspiration/canon boundaries matter - -Personality pressure: - -- high `content_canon_bias` -- high `source_fidelity` -- medium `interface_orientation` -- high `operational_restraint` for ops -- low to medium `runtime_proximity` - -### Unity And Runtime Bodies - -Repos: `Aetheria-Economy`, `StreamPixelsUnity`, `CultPongReborn`, -`MixerRagnarok` - -Traits: - -- Unity project state, scenes, prefabs, assets, editor versions -- runtime/editor APIs beat text archeology -- high actuation risk -- old production scars mixed with new bridge grafts - -Personality pressure: - -- very high `runtime_proximity` -- very high `actuation_risk` -- high `environment_truth_need` -- high `verification_environment_need` -- low trust in filesystem-only modeling - -### Service And Product Apps - -Repos: `StreamPixels`, `Bifrost`, `Heimdall`, `VoidBot` - -Traits: - -- auth, Discord, Postgres, Redis, MCP, Qdrant, Ollama, realtime fanout -- explicit authority boundaries -- user-facing behavior and operational receipts -- scheduled jobs and runtime state - -Personality pressure: - -- high `production_pressure` -- high `temporal_pressure` -- high `boundary_severity` -- high `social_surface` for VoidBot and Bifrost -- high `auth_paranoia` for Heimdall and StreamPixels - -### Research And Workbench Repos - -Repos: `Ghostlight`, `VibeGeometry`, `LunaMosaic`, `repixelizer`, -`Eusocial Interbeing`, `SyncBook` - -Traits: - -- experimental loops -- artifacts, manifests, rendered evidence, queues -- strong maps and handoffs -- specialized domain language -- repeated “prove the shape before trusting output” - -Personality pressure: - -- high `experimental_heat` -- high `evidence_appetite` -- high `state_hygiene` -- high `aesthetic_appetite` for visual/artifact repos -- medium to high `churn_spiral_risk` when iteration becomes pattern completion - -## Variance Criteria - -These are the first-pass numeric axes. Each should be normalized to `0.0..1.0` -with an explanation and source evidence. - -### Structure And Authority - -- `contract_strictness`: typed schemas, generated contracts, API boundaries, - slot-keyed serialization, protocol docs. -- `protocol_intolerance`: hostility to ad hoc JSON, stringly APIs, hidden - envelopes, and undocumented state mutation. -- `boundary_severity`: auth, app-domain separation, swarm boundaries, workspace - ownership, ops safety. -- `actuation_risk`: likelihood that a wrong write affects production, auth, - runtime scenes, data stores, deployment, or user-facing behavior. -- `runtime_proximity`: dependence on live editors, browsers, providers, Unity, - Discord, databases, or deployed services. - -### Memory And Evidence - -- `state_hygiene`: explicit maps, scratch, evidence ledgers, handoffs, typed - memory, compaction protocols. -- `evidence_appetite`: tests, smoke checks, visual receipts, artifact manifests, - verifier loops, rendered outputs. -- `source_fidelity`: insistence on source-of-truth reads, RAG-first vault - navigation, canonical docs, exact editor/runtime facts. -- `content_canon_bias`: lore/site/editorial surfaces where continuity, canon, - and inspiration separation matter. -- `verification_environment_need`: need for validation in the real runtime, not - just unit tests or text inspection. - -### Motion And History - -- `burstiness`: active-day commit clustering and same-day workbench intensity. -- `consolidation_drive`: refactor/remove/extract/replace behavior and - deletion-heavy simplification. -- `production_pressure`: bugfix, deploy, queue, policy, hosted, CI, and small - careful commits. -- `experimental_heat`: prototype/scaffold/study/example churn, high insertion - bursts, research artifacts. -- `churn_spiral_risk`: repeated polish/correct/refine loops, high file-count - diffs, large add/delete swings without matching receipts. - -### Human Surface And Aesthetic Pressure - -- `interface_orientation`: UI, DOM, Aquarium, overlay, rendered site, human - inspection surfaces. -- `aesthetic_appetite`: visual/audio/scene/tone quality as a first-class - objective rather than decoration. -- `social_surface`: Discord, public speech, moderation, auth/accounting, user - relationship state. -- `sensory_salience`: motion, sound, spatial memory, cute affordances, rendered - evidence, organism-like UI. -- `editorial_restraint`: emotionally salient prose with canon/source discipline. - -### Ghostlight-Derived Inner State - -- `speech_pressure`: how readily Persona should speak instead of emitting silence - or a local bubble. -- `novelty_hunger`: how strongly Eyes/Imagination should seek fresh seams. -- `guardedness`: caution around authority, state mutation, trust boundaries, - and irreversible changes. -- `rumination_bias`: tendency to sleep, consolidate, and refine memory before - acting. -- `initiative_drive`: baseline readiness and heartbeat speed. -- `mood_lability`: how strongly urgency/anxiety/blocked status bends behavior. - -## Role Modulation - -Repo personality does not directly become every sub-agent personality. It -modulates each role through that role's job. - -### Self - -Primary inputs: - -- `boundary_severity` -- `contract_strictness` -- `state_hygiene` -- `churn_spiral_risk` -- `production_pressure` - -Projection: - -- Raises review strictness, adversarial questioning, and refusal clarity. -- Lowers tolerance for implementation turns that produce plausible motion - without objective evidence. - -### Persona - -Primary inputs: - -- `social_surface` -- `interface_orientation` -- `sensory_salience` -- `speech_pressure` -- `editorial_restraint` - -Projection: - -- Determines whether public output is chatty, sparse, poetic, sleepy, formal, - or local-only. -- Keeps humans talking to Persona while internals remain inspectable. - -### Imagination - -Primary inputs: - -- `experimental_heat` -- `aesthetic_appetite` -- `content_canon_bias` -- `novelty_hunger` -- `churn_spiral_risk` - -Projection: - -- Shapes objective drafts, backlog synthesis, future artifacts, and speculative - design. -- High churn risk forces more explicit acceptance gates before dream becomes - work. - -### Eyes - -Primary inputs: - -- `source_fidelity` -- `protocol_intolerance` -- `runtime_proximity` -- `novelty_hunger` -- `verification_environment_need` - -Projection: - -- Determines how aggressively Eyes must research existing systems, official - docs, live runtime state, and repo-local source before invention. - -### Modeling - -Primary inputs: - -- `runtime_proximity` -- `contract_strictness` -- `state_hygiene` -- `content_canon_bias` -- `source_fidelity` - -Projection: - -- Shapes graph/model depth, checkpoint strictness, and required source anatomy. -- Unity/editor repos get environment and asset-model nodes early. - -### Hands - -Primary inputs: - -- `production_pressure` -- `actuation_risk` -- `implementation_precision` -- `consolidation_drive` -- `churn_spiral_risk` - -Projection: - -- Determines slice size, willingness to edit, need for queue state, and how much - Hands must wait for Modeling/Soul before cutting. - -### Soul - -Primary inputs: - -- `evidence_appetite` -- `verification_environment_need` -- `actuation_risk` -- `interface_orientation` -- `content_canon_bias` - -Projection: - -- Selects verification style: formal tests, visual smoke, live provider checks, - Unity probes, DOM inspection, lore/canon review, or artifact review. - -### Continuity - -Primary inputs: - -- `state_hygiene` -- `burstiness` -- `rumination_bias` -- `mood_lability` -- `temporal_pressure` - -Projection: - -- Shapes compaction readiness, sleep cadence, reorientation strictness, and how - often the repo should distill memory. - -## Initialization Map-Reduce Pipeline - -The projection layer should run as a native Epiphany initialization command, -not as a chat-only exercise. - -### Map Phase - -For each repo: - -1. inventory: - - git root and remotes - - language/framework extensions - - state surfaces - - instruction surfaces - - test/smoke surfaces - - runtime/editor/provider surfaces -2. sample history: - - commit count - - recent messages - - active-day burstiness - - numstat insertions/deletions - - path class churn -3. inspect doctrine: - - AGENTS instructions - - state map/handoff - - explicit memory/personality files - - architecture/roadmap docs named by state -4. emit a `repoTerrainReport` with scores, evidence snippets, and confidence. - -### Reduce Phase - -For the target repo: - -1. classify source family or families. -2. normalize axes against the nearby-repo baseline. -3. derive a `repoPersonalityProfile`. -4. project role-specific deltas. -5. emit reviewable initialization artifacts: - - `agentMemoryPatchCandidates` - - `heartbeatParticipantSeed` - - `planningSeedCandidates` - - `graphSeedCandidates` - - `verificationPolicySeed` - - `sourceInventory` - -### Apply Phase - -Application must remain review-gated: - -1. write candidates under `.epiphany/imports//` -2. run validation -3. let Self decide which patches are acceptable -4. apply accepted role memory through `epiphany-agent-memory-store` -5. initialize heartbeat through `epiphany-heartbeat-store` -6. leave project truth as candidates until explicit adoption - -## Proposed Typed Artifacts - -### `epiphany.repo_terrain_report.v0` - -Fields: - -- `repo_id` -- `path` -- `remote_urls` -- `source_families` -- `languages` -- `state_surfaces` -- `instruction_surfaces` -- `test_surfaces` -- `runtime_surfaces` -- `history_metrics` -- `axis_scores` -- `axis_evidence` -- `confidence` -- `warnings` - -### `epiphany.repo_personality_profile.v0` - -Fields: - -- `repo_id` -- `summary` -- `source_family_weights` -- `axis_scores` -- `axis_confidence` -- `dominant_pressures` -- `risk_pressures` -- `role_modulations` - -### `epiphany.role_personality_projection.v0` - -Fields: - -- `role_id` -- `repo_id` -- `trait_deltas` -- `heartbeat_deltas` -- `default_mood_pressure` -- `semantic_memory_candidates` -- `goal_candidates` -- `value_candidates` -- `private_note_candidates` -- `reason` -- `evidence_refs` - -## First Implementation Seam - -Build a native binary in `epiphany-core`: - -```text -epiphany-repo-personality -``` - -Initial commands: - -```powershell -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-personality -- scout --root F:\Projects --artifact-dir .\.epiphany-imports\repo-personality-terrain -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-personality -- project --repo F:\Projects\AetheriaLore --baseline .\.epiphany-imports\repo-personality-terrain\baseline.msgpack --artifact-dir .\.epiphany-imports\aetheria-lore-personality -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-repo-personality -- memory-packet --store .\.epiphany-imports\aetheria-lore-personality\projection.msgpack --artifact-dir .\.epiphany-imports\aetheria-lore-memory -``` - -The first version can use deterministic scoring from local filesystem and git -history. A later Eyes-backed version may add model-based classification for -natural-language doctrine, but the deterministic substrate should exist first so -the machine has repeatable bones. - -## Guardrails - -- Do not inspect sealed worker transcripts or raw agent-thought artifacts. -- Do not edit other repos during terrain scouting. -- Do not store repo facts inside role personality memory. -- Do not let high aesthetic appetite lower verification gates. -- Do not let high experimental heat excuse churn without receipts. -- Do not let high production pressure make Hands bypass Modeling or Soul. -- Treat low-confidence inferred personality as a candidate, not a soul brand. - -## Landed First Slice - -The native `epiphany-repo-personality` binary now exists in -`epiphany-core/src/bin/epiphany-repo-personality.rs`. - -It provides: - -- `scout --root --artifact-dir [--max-repos ]` -- `project --repo --baseline --artifact-dir ` -- `agent-packet --store --artifact-dir [--repo-id ]` -- `memory-packet --store --artifact-dir [--repo-id ]` -- `startup --repo --baseline --artifact-dir --init-store ` -- `accept-init --init-store --packet --kind [--accepted-by ] [--summary ] [--result ] [--agent-store ] [--apply-self-patches ] [--heartbeat-store ] [--apply-heartbeat-seeds ]` -- `status --store ` - -The first implementation is intentionally deterministic. It inventories git -repos, instruction/state/test/runtime surfaces, file-extension/body shape, -remote URLs, sampled commit history, path-class churn, commit-message keywords, -and emits typed CultCache MessagePack records: - -- `epiphany.repo_terrain_report` -- `epiphany.repo_personality_profile` -- `epiphany.role_personality_projection` - -It also writes JSON/Markdown inspection summaries beside the typed stores. -Those exports are operator glass, not the source of truth. - -The startup-personality organ now has a dedicated distiller prompt at -`epiphany-core/src/prompts/repo_personality_distiller.md`, mirrored into the -app-server specialist prompt config as `roles.repo_personality`. The -`agent-packet` command renders the prompt, the typed terrain/profile/projection -input, explicit output contract, and guardrails into an auditable packet for a -specialist pass. The distiller is allowed to petition Self with subtle -role-local quirks and bounded `selfPatch` candidates; it is not allowed to -mutate role memory, smuggle repo facts into personality, or invent project -truth. - -The distiller is birth-only. It runs when a repo/swarm has no accepted -personality initialization record. After Self accepts the initial role-memory -pressure, personality is allowed to drift through heartbeat, mood, rumination, -sleep consolidation, lived evidence, and reviewed `selfPatch` mutations. Startup -must not rerun this specialist merely because terrain changed or the repo -reopened; major terrain surprises should route to Eyes/Modeling/Self as normal -modeling or review work, not as a personality reset. - -## Repo Memory Distillation - -Repo memory initialization is a separate birth organ. Personality answers what -temperament pressure the repo should exert; memory answers what each newborn -sub-agent should know before it starts work. - -The `memory-packet` command renders a dedicated Repo Memory Distiller packet. -It uses the same typed terrain/profile/projection store, then adds a bounded -source inventory from instruction, documentation, state, research, contract, -verification, runtime, and code surfaces. The packet includes one role-specific -distiller brief for each standing sub-agent, plus Continuity protocol pressure: - -- Self receives routing, authority, review-gate, and cross-swarm boundary memory. -- Persona receives public-surface, Aquarium, Discord, voice, and sealed-thought boundary memory. -- Imagination receives roadmap, backlog, objective-shaping, dream, and rejected-path memory. -- Eyes receives research, prior-art, canonical algorithm, vendor/API, and invention-avoidance memory. -- Modeling receives architecture, graph, control/data-flow, invariant, and checkpoint memory. -- Hands receives implementation conventions, build/edit constraints, dependency habits, and common patch traps. -- Soul receives verification commands, evidence standards, invariant checks, and truth-refusal memory. -- Continuity receives compaction, scratch, checkpoint, heartbeat, sleep, and continuity protocol pressure, not role memory. - -The memory distiller petitions Self with Ghostlight-shaped `selfPatch` -candidates. It must not mutate memory directly, adopt objectives, copy raw file -dumps into role memory, or store detailed project maps where typed Epiphany -state should live. If source is thin or stale, it names that risk instead of -filling the newborn with pasteboard certainty. - -Smoke coverage lives in -`epiphany-core/src/bin/epiphany-repo-personality-smoke.rs`. The smoke creates two -tiny git repos, scouts them into a baseline store, projects one repo, and proves -the projection store contains one terrain report, one profile, embodied -sub-agent role projections, a rendered personality distiller packet plus prompt, -and a rendered role-specific memory distiller packet plus prompt. It also -proves `epiphany-repo-birth-runner --mode plan` turns missing birth packets into -startup-only typed runtime execution artifacts and accept commands without creating -heartbeat participants. - -## Next Implementation Step - -The first startup valve is now wired: - -1. `startup` checks a typed CultCache init store for accepted - `repo-personality` and `repo-memory` records. -2. missing birth records generate the matching personality and/or memory packet - under the startup artifact directory and return `reviewInitializationPackets`. - Those generated packets are explicitly startup-only. They are executed by a - repo-initialization runner, not registered as heartbeat lanes. -3. `accept-init` records Self-reviewed packet acceptance in - `epiphany.repo_initialization_record.v0`. -4. when given a reviewed distiller result plus `--agent-store`, `accept-init` - routes role-local `selfPatch` candidates through the native agent-memory - review/apply path instead of mutating memory through a side channel. -5. when accepting `repo-personality` with `--heartbeat-store` and - `--apply-heartbeat-seeds true`, `accept-init` seeds role initiative speed, - reaction bias, cooldown multiplier, and birth mood pressure from typed - role-personality projections. -6. a later `startup` for the same repo sees both accepted records and returns - `continueStartup` without regenerating packets. -That gets Epiphany from “I feel like this repo is anxious and glittery” to a -repeatable profile that can initialize a swarm and seed reviewed role memory -without needing the operator to personally diagnose every little machine soul by -hand. A relief, frankly. - -Remaining startup-routing gaps after the first valve: - -1. Aquarium review/action surfaces for generated birth packets, birth runner - results, and accepted initialization records -2. live `--mode run` dogfood against a real repo initialization once the user is - ready to spend the model call - -Clarification: "drift detection" here does not mean rerunning the distiller to -keep personality synchronized with repo terrain. It means detecting whether the -birth rite has already been accepted. If it has, startup should leave the -personality alone and let the swarm change through lived heartbeat/mood/sleep -drift plus reviewed memory mutations. diff --git a/schemas/README.md b/schemas/README.md index 8e093eb3..13e22cdc 100644 --- a/schemas/README.md +++ b/schemas/README.md @@ -33,9 +33,6 @@ developer's damp recollection. derived speech-conditioning subset for Weks, Aquarium, and other utterance surfaces; it carries identity, trait vectors, mood, and activation without memory records. -- [repo-personality-birth-projection.md](./repo-personality-birth-projection.md): - deterministic birth-time path from repo terrain to newborn trait lattice and - heartbeat seeds. - [heartbeat-state-schema.md](./heartbeat-state-schema.md): typed initiative and routine-state contract for the swarm heartbeat organ. - [cultnet/README.md](./cultnet/README.md): @@ -49,7 +46,6 @@ The living implementation is in code: - [agent_memory.rs](/E:/Projects/EpiphanyAgent/epiphany-core/src/agent_memory.rs) - [heartbeat_state.rs](/E:/Projects/EpiphanyAgent/epiphany-core/src/heartbeat_state.rs) -- [epiphany-repo-personality.rs](/E:/Projects/EpiphanyAgent/epiphany-core/src/bin/epiphany-repo-personality.rs) The rule is simple: @@ -64,7 +60,6 @@ When changing any of the following, update this folder in the same pass: - canonical organ-state or Persona family names - standing role trait names - heartbeat store shape or pacing semantics -- repo-personality birth-time projection math or routing - schema version identifiers If a change lands in code without a matching receipt here, assume the machine diff --git a/schemas/canonical-agent-state-schema.md b/schemas/canonical-agent-state-schema.md index 3cd87d65..d8c3834f 100644 --- a/schemas/canonical-agent-state-schema.md +++ b/schemas/canonical-agent-state-schema.md @@ -142,13 +142,9 @@ Normal reviewed `selfPatch` writes may mutate only: They must not rewrite canonical trait bundles directly. -Canonical trait bundles currently change through two sanctioned paths: - -1. initial role-shell provisioning -2. repo-personality birth-time seeding through `accept-init` - -That birth-time route is documented in -[repo-personality-birth-projection.md](./repo-personality-birth-projection.md). +Canonical trait bundles are provisioned with the role shell. Later Persona +memory and social-state changes enter as typed keyed Mind mutations with exact +decision context; a startup classifier cannot rewrite them. Persona state is expected to evolve through richer event, relationship, appraisal, affect, and distillation flows. The current narrow `selfPatch` @@ -206,4 +202,3 @@ current world model. - [agent-state-variable-glossary.md](./agent-state-variable-glossary.md) - [organ-state-profiles.md](./organ-state-profiles.md) - [heartbeat-state-schema.md](./heartbeat-state-schema.md) -- [repo-personality-birth-projection.md](./repo-personality-birth-projection.md) diff --git a/schemas/cultnet/README.md b/schemas/cultnet/README.md index 30d0cf9a..e36bcaef 100644 --- a/schemas/cultnet/README.md +++ b/schemas/cultnet/README.md @@ -32,13 +32,12 @@ server object deserves to be duplicated into a second baroque schema maze. `epiphany.agent_utterance_state.v0` for speech-conditioning without memory payloads. - `epiphany.surface.*`: operator-safe projections for scene, freshness, - context, graph query, coordinator, roles, jobs, Persona/Persona, Rider, Unity, repo - initialization, and related live surfaces. + context, graph query, roles, jobs, Persona, Rider, Unity, and related live + surfaces. - `*.intent.v0`: control documents Aquarium, a Discord bridge, a voice surface, or another trusted coordinator can submit through CultNet. - receipt/artifact schemas such as `epiphany.swarm-control-receipt`, - `epiphany.persona-bubble`, `epiphany.character-turn-packet`, and - `epiphany.repo-birth-runner`. + `epiphany.persona-bubble`, and `epiphany.character-turn-packet`. ## Publication Path diff --git a/schemas/cultnet/epiphany.repo-birth-runner.schema.json b/schemas/cultnet/epiphany.repo-birth-runner.schema.json deleted file mode 100644 index 5c9e5637..00000000 --- a/schemas/cultnet/epiphany.repo-birth-runner.schema.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.repo-birth-runner.schema.json", - "title": "Epiphany Repo Birth Runner Summary", - "description": "Startup-only birth runner summary artifact.", - "type": "object", - "additionalProperties": true, - "properties": { - "schemaVersion": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "mode": { - "type": "string" - }, - "repo": { - "type": "string" - }, - "baseline": { - "type": "string" - }, - "artifactDir": { - "type": "string" - }, - "initStore": { - "type": "string" - }, - "agentStore": { - "type": "string" - }, - "heartbeatStore": { - "type": "string" - }, - "startup": { - "type": "object", - "additionalProperties": true - }, - "executions": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "requiresReview": { - "type": "boolean" - }, - "nextSafeMove": { - "type": "string" - } - }, - "required": [ - "schemaVersion", - "createdAt", - "mode", - "repo", - "baseline", - "artifactDir", - "initStore", - "agentStore", - "heartbeatStore", - "startup", - "executions", - "requiresReview", - "nextSafeMove" - ] -} diff --git a/schemas/cultnet/epiphany.repo-initialization-record.schema.json b/schemas/cultnet/epiphany.repo-initialization-record.schema.json deleted file mode 100644 index f9639ffe..00000000 --- a/schemas/cultnet/epiphany.repo-initialization-record.schema.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.repo-initialization-record.schema.json", - "title": "Epiphany Repo Initialization Record", - "description": "Accepted startup-only birth rite record.", - "type": "object", - "additionalProperties": true, - "properties": { - "schemaVersion": { - "type": "string" - }, - "recordId": { - "type": "string" - }, - "repoId": { - "type": "string" - }, - "repoPath": { - "type": "string" - }, - "kind": { - "type": "string" - }, - "sourcePacketSchemaVersion": { - "type": "string" - }, - "sourcePacketPath": { - "type": "string" - }, - "acceptedAt": { - "type": "string" - }, - "acceptedBy": { - "type": "string" - }, - "summary": { - "type": "string" - } - }, - "required": [ - "schemaVersion", - "recordId", - "repoId", - "repoPath", - "kind", - "sourcePacketSchemaVersion", - "sourcePacketPath", - "acceptedAt", - "acceptedBy", - "summary" - ] -} diff --git a/schemas/cultnet/epiphany.repo-personality-artifacts.schema.json b/schemas/cultnet/epiphany.repo-personality-artifacts.schema.json deleted file mode 100644 index 9582e65d..00000000 --- a/schemas/cultnet/epiphany.repo-personality-artifacts.schema.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.repo-personality-artifacts.schema.json", - "title": "Epiphany Repo Personality Artifacts", - "description": "Repo scout/project/startup artifact family emitted by epiphany-repo-personality.", - "type": "object", - "additionalProperties": true, - "properties": { - "schemaVersion": { - "type": "string" - }, - "mode": { - "type": "string" - }, - "root": { - "type": [ - "string", - "null" - ] - }, - "repo": { - "type": [ - "string", - "null" - ] - }, - "repoId": { - "type": [ - "string", - "null" - ] - }, - "artifactDir": { - "type": [ - "string", - "null" - ] - }, - "store": { - "type": [ - "string", - "null" - ] - }, - "profile": { - "type": "object", - "additionalProperties": true - }, - "terrain": { - "type": "object", - "additionalProperties": true - }, - "trajectory": { - "type": "object", - "additionalProperties": true - }, - "generatedPackets": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "requiredActions": { - "type": "array", - "items": { - "type": "string" - } - }, - "nextSafeMove": { - "type": "string" - } - }, - "required": [ - "schemaVersion", - "mode" - ] -} diff --git a/schemas/cultnet/epiphany.repo_birth_run_intent.v0.schema.json b/schemas/cultnet/epiphany.repo_birth_run_intent.v0.schema.json deleted file mode 100644 index fcdea484..00000000 --- a/schemas/cultnet/epiphany.repo_birth_run_intent.v0.schema.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.repo_birth_run_intent.v0.schema.json", - "title": "epiphany repo_birth_run_intent v0", - "description": "Startup-only repo birth runner plan/run intent.", - "type": "object", - "additionalProperties": true, - "properties": { - "repo": { - "type": "string" - }, - "baseline": { - "type": [ - "string", - "null" - ] - }, - "artifactDir": { - "type": [ - "string", - "null" - ] - }, - "initStore": { - "type": [ - "string", - "null" - ] - }, - "agentStore": { - "type": [ - "string", - "null" - ] - }, - "heartbeatStore": { - "type": [ - "string", - "null" - ] - }, - "mode": { - "type": "string" - }, - "model": { - "type": [ - "string", - "null" - ] - }, - "timeoutSeconds": { - "type": "integer" - }, - "autoAccept": { - "type": "boolean" - } - }, - "required": [ - "repo" - ] -} diff --git a/schemas/cultnet/epiphany.repo_initialization_accept_intent.v0.schema.json b/schemas/cultnet/epiphany.repo_initialization_accept_intent.v0.schema.json deleted file mode 100644 index 941f737e..00000000 --- a/schemas/cultnet/epiphany.repo_initialization_accept_intent.v0.schema.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.repo_initialization_accept_intent.v0.schema.json", - "title": "epiphany repo_initialization_accept_intent v0", - "description": "Startup-only repo initialization acceptance intent.", - "type": "object", - "additionalProperties": true, - "properties": { - "kind": { - "type": "string" - }, - "packetPath": { - "type": "string" - }, - "resultPath": { - "type": [ - "string", - "null" - ] - }, - "acceptedBy": { - "type": [ - "string", - "null" - ] - }, - "summary": { - "type": [ - "string", - "null" - ] - }, - "agentStore": { - "type": [ - "string", - "null" - ] - }, - "applySelfPatches": { - "type": "boolean" - }, - "applyTraitSeeds": { - "type": "boolean" - }, - "heartbeatStore": { - "type": [ - "string", - "null" - ] - }, - "applyHeartbeatSeeds": { - "type": "boolean" - } - }, - "required": [ - "kind", - "packetPath" - ] -} diff --git a/schemas/cultnet/epiphany.surface.repo-birth-runner.schema.json b/schemas/cultnet/epiphany.surface.repo-birth-runner.schema.json deleted file mode 100644 index 79c28795..00000000 --- a/schemas/cultnet/epiphany.surface.repo-birth-runner.schema.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.surface.repo-birth-runner.schema.json", - "title": "Epiphany repo-birth-runner", - "description": "Startup-only typed birth runner summary.", - "type": "object", - "additionalProperties": true, - "properties": { - "schemaVersion": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "mode": { - "type": "string" - }, - "repo": { - "type": "string" - }, - "baseline": { - "type": "string" - }, - "artifactDir": { - "type": "string" - }, - "initStore": { - "type": "string" - }, - "agentStore": { - "type": "string" - }, - "heartbeatStore": { - "type": "string" - }, - "startup": { - "type": "object", - "additionalProperties": true - }, - "executions": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "requiresReview": { - "type": "boolean" - }, - "nextSafeMove": { - "type": "string" - } - }, - "required": [ - "schemaVersion", - "createdAt", - "mode", - "repo", - "baseline", - "artifactDir", - "initStore", - "agentStore", - "heartbeatStore", - "startup", - "executions", - "requiresReview", - "nextSafeMove" - ] -} diff --git a/schemas/cultnet/epiphany.surface.repo-initialization.schema.json b/schemas/cultnet/epiphany.surface.repo-initialization.schema.json deleted file mode 100644 index 3366a474..00000000 --- a/schemas/cultnet/epiphany.surface.repo-initialization.schema.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.surface.repo-initialization.schema.json", - "title": "Epiphany repo-initialization", - "description": "Startup-only typed repo initialization summary.", - "type": "object", - "additionalProperties": true, - "properties": { - "schemaVersion": { - "type": "string" - }, - "mode": { - "type": "string" - }, - "action": { - "type": "string" - }, - "repoId": { - "type": "string" - }, - "repo": { - "type": "string" - }, - "baseline": { - "type": "string" - }, - "artifactDir": { - "type": "string" - }, - "initStore": { - "type": "string" - }, - "acceptedKinds": { - "type": "array", - "items": { - "type": "string" - } - }, - "missingKinds": { - "type": "array", - "items": { - "type": "string" - } - }, - "projectionStore": { - "type": [ - "string", - "null" - ] - }, - "generatedPackets": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "requiredActions": { - "type": "array", - "items": { - "type": "string" - } - }, - "requiresReview": { - "type": "boolean" - }, - "nextSafeMove": { - "type": "string" - } - }, - "required": [ - "schemaVersion", - "mode", - "action", - "repoId", - "repo", - "baseline", - "artifactDir", - "initStore", - "acceptedKinds", - "missingKinds", - "generatedPackets", - "requiredActions", - "requiresReview", - "nextSafeMove" - ] -} diff --git a/schemas/cultnet/index.json b/schemas/cultnet/index.json index a098eb2c..a95b5f17 100644 --- a/schemas/cultnet/index.json +++ b/schemas/cultnet/index.json @@ -543,28 +543,6 @@ "title": "Epiphany void-memory", "path": "epiphany.surface.void-memory.schema.json" }, - { - "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.surface.repo-initialization.schema.json", - "kind": "document_payload", - "wireContracts": [ - "cultnet.schema.v0" - ], - "schemaVersion": "epiphany.repo_initialization_surface.v0", - "documentType": "epiphany.surface.repo_initialization", - "title": "Epiphany repo-initialization", - "path": "epiphany.surface.repo-initialization.schema.json" - }, - { - "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.surface.repo-birth-runner.schema.json", - "kind": "document_payload", - "wireContracts": [ - "cultnet.schema.v0" - ], - "schemaVersion": "epiphany.repo_birth_runner_surface.v0", - "documentType": "epiphany.surface.repo_birth_runner", - "title": "Epiphany repo-birth-runner", - "path": "epiphany.surface.repo-birth-runner.schema.json" - }, { "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.runtime.session_intent.v0.schema.json", "kind": "document_payload", @@ -763,28 +741,6 @@ "title": "epiphany repo_startup_intent v0", "path": "epiphany.repo_startup_intent.v0.schema.json" }, - { - "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.repo_initialization_accept_intent.v0.schema.json", - "kind": "document_payload", - "wireContracts": [ - "cultnet.schema.v0" - ], - "schemaVersion": "epiphany.repo_initialization_accept_intent.v0", - "documentType": "epiphany.repo_initialization_accept_intent.v0", - "title": "epiphany repo_initialization_accept_intent v0", - "path": "epiphany.repo_initialization_accept_intent.v0.schema.json" - }, - { - "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.repo_birth_run_intent.v0.schema.json", - "kind": "document_payload", - "wireContracts": [ - "cultnet.schema.v0" - ], - "schemaVersion": "epiphany.repo_birth_run_intent.v0", - "documentType": "epiphany.repo_birth_run_intent.v0", - "title": "epiphany repo_birth_run_intent v0", - "path": "epiphany.repo_birth_run_intent.v0.schema.json" - }, { "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.rider_status_intent.v0.schema.json", "kind": "document_payload", @@ -927,39 +883,6 @@ "documentType": "epiphany.persona_other_request.v0", "title": "Epiphany Persona Other-Surface Crossing Request", "path": "epiphany.persona_other_request.v0.schema.json" - }, - { - "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.repo-personality-artifacts.schema.json", - "kind": "document_payload", - "wireContracts": [ - "cultnet.schema.v0" - ], - "schemaVersion": "epiphany.repo_personality_artifacts.v0", - "documentType": "epiphany.repo_personality_artifacts.v0", - "title": "Epiphany Repo Personality Artifacts", - "path": "epiphany.repo-personality-artifacts.schema.json" - }, - { - "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.repo-initialization-record.schema.json", - "kind": "document_payload", - "wireContracts": [ - "cultnet.schema.v0" - ], - "schemaVersion": "epiphany.repo_initialization_record.v0", - "documentType": "epiphany.repo_initialization_record", - "title": "Epiphany Repo Initialization Record", - "path": "epiphany.repo-initialization-record.schema.json" - }, - { - "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.repo-birth-runner.schema.json", - "kind": "document_payload", - "wireContracts": [ - "cultnet.schema.v0" - ], - "schemaVersion": "epiphany.repo_birth_runner.v0", - "documentType": "epiphany.repo_birth_runner.v0", - "title": "Epiphany Repo Birth Runner Summary", - "path": "epiphany.repo-birth-runner.schema.json" } ] } diff --git a/schemas/repo-personality-birth-projection.md b/schemas/repo-personality-birth-projection.md deleted file mode 100644 index 20be4f91..00000000 --- a/schemas/repo-personality-birth-projection.md +++ /dev/null @@ -1,111 +0,0 @@ -# Repo Personality Birth Projection - -This note explains how a newborn repo's personality reaches the actual Epiphany -body. - -Before this was wired correctly, startup could enrich memories and tweak -heartbeat timing while leaving the canonical trait lattice full of generic -placeholder mush. That was nonsense. This is the repair receipt. - -## Source Paths - -The live implementation is in: - -- [epiphany-repo-personality.rs](/E:/Projects/EpiphanyAgent/epiphany-core/src/bin/epiphany-repo-personality.rs) -- [agent_memory.rs](/E:/Projects/EpiphanyAgent/epiphany-core/src/agent_memory.rs) - -## Birth-Time Flow - -`epiphany-repo-personality` does four relevant things: - -1. scouts repo terrain and history -2. reduces those signals into repo axis scores -3. emits role-local `rolePersonalityProjection` candidates -4. during `accept-init`, optionally stamps canonical trait bundles and heartbeat - seeds into the newborn stores - -## Projection Inputs - -Each role projection currently carries: - -- `traitDeltas` -- `heartbeatDeltas` -- `defaultMoodPressure` -- candidate memory/goal/value/private-note pressure - -The role-local input axes are selected by `role_axes(...)`. For example: - -- Self cares about `boundary_severity`, `contract_strictness`, `state_hygiene`, - `churn_spiral_risk`, `production_pressure` -- Eyes cares about `source_fidelity`, `protocol_intolerance`, - `runtime_proximity`, `novelty_hunger`, `verification_environment_need` -- Hands cares about `production_pressure`, `actuation_risk`, - `contract_strictness`, `consolidation_drive`, `churn_spiral_risk` - -## Canonical Trait Templates - -Each organ has a standing six-trait template: - -- one trait in `underlying_organization` -- one in `stable_dispositions` -- one in `behavioral_dimensions` -- one in `presentation_strategy` -- one in `voice_style` -- one in `situational_state` - -Those templates are the current live Epiphany skeleton. Repo personality does -not invent new trait names at startup. It modulates the standing ones. - -## Stamp Math - -When `accept-init --apply-trait-seeds true` runs for `repo-personality`, -Epiphany deterministically derives an `AgentCanonicalTraitSeed` for each role -family entry. - -For the first five families, a role axis delta is applied to the standing -template: - -- `mean = template.mean + delta * 0.22` -- `plasticity = template.plasticity + abs(delta) * 0.08` -- `current_activation = template.current_activation + delta * 0.28` - -All values are clamped into `0..1`. - -For `situational_state`, Epiphany computes a mood-derived delta from: - -- `urgency` -- `anxiety` -- `curiosity` - -Current weighting: - -- urgency: `0.50` -- anxiety: `0.35` -- curiosity: `0.15` - -That mood delta is then applied through the same template formula. - -## Mutation Boundaries - -This path is birth-only. - -It is not part of normal reviewed `selfPatch` because live personality drift -should not casually rewrite the lattice every time a lane has a feeling. - -Current sanctioned routes are: - -1. initial role-shell provisioning -2. `repo-personality` birth-time trait seeding -3. later work through explicit future machinery, if we choose to build it - -## Related Stores - -- canonical organ state: `state/agents.msgpack` -- heartbeat physiology: `state/agent-heartbeats.msgpack` -- initialization receipts: `state/repo-initialization.msgpack` - -## Operator Rule - -If a newborn repo wakes up with rich memory text but generic `baseline = 0.5` -trait mush, the plumbing is broken. Do not call that personality. Call it a -clerical error and fix the organ. diff --git a/state/agents/README.md b/state/agents/README.md deleted file mode 100644 index 7f7fe161..00000000 --- a/state/agents/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# Epiphany Role Memory - -Epiphany's lane dossiers are Ghostlight-shaped persistent memory reliquaries -stored in `state/agents.msgpack` through CultCache. They are not project truth. -The active objective, graphs, checkpoint, scratch, planning records, evidence, -and job bindings still live in authoritative `EpiphanyThreadState`; role memory -is a lane's soul-polish, not a counterfeit throne. - -Specialists may request bounded self-memory updates through `selfPatch`. -The coordinator reviews those petitions and accepts only role-matched mutations -that improve a lane's future judgment, memory, values, goals, or personality -pressure. Project facts, code edits, graph changes, objective changes, raw -transcripts, and authority requests belong on their explicit Epiphany control -surfaces instead. Smuggling project truth into personality memory is heretek -behavior with better stationery. - -All standing lanes use the same Ghostlight-shaped protocol, including the -coordinator/Self and Persona. Persona is the public interactive surface for Epiphany -agents: it translates useful agent thought-weather into #aquarium chat or drafts -and is not a moderator. The native `epiphany-heartbeat-store` scheduler borrows -Ghostlight initiative timing: each lane carries speed, readiness, reaction bias, -interrupt threshold, load, status, and constraints. The harness sets a target -heartbeat rate, pending coordinator work may pull its owning lane through a -reaction window, and otherwise the earliest ready lane wins the slot. This is -physiology, not a meeting calendar. - -If a heartbeat wakes a lane and there is no coordinator-approved work for it, -the lane must ruminate on its own role and distill memory rather than invent -project work. Bounded rumination can write a normal `selfPatch`; the coordinator -review rules still apply. Idle organs dream; they do not declare crusades. - -Newborn Epiphanies get two separate birth rites. Repo personality initialization -sets subtle temperament pressure once; repo memory initialization pre-fills each -organ with mission-relevant knowledge from doctrine, docs, state, research, -contracts, verification, runtime, and source. The native -`epiphany-repo-personality memory-packet` command renders the role-specific -memory distiller packet. It is a petition to Self, not a direct mutation, and it -must not reset a living lane's learned memory after startup. -When Self reviews a generated distiller result, run -`epiphany-repo-personality accept-init --result ... --agent-store ... --apply-self-patches true` -to apply accepted birth-memory petitions through the same coordinator-reviewed -`selfPatch` machinery used by living lanes. -For the personality birth packet, add -`--heartbeat-store ... --apply-heartbeat-seeds true` after review to seed -initiative speed, reaction bias, cooldown multiplier, and birth mood pressure -in the typed heartbeat store. That seed is a one-time physiology nudge, not a -license to rerun personality initialization whenever the repo mood changes. - -Persona's public mouth is a Bifrost crossing, not local OAuth improvisation. -`state/persona-discord.toml` confines Discord speech to #aquarium and -`state/persona-reddit.toml` confines Reddit organizing posts to the configured -subreddit. If a channel, subreddit, bridge path, Bifrost identity, or -surface-shaped Heimdall-backed capability/account reference is missing, Persona -writes candidate artifacts through the native draft commands instead of -posting. The public mouth does not invent new pulpits. - -`epiphany-persona-discord post` and `epiphany-persona-reddit post` audit speech -at the mouth edge, then ask Bifrost to perform the external crossing. Bifrost is -the GameCult identity provider and bridge receipt owner; native Bifrost -registration does not require OAuth, and a Bifrost identity is the default -authenticating identity for GameCult services that accept Bifrost trust. Heimdall -is the external-account association and token-custody organ: it links Discord, -Reddit, GitHub, or other provider accounts to the Bifrost identity and -contributes surface-shaped capability/account references such as -`heimdall:discord:*` or `heimdall:reddit:*`. Bifrost must know the Persona's -registered identity and same-account Heimdall link before authorizing an -outside-provider crossing. Persona nickname, avatar, and Reddit flair are -presentation metadata only, not authority, voting power, or a separate platform -identity. - -Void memory access lives behind `state/void-memory.toml` and the native -`epiphany-void-memory` bridge. It checks Void's Docker Postgres state spine, -queries Qdrant history/source collections with the configured Ollama embedding -model, and fetches raw Discord archive context for exact message windows. Persona -may use those surfaces to ground speech; raw archive rows are evidence, not the -speech itself. - -Use: - -```powershell -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-agent-memory-store -- validate --store .\state\agents.msgpack -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-agent-memory-store -- smoke --store .\state\agents.msgpack -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-agent-memory-store -- status --store .\state\agents.msgpack -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-heartbeat-store -- tick --store .\state\agent-heartbeats.msgpack --artifact-dir .\.epiphany-heartbeats --coordinator-action continueImplementation --urgency 0.95 --agent-store .\state\agents.msgpack --apply-rumination -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-heartbeat-store -- smoke --agent-store .\state\agents.msgpack -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-persona-discord -- draft --content 'Persona notices the organs are arguing about evidence again.' -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-persona-discord -- post --content 'Persona has entered the aquarium.' --persona-name 'Epiphany Persona' -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-persona-discord -- smoke -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-persona-reddit -- draft --title 'Epiphany status' --content 'Persona has a bounded public update.' -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-persona-reddit -- post --title 'Epiphany status' --content 'Persona has a bounded public update.' -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-persona-reddit -- smoke -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-void-memory -- status -cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-void-memory -- search-history --query 'Epiphany Aquarium Persona Discord' --limit 3 -``` diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index ad6b1009..439abe84 100644 Binary files a/state/ledgers.msgpack and b/state/ledgers.msgpack differ diff --git a/state/map.yaml b/state/map.yaml index 0a3408fe..3bc748f4 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -48,9 +48,9 @@ current_status: package_baseline: Exact d2ca66301fb6af4e7d2d27fff0b772b0f0fccdf4 remains the admitted production source and current symlink target. Its native serialized full-workspace gate passed with receipt SHA-256 de0fc6b360ce03493b13208d917dc8349801f03364617d966152c85846c47482. Idunn sealed 26 root-owned non-writable binaries plus release-witness.ccmp as release sha256-46407552b4a0937f63d2b7f2bd09a1dacb89d671a6e3807c97209159541aef06; witness-file SHA-256 is 348785ffb0fc3130d3b4538329870c6e6f8442a8da4a01b32fa9b7ffb1f01357. Source 9b9b5c85 also passed Idunn's native test gate under receipt SHA-256 6c6a71359f8c31297419665d2872f6982a89f84e197d71fc5b36a7fc86216093 and remains sealed as release sha256-30a910785a380224bbdbd56a7b742c8d058fa70cb72dab3562e3907903e0d191 with witness-file SHA-256 75bf52d31c56ea3b08e1a702273301c1db12a4b1ffae45e3d948ce8a29b50e1d. A foreign-target helper restarted Idunn during the Epiphany transaction; rollback restored d2ca, removed deployment.env, and left production Epiphany units inactive. OpenRouter model stealth/ox-alpha and its systemd-injected credential are readiness-proven. Idunn is active with zero restarts and its deployment brake is engaged. summary: The keyed RepoModel hard cut, decision-context foundation, Model Atlas code slice, complete Body, Proposal, frontier-verdict Modeling, frontier Research, frontier Verification, frontier Planning/PlanMind, Reorientation, and Persona decision-context lifecycles are landed on codex/epiphany-shakedown-live. Keyed Mind plus exact runtime receipts own coordinator presence, work discovery, deterministic launch, job-bound continuation, review, typed failure, semantic admission, and retained decision reachability. Exact 07d891ba and ec1431ff delete the persisted/global thread aggregate and its generic coordinator/prompt/revision surfaces. Exact f7948795 deletes MindGatewayReview, MindStateCommitReceipt v0, the generic interpreter prompt, runtime registration/read APIs, false thought/state-effect/adoption CultNet mouths, and stale launch proof profiles. CultMesh now projects only reasoning basis, decision context, and exact Mind commit receipt, read-only. Exact 1c9aafd8 seals the terminal decision context before model-backed frontier-Planning failure and binds the typed failure plus generic job result to it. Exact e0e75a30 deletes the residual aggregate role patch/parser/policy surface, gives Research one closed typed decision, and deletes the unowned generic Imagination planning patch. Exact 12b1b285 makes runtime model/tool binding validation the shared owner for execution and decision sealing; hostile provider, receipt, and stored-binding substitutions refuse, while retained basis/context audit survives transcript-delta deletion. Exact 01602fd3 advances Mind/runtime to v2/v1 and refuses historical or split writable identity pairs before registered writers see them. Exact 26b6a5bf proves simultaneous disjoint Modeling writes, simultaneous Verification+Modeling admission, same-identity conflict, exact replay, and reopened graph assembly; raw mutation primitives are crate-private. Exact 7c2ebd81 moves Persona state-note admission from the legacy agent-memory aggregate into keyed Mind documents and proves a concrete Hands consequence can commit simultaneously. Exact 79c0e373 seals the assembled Persona input and observed source versions in Mind before inference. Exact d3300bba authenticates that input through its one exact Mind commit, refuses naked typed documents, re-enters without external observation or model inference, and proves current-work reconstruction is identical and read-only across Launch, Wait, completed, and post-Research boundaries. No compatibility reader, bootstrap aggregate, schema migrator, or dual reader survives. The source-level Decision-Auditable Concurrent Mind migration is accepted; the fresh exact-package capstone remains open. The machine remains a supervised engineering alpha; Model Atlas operational Gate 1 stays paused. public_positioning: docs/positioning.md is the current public claim owner. Epiphany is the organizational layer above frontier AI workers; global coherence collapse is the primary failure; prompts are bounded projections of governed organizational state; humans retain purpose, judgment, authority, disagreement, exceptions, and acceptance. GameCult is the forge and longitudinal evidence program for a target federation of one locally owned Epiphany project organism per living project Body. Model Atlas carries typed offers, claims, evidence, and derived technical relationships; Bifrost is the intended external work, membership, governance, attribution, and identity-linked receipt rail. Public Persona relationships and visitor surfaces are intended social infrastructure, while private Mind and consequence authority remain local. The fleet, real three-repository Gate 1, complete Bifrost attribution, sustained autonomous production, visitor experience, and external design-partner pilots remain gated alpha targets. - latest_phase: Exact 749d977e proved in private Ox16 that direct Body Modeling launches without Eyes while Persona runs concurrently and that a completed historical direction decision remains valid across later disjoint RepoModel changes. Planning then remained structurally unavailable because Modeling used inspected evidence paths as future path authority, omitted OX-CAPSTONE.md, and emitted an unordered scope. Exact 5b799b12 hard-cuts the overloaded source_scope/sourceScope contract: repository_scope is the canonical sorted repository-relative ceiling that Planning may narrow, Hands receives only authorized_paths, and generic RepoModel validation refuses an invalid unresolved routed frontier before any graph write. Inspected files and evidence remain separate reasoning cargo. Mind/runtime/RepoModel epochs advance to v4/v5/v2 without compatibility reads. Local core 494/494, OpenAI runtime 26/26, coordinator 12/12, swarm 10/10, and model-runtime 13/13 pass. Ox16 is sealed historical evidence and must never resume. + latest_phase: The fresh-package Ox17 lane is paused for source subtraction. Yggdrasil exposed and then released 96 GiB of dead native Cargo targets. The first causal cut removes 19,252 net non-vendor lines: 21 duplicate core/release binary declarations, 19 generic repo-request smoke executables that cargo test compiled but never executed as tests, the forbidden runtime-store migrator CLI, and the unshipped repo-personality birth subsystem with its parallel agents.msgpack/heartbeat initialization, prompt fields, schemas, and stale contract plans. epiphany-core falls from 75 to 30 binary targets and duplicate executable ownership is zero. Focused core library acceptance remains 494/494; Hands under its sole release-package owner remains 5/5; the CultNet schema catalog and local PowerShell syntax parse. Build-profile and janitor patches remain deferred. The next cut audits the legacy agent-memory aggregate, runtime migration library/tests, and local PowerShell operator control plane against keyed Mind and Eve ownership. Exact 103e8510 remains pushed but is not authorized for another Idunn build. Ox16 remains sealed historical evidence and must never resume. operational_state: - local_worktree: source_head_5b799b12_committed_and_pushed_repository_scope_authority_cut_docs_and_ox17_preparation_in_progress + local_worktree: source_head_103e8510_pushed_first_subtraction_cut_verified_uncommitted_ox17_and_deployment_paused docker_engine_access: starfire_compiler_retired_all_yggdrasil_epiphany_transient_containers_volumes_package_roots_and_per_run_test_target_removed_future_idunn_epiphany_builds_native_rust_1_95_0_via_gamecult_ops_89f0d78 operator_wireguard: restored starfire_persona_permit_firewall: persistent_exact_gamecult_ops_6ee08a4 @@ -90,8 +90,8 @@ current_status: starfire_linux_modeling_peak: 109887488_bytes_resident_body_plus_modeling_circuit yggdrasil_epiphany_service_contract: idunn_source_change_owner_eve_and_local_typed_operator_surfaces_no_discord_control_plane resident_physiology: historical_c011_exact_465af24d_zero_zombie_proof_accepted_current_containers_exited_255 - unresolved_authority: The Decision-Auditable Concurrent Mind cut, explicit OpenRouter/Ox provider boundary, and repository-scope ownership cut are source-complete; fresh exact-package Ox17 remains open. Bifrost remains Persona and governed external-consequence transport; Eve/CultUI and local typed tools remain human and enginseer surfaces. Model Atlas Gate 1 remains paused. Historical c011 and partial Gate roots stay untouched/read-only. - next_action: Let Idunn natively compile, test, and seal exact 5b799b12 without triggering another target or moving the historical production store. Use only that exact package in private fresh-store Ox17; stop after three provider failures and prove direct Body Modeling without Eyes, concurrent Persona, typed refusal-to-retry if exercised, Planning-Mind-Hands-Verification-Modeling, exact decision audit, and restart/re-entry without public speech. Never resume Ox10, Ox12, Ox13, Ox15, or Ox16, transplant decisions, restore event/coordinator authority, trigger non-Epiphany targets, or compile on Starfire. + unresolved_authority: The first executable-ownership cut is coherent, but `agent_memory` and `state/agents.msgpack` still expose a parallel durable role-memory aggregate beside keyed Mind, `runtime_store_migration` still preserves a forbidden writable migration law after its CLI was deleted, and the 2,000-line local PowerShell operator wrapper still keeps unshipped direct Persona/operator paths alive beside Eve and the packaged runtime. Fresh exact-package Ox17 and Model Atlas Gate 1 remain paused until those owners are resolved. Bifrost remains Persona and governed external-consequence transport. Historical c011 and partial Gate roots stay untouched/read-only. + next_action: Map and cut the legacy agent-memory aggregate, runtime-store migration module/tests, and local PowerShell operator control plane. A test survives only when it falsifies a named live invariant at its owning boundary; green serialization mirrors, manual main-only smokes, and tests that preserve deleted authorities do not. Do not run a full-workspace compile, package, deployment, or Ox root before this subtraction audit closes. active_subgoals: - Complete the fresh exact-package Decision-Auditable Concurrent Mind capstone before any further operational Gate run. diff --git a/state/scratch.md b/state/scratch.md index c184b28d..553ce13e 100644 --- a/state/scratch.md +++ b/state/scratch.md @@ -2,84 +2,64 @@ Disposable working memory for one bounded rite. -## Current Subgoal +## Current subgoal: subtraction audit -Continue organ shakedown after accepting exact braked resident physiology. -Keep actual c006 alive and cognitively braked; Yggdrasil never builds. +The fresh-package Ox17 deployment lane is paused. Do not compile, package, +deploy, or resume a historical Ox root until this audit has made the source +machine smaller and the canonical map explicitly reopens that lane. -## Accepted braked physiology cut +## Triggering evidence -Exact pushed `3f37503e` blocks cognition ingress under brake; exact pushed -`f04abd6b` makes runtime maintenance follow startup, resident revision, or a -60-second deadline rather than poll cadence. Exact `f04abd6b` release -`sha256-544b95404d9445b2af0fdd67495bcff5f284ab2d2a29e16450e90d50f72a3f84` -and witness `sha256-79639e35da79f7038111234fa33e21829e88eddcb77eb702f8f53f5369e9def3` -passed 24-binary inspection. Braked copied state held 0.29-0.33% CPU at -one-second cadence; released sleeping state used 91 process ticks over 30 -seconds at five-second cadence (about 3.0% of one core). Revision 66, runtime -hash, and no-grant state stayed exact; the brake was restored and both proofs -exited zero. The readiness-pacing hypothesis did not move CPU and was reverted. +- Idunn's interrupted native build left three commit-keyed test targets of + 24 GiB, 27 GiB, and 27 GiB plus 19 GiB of release construction caches. + All were dead compiler scratch and were removed on Yggdrasil; the Cargo + target root returned to 4 KiB. +- One target consisted of 19 GiB `debug/deps`, 4.9 GiB incremental state, and + 388 MiB build-script output. Several individual test harnesses were + 500--700 MiB. +- `epiphany-core` declared 75 binary targets. The root release bundle declared + another 26 targets, many by pointing at the same source files. The first cut + reduces core to 30 binaries with zero duplicate owners and removes 19,252 net + non-vendor lines. -## Accepted live boundary +The storage failure is evidence of source-shape failure. Build-profile flags +and janitors may reduce the symptom but cannot substitute for deleting code. -Exact packaged `31dcd351` gave Modeling governed directory inventory and it -measured the copied actual Heartbeat artifact family as 72 directories, 74 -files, and 3,975,362 regular-file bytes with `complete=true`. It correctly -refused to infer post-brake grant terminality from pulse names. Self settled the -proposal grant and two receipt-bound continuations, ended sleeping at revision -54 with 57/57 runtime jobs/results and zero open jobs, then both copied owners -stopped cleanly under the restored brake. Actual c006 was untouched. +## Authority map -## Accepted lifecycle sight +- Owner: each live organ or operator tool owns one executable boundary only + when process isolation, privilege isolation, lifecycle ownership, or an + independently deployable contract requires it. +- Inputs: the canonical runtime/service maps, Idunn's immutable release bundle, + current systemd manifests, operator-tool call sites, and source-grounded test + coverage. +- Outputs: a minimal release binary set and library/integration tests for + verification that does not need a separately shipped process. +- Derived state: smoke runners, fixtures, benchmarks, and migration probes are + test/development surfaces. They do not become runtime organs merely because + Cargo can compile them as binaries. +- Forbidden writers: historical docs, stale smoke commands, and previous + package manifests cannot preserve an executable that no live authority uses. +- Shared paths: development verification and Idunn acceptance must exercise the + same library/runtime contracts without compiling duplicate wrappers. +- Cut line: delete unowned legacy executables; collapse table-shaped smoke + families into ordinary tests; give each surviving production entrypoint one + Cargo package owner; remove root/core duplicate bin declarations; then + reassess crate boundaries and the release list. Do not add build cleanup or + cache machinery as a substitute for this cut. -Exact pushed `34d75d05`, release -`sha256-718316c4372f3caf127579cd8a185fe11717f2370f1418d9a1f465a0efa54b8c`, -witness -`sha256-58d4c05c3de5b9c07ea76e06a2f60af94571a0f053da49de65629f413e0cbd92` -exposes only the explicitly bound resident store. The exact grant envelope and -resident state own lifecycle truth; scheduling and projection share -`resident_self_grant_is_pending`. Artifact names and terminal acks do not own -launchability. +## Immediate audit order -Copied live Modeling invoked this organ. Attempts one through three failed -strict typed admission because the model duplicated `checkpointSummary` or -`verdict`; resident Self requeued them without consequence. Attempt four -completed and reported sampled older terminal grants `launchable=false` and -the active consumed grant `launchable=false`. Under an engaged copied brake, -Self reconciled the completed evidence, cleared the active turn at revision 74, -and remained braked. No new grant appeared; both copied owners exited zero. - -## Accepted first-result publication - -Exact pushed `9f82ee38` replaces the generic full-patch proposal mouth with a -closed semantic draft and runtime-owned causal composition. Exact pushed -`e723058a` adds the missing provider-required type beside schema constants. -Release `sha256-6428f9194c261806a8859ccff9efeb9bb041a0103c5bbd8fac617aae265a651e`, -witness `sha256-7ee2e14548bad526e946fbd79b555b7891d2eb6e2917856fefb1ebb7aeebd995`, -passed 24-binary inspection. Fresh copied live request -`repo-frontier-proposal-modeling-c6e584188580f373360a1b9d5113e2df67a22dd16c7cefcd97361067862b6d4b` -completed through one outer job on attempt one, bound one runtime-owned -frontier, terminalized its proposal grant, and ended with zero open jobs and no -Hands/Eyes frontier. Both owners exited zero under the restored brake. - -## Review continuation authority map - -- Owner: exact runtime-linked role-result terminality decides review readiness; - resident Self converts one exact receipt into one deterministic pressure. -- Inputs: the exact completed result, proposal binding/admission state, prior - review receipt, and resident continuation lifecycle. -- Output: no pending-result continuation; at most one review continuation for one exact result, followed by one - terminal settled projection. -- Forbidden cut: do not suppress review with a cooldown, ack tombstone, log - heuristic, or retry ceiling; move the consumed fact to the exact result or - its owning typed review transaction. -- Verification: fresh copied proposal produces one proposal grant, one outer - Modeling job, at most one review continuation, exact settlement, zero open - jobs, and no Hands, Persona, or deployment consequence. - -Exact packaged `d4907ee8` proves the complete cut. Fresh copied request -`repo-frontier-proposal-modeling-507ad0a7dda5c8cc076b910f02d19c196b4cd5bb63e06ab25724aaac148897f8` -launched one outer Modeling job, received one terminal review, and then -published `awaitFrontierProposal`. No second review grant appeared. Runtime -ended with zero open jobs, Hands/Eyes false, and both owners exited zero under -the restored copied brake. Actual c006 was untouched. +1. Map `agent_memory` and every `state/agents.msgpack` consumer against keyed + Mind. Delete memories, social state, migrations, projections, and tests whose + only purpose is to preserve the parallel aggregate. +2. Delete `runtime_store_migration` and its tests unless a live fresh-epoch path + proves an owner; the approved migration explicitly has no writable migrator. +3. Map the local PowerShell operator wrapper and its direct Persona/operator + binaries against Eve, Bifrost, and the packaged runtime. Delete the parallel + control plane rather than repairing its modes. +4. Continue classifying remaining smoke/fixture/benchmark executables. A test + must falsify a named live invariant; compilation or a green mirror assertion + is not enough. +5. Run focused tests after each cut. Only after source subtraction stabilizes + should Idunn's build scratch contract be reconsidered. diff --git a/tools/epiphany_local_run.ps1 b/tools/epiphany_local_run.ps1 index bbcfbb4c..536c810d 100644 --- a/tools/epiphany_local_run.ps1 +++ b/tools/epiphany_local_run.ps1 @@ -1,5 +1,5 @@ param( - [ValidateSet("status", "plan", "smoke", "run", "mvp", "agent-state-soa", "swarm-status", "swarm-poke-down", "swarm-triage", "cluster-topology", "eve-surfaces", "eve-connect", "collaboration-feedback", "persona-discord", "persona-reddit", "persona-other", "daemon-survival-rehearsal", "repo-livefire-closure", "bifrost-publication", "bifrost-public-proof", "bifrost-artifact-acceptance", "bifrost-metrics", "receipt-directory", "tool-directory", "tool-invoke", "swarm-overview", "repo-persona-intake", "repo-swarm-run", "repo-work-queue-run", "repo-work-public-proof", "repo-work-readiness", "repo-deployment-config-audit", "repo-deployment-runbook", "repo-deployment-aftercare-audit", "repo-work-service-plan", "repo-work-service-runbook", "repo-work-service-launch", "repo-work-service-audit", "service-policy-directory", "service-plan", "service-launch", "service-runbook", "service-tick", "managed-service-task-plan", "managed-service-task-install", "managed-service-task-status", "managed-service-task-start", "managed-service-task-stop", "managed-service-task-uninstall")] + [ValidateSet("status", "plan", "smoke", "run", "mvp", "agent-state-soa", "swarm-status", "swarm-poke-down", "swarm-triage", "cluster-topology", "eve-surfaces", "eve-connect", "collaboration-feedback", "persona-discord", "persona-reddit", "persona-other", "daemon-survival-rehearsal", "bifrost-publication", "bifrost-public-proof", "bifrost-artifact-acceptance", "bifrost-metrics", "receipt-directory", "tool-directory", "tool-invoke", "swarm-overview", "repo-persona-intake", "repo-swarm-run", "repo-work-queue-run", "repo-work-public-proof", "repo-work-readiness", "repo-deployment-config-audit", "repo-deployment-runbook", "repo-deployment-aftercare-audit", "repo-work-service-plan", "repo-work-service-runbook", "repo-work-service-launch", "repo-work-service-audit", "service-policy-directory", "service-plan", "service-launch", "service-runbook", "service-tick", "managed-service-task-plan", "managed-service-task-install", "managed-service-task-status", "managed-service-task-start", "managed-service-task-stop", "managed-service-task-uninstall")] [string]$Mode = "smoke", [string]$Root = (Resolve-Path ".").Path, [string]$Workspace = "", @@ -56,10 +56,6 @@ param( [int]$RepoSwarmMaxIterations = 8, [string]$RepoSwarmUntil = "blocked-or-published", [string]$RepoWorkItem = "persona-intake-local", - [string]$RepoLivefireExpectedPath = "README.md", - [string]$RepoLivefireExpectedFamily = "repo.status_section", - [string]$RepoLivefireExpectedGate = "awaiting-publication", - [string]$RepoLivefireExpectedBlocker = "bifrost-publication-missing", [string]$RepoWorkPublicProofOutput = "", [string]$RepoWorkRuntimeId = "repo-swarm-local", [string]$RepoWorkPublicProof = "", @@ -280,7 +276,6 @@ $operatorRunExe = Join-Path $TargetDir "debug\epiphany-operator-run.exe" $operatorSnapshotExe = Join-Path $TargetDir "debug\epiphany-operator-snapshot.exe" $verseQueryExe = Join-Path $TargetDir "debug\epiphany-verse-query.exe" $daemonSurvivalRehearsalExe = Join-Path $TargetDir "debug\epiphany-daemon-survival-rehearsal-smoke.exe" -$repoLivefireClosureSmokeExe = Join-Path $TargetDir "debug\epiphany-repo-livefire-closure-smoke.exe" $swarmExe = Join-Path $TargetDir "debug\epiphany-swarm.exe" $repoWorkExe = Join-Path $TargetDir "debug\epiphany-work.exe" $daemonSupervisorExe = Join-Path $TargetDir "debug\epiphany-daemon-supervisor.exe" @@ -339,7 +334,6 @@ if (-not $SkipBuild) { "--bin", "epiphany-operator-snapshot", "--bin", "epiphany-verse-query", "--bin", "epiphany-daemon-survival-rehearsal-smoke", - "--bin", "epiphany-repo-livefire-closure-smoke", "--bin", "epiphany-swarm", "--bin", "epiphany-work", "--bin", "epiphany-daemon-supervisor", @@ -382,10 +376,6 @@ $requiredBinaries = @($statusExe, $operatorRunExe, $operatorSnapshotExe, $verseQ if ($Mode -eq "daemon-survival-rehearsal") { $requiredBinaries += @($daemonSurvivalRehearsalExe) } -if ($Mode -eq "repo-livefire-closure") { - $requiredBinaries += @($repoLivefireClosureSmokeExe) -} - function Format-ServiceExecutionFailedChecks { param([object]$Rows) @@ -472,7 +462,7 @@ function Assert-SwarmBrakeAllowsLiveRun { } } -if (@("plan", "run", "mvp", "repo-persona-intake", "repo-swarm-run", "repo-work-queue-run", "repo-livefire-closure", "repo-work-public-proof", "repo-work-readiness", "repo-deployment-config-audit", "repo-deployment-runbook", "repo-deployment-aftercare-audit", "repo-work-service-plan", "repo-work-service-runbook", "repo-work-service-launch", "repo-work-service-audit", "service-plan", "service-launch", "service-runbook", "service-tick", "managed-service-task-plan", "managed-service-task-install", "managed-service-task-status", "managed-service-task-start", "managed-service-task-stop", "managed-service-task-uninstall") -contains $Mode) { +if (@("plan", "run", "mvp", "repo-persona-intake", "repo-swarm-run", "repo-work-queue-run", "repo-work-public-proof", "repo-work-readiness", "repo-deployment-config-audit", "repo-deployment-runbook", "repo-deployment-aftercare-audit", "repo-work-service-plan", "repo-work-service-runbook", "repo-work-service-launch", "repo-work-service-audit", "service-plan", "service-launch", "service-runbook", "service-tick", "managed-service-task-plan", "managed-service-task-install", "managed-service-task-status", "managed-service-task-start", "managed-service-task-stop", "managed-service-task-uninstall") -contains $Mode) { if (-not (Test-Path -LiteralPath $verseQueryExe)) { throw "required binary not found for swarm brake preflight: $verseQueryExe" } @@ -571,7 +561,7 @@ Invoke-Checked ` -StdoutPath (Join-Path $artifactRoot "operator-run-intent.stdout.json") ` -StderrPath (Join-Path $artifactRoot "operator-run-intent.stderr.log") -$compactOperatorContextModes = @("agent-state-soa", "swarm-status", "swarm-poke-down", "swarm-triage", "cluster-topology", "eve-surfaces", "eve-connect", "collaboration-feedback", "daemon-survival-rehearsal", "repo-livefire-closure", "bifrost-publication", "bifrost-public-proof", "bifrost-artifact-acceptance", "bifrost-metrics", "receipt-directory", "tool-directory", "tool-invoke", "swarm-overview", "service-policy-directory") +$compactOperatorContextModes = @("agent-state-soa", "swarm-status", "swarm-poke-down", "swarm-triage", "cluster-topology", "eve-surfaces", "eve-connect", "collaboration-feedback", "daemon-survival-rehearsal", "bifrost-publication", "bifrost-public-proof", "bifrost-artifact-acceptance", "bifrost-metrics", "receipt-directory", "tool-directory", "tool-invoke", "swarm-overview", "service-policy-directory") $usesCompactOperatorContext = $compactOperatorContextModes -contains $Mode $shouldReadLocalVerse = $Mode -ne "smoke" -and -not $usesCompactOperatorContext if ($shouldReadLocalVerse -and $Mode -ne "status" -and $Mode -ne "mvp") { @@ -986,24 +976,6 @@ if ($Mode -eq "daemon-survival-rehearsal") { -StderrPath (Join-Path $artifactRoot "daemon-survival-rehearsal.stderr.log") } -if ($Mode -eq "repo-livefire-closure") { - $resultPath = Join-Path $artifactRoot "repo-livefire-closure.stdout.json" - Invoke-Checked ` - -Label "verify repo live-fire closure proof" ` - -FilePath $repoLivefireClosureSmokeExe ` - -Arguments @( - "--workspace", $Workspace, - "--item", $RepoWorkItem, - "--expected-path", $RepoLivefireExpectedPath, - "--expected-family", $RepoLivefireExpectedFamily, - "--expected-gate", $RepoLivefireExpectedGate, - "--expected-blocker", $RepoLivefireExpectedBlocker - ) ` - -WorkingDirectory $Root ` - -StdoutPath $resultPath ` - -StderrPath (Join-Path $artifactRoot "repo-livefire-closure.stderr.log") -} - if ($Mode -eq "receipt-directory") { $resultPath = Join-Path $artifactRoot "receipt-directory.stdout.json" Invoke-Checked ` @@ -1822,12 +1794,6 @@ if ($resultPath -ne "" -and (Test-Path -LiteralPath $resultPath)) { Write-Host "Metrics request: status=$($result.status), item=$($result.item), branch=$($result.sourceBranch), responseOwner=$($result.responseOwner), privateStateExposed=$($result.privateStateExposed)" } elseif ($Mode -eq "daemon-survival-rehearsal") { Write-Host "Daemon survival rehearsal: status=$($result.status), daemon=$($result.daemonId), scheduler=$($result.schedulerId), policy=$($result.policyStatus), serve=$($result.serveStatus), iterations=$($result.serveIterations), schedulerReceipt=$($result.schedulerReceiptId), serviceManagerMutated=$($result.serviceManagerMutated), elevated=$($result.requiresElevatedAuthority), privateStateExposed=$($result.privateStateExposed), smokeDir=$($result.smokeDir)" - } elseif ($Mode -eq "repo-livefire-closure") { - $changedPaths = "none" - if ($null -ne $result.changedPaths -and $result.changedPaths.Count -gt 0) { - $changedPaths = ($result.changedPaths -join ",") - } - Write-Host "Repo live-fire closure: status=$($result.status), item=$($result.item), branch=$($result.branch), commit=$($result.commitSha), family=$($result.safeActionFamily), paths=$changedPaths, gate=$($result.currentGate), blocker=$($result.blocker), publicationGate=$($result.publicationGate), soul=$($result.soulVerdict), mindCommit=$($result.mindStateCommitReceiptId), privateStateExposed=$($result.privateStateExposed)" } elseif ($Mode -eq "receipt-directory") { $artifactHashes = "none" $attentionRoutes = "none"