diff --git a/epiphany-core/src/bin/epiphany-repo-work-readiness-smoke.rs b/epiphany-core/src/bin/epiphany-repo-work-readiness-smoke.rs new file mode 100644 index 00000000..d66ced52 --- /dev/null +++ b/epiphany-core/src/bin/epiphany-repo-work-readiness-smoke.rs @@ -0,0 +1,431 @@ +use anyhow::anyhow; +use anyhow::Context; +use anyhow::Result; +use chrono::Utc; +use serde_json::json; +use serde_json::Value; +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, + smoke_root: PathBuf, +} + +impl Args { + fn parse() -> Result { + let mut root = env::current_dir().context("failed to resolve current directory")?; + let mut smoke_root = root.join(".epiphany-smoke"); + 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")?, + "--smoke-root" => smoke_root = take_path(&mut args, "--smoke-root")?, + other => return Err(anyhow!("unexpected argument {other:?}")), + } + } + Ok(Self { root, smoke_root }) + } +} + +fn run_smoke(args: Args) -> Result { + let root = args + .root + .canonicalize() + .with_context(|| format!("failed to resolve {}", args.root.display()))?; + 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(&args.smoke_root) + .with_context(|| format!("failed to create {}", args.smoke_root.display()))?; + let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string(); + let smoke_dir = args.smoke_root.join(format!("repo-work-readiness-{stamp}")); + if smoke_dir.exists() { + fs::remove_dir_all(&smoke_dir) + .with_context(|| format!("failed to clear {}", smoke_dir.display()))?; + } + fs::create_dir_all(&smoke_dir) + .with_context(|| format!("failed to create {}", 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 Readiness Smoke\n\nThis repository proves readiness sight without readiness approval.\n", + ) + .with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?; + git(["add", "README.md"], &repo)?; + git( + ["commit", "-m", "Seed repo work readiness smoke body"], + &repo, + )?; + git(["switch", "-c", "epiphany/repo-work-readiness"], &repo)?; + + let item = "repo-work-readiness"; + 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-readiness-smoke", + "--topic", + "repo-work-readiness", + ], + &root, + )?; + cargo_json( + &manifest, + "epiphany-swarm", + &[ + "online", + "--workspace", + path_str(&repo)?, + "--epiphany-root", + path_str(&root)?, + "--runtime-id", + "repo-work-readiness-smoke", + "--local-verse-store", + path_str(&local_verse)?, + ], + &root, + )?; + cargo_json( + &manifest, + "epiphany-work", + &[ + "accept", + "--workspace", + path_str(&repo)?, + "--from", + "persona", + "--item", + item, + "--summary", + "Prove repo-work readiness sight can name missing publication and lifecycle gates without granting authority.", + "--candidate-action-ref", + "candidate-action://repo-work-readiness/checklist", + "--public-discussion-ref", + "epiphany-global/persona-collaboration/repo-work-readiness", + ], + &root, + )?; + cargo_json( + &manifest, + "epiphany-work", + &[ + "derive-plan", + "--workspace", + path_str(&repo)?, + "--item", + item, + "--action-family", + "checklist-note", + "--model-ref", + "repo-work-readiness-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, + )?; + } + cargo_json( + &manifest, + "epiphany-work", + &[ + "queue-run", + "--workspace", + path_str(&repo)?, + "--local-verse-store", + path_str(&local_verse)?, + "--runtime-id", + "repo-work-readiness-smoke", + "--max-items", + "1", + "--dry-run", + ], + &root, + )?; + let public_proof = cargo_json( + &manifest, + "epiphany-work", + &[ + "export-proof", + "--workspace", + path_str(&repo)?, + "--item", + item, + "--local-verse-store", + path_str(&local_verse)?, + "--runtime-id", + "repo-work-readiness-smoke", + ], + &root, + )?; + let readiness = cargo_json( + &manifest, + "epiphany-work", + &["readiness", "--workspace", path_str(&repo)?, "--item", item], + &root, + )?; + + require_eq( + &readiness, + &["schemaVersion"], + "epiphany.repo_work_readiness.v0", + )?; + require_eq(&readiness, &["status"], "not-ready")?; + require_u64(&readiness, &["missingRequiredCount"], 4)?; + require_bool(&readiness, &["authority", "sightOnly"], true)?; + require_bool( + &readiness, + &["authority", "readinessApprovalAuthorized"], + false, + )?; + require_bool(&readiness, &["authority", "publicationAuthorized"], false)?; + require_bool(&readiness, &["authority", "deploymentAuthority"], false)?; + require_bool( + &readiness, + &["authority", "serviceLifecycleAuthority"], + false, + )?; + require_bool(&readiness, &["authority", "handsActionAuthorized"], false)?; + require_bool(&readiness, &["authority", "privateStateExposed"], false)?; + require_row(&readiness, "repo-init", true)?; + require_row(&readiness, "swarm-online", true)?; + require_row(&readiness, "persona-intake", true)?; + require_row(&readiness, "imagination-plan", true)?; + require_row(&readiness, "self-queue-run", true)?; + require_row(&readiness, "hands-branch-work", true)?; + require_row(&readiness, "soul-closure", true)?; + require_row(&readiness, "modeling-mind-admission", true)?; + require_row(&readiness, "public-proof", true)?; + require_row(&readiness, "bifrost-publication", false)?; + require_row(&readiness, "upstream-main-sync", false)?; + require_row(&readiness, "idunn-lifecycle", false)?; + require_row(&readiness, "tool-directory", false)?; + require_row(&readiness, "private-state-redaction", true)?; + + let summary = json!({ + "schemaVersion": "epiphany.repo_work_readiness_smoke.v0", + "status": "ok", + "smokeDir": smoke_dir, + "repo": repo, + "branch": git_output(["branch", "--show-current"], &repo)?, + "item": item, + "publicProofOutput": public_proof["outputPath"], + "readinessStatus": readiness["status"], + "missingRequiredCount": readiness["missingRequiredCount"], + "sightOnly": readiness["authority"]["sightOnly"], + "readinessApprovalAuthorized": readiness["authority"]["readinessApprovalAuthorized"], + "publicationAuthorized": readiness["authority"]["publicationAuthorized"], + "deploymentAuthority": readiness["authority"]["deploymentAuthority"], + "serviceLifecycleAuthority": readiness["authority"]["serviceLifecycleAuthority"], + "handsActionAuthorized": readiness["authority"]["handsActionAuthorized"], + "privateStateExposed": readiness["privateStateExposed"], + }); + 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 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_u64(value: &Value, path: &[&str], expected: u64) -> Result<()> { + let actual = path + .iter() + .try_fold(value, |current, key| current.get(*key)) + .and_then(Value::as_u64) + .unwrap_or(u64::MAX); + 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) + .unwrap_or(!expected); + if actual == expected { + Ok(()) + } else { + Err(anyhow!( + "expected {} to be {expected:?}, got {actual:?}", + path.join(".") + )) + } +} + +fn require_row(value: &Value, kind: &str, expected_satisfied: bool) -> Result<()> { + let rows = value + .get("rows") + .and_then(Value::as_array) + .ok_or_else(|| anyhow!("readiness output did not include rows"))?; + let row = rows + .iter() + .find(|row| row.get("kind").and_then(Value::as_str) == Some(kind)) + .ok_or_else(|| anyhow!("missing readiness row {kind:?}"))?; + let actual = row + .get("satisfied") + .and_then(Value::as_bool) + .unwrap_or(!expected_satisfied); + if actual == expected_satisfied { + Ok(()) + } else { + Err(anyhow!( + "expected readiness row {kind:?} satisfied={expected_satisfied}, got {actual}" + )) + } +} + +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-work.rs b/epiphany-core/src/bin/epiphany-work.rs index 0f410cb7..0f30fe9a 100644 --- a/epiphany-core/src/bin/epiphany-work.rs +++ b/epiphany-core/src/bin/epiphany-work.rs @@ -1,30 +1,8 @@ +use anyhow::anyhow; use anyhow::Context; use anyhow::Result; -use anyhow::anyhow; use chrono::DateTime; use chrono::Utc; -use epiphany_core::EPIPHANY_CULTMESH_LOCAL_AREA_VERSE_ID; -use epiphany_core::EPIPHANY_CULTMESH_REPO_WORK_MAP_ENTRY_LATEST_KEY; -use epiphany_core::EPIPHANY_CULTMESH_REPO_WORK_MAP_ENTRY_SCHEMA_VERSION; -use epiphany_core::EPIPHANY_CULTMESH_REPO_WORK_OVERVIEW_SCHEMA_VERSION; -use epiphany_core::EPIPHANY_CULTMESH_REPO_WORK_PUBLIC_PROOF_SCHEMA_VERSION; -use epiphany_core::EpiphanyCultMeshRepoWorkMapEntry; -use epiphany_core::EpiphanyCultMeshRepoWorkOverviewEntry; -use epiphany_core::EpiphanyCultMeshRepoWorkPublicProofEntry; -use epiphany_core::HANDS_ACTION_INTENT_SCHEMA_VERSION; -use epiphany_core::HANDS_COMMAND_RECEIPT_TYPE; -use epiphany_core::HANDS_COMMIT_RECEIPT_TYPE; -use epiphany_core::HANDS_PATCH_RECEIPT_TYPE; -use epiphany_core::HANDS_PR_RECEIPT_TYPE; -use epiphany_core::HandsActionIntent; -use epiphany_core::MIND_GATEWAY_REVIEW_SCHEMA_VERSION; -use epiphany_core::MindGatewayDecision; -use epiphany_core::MindGatewayReview; -use epiphany_core::RuntimeSpineInitOptions; -use epiphany_core::SOUL_VERDICT_RECEIPT_SCHEMA_VERSION; -use epiphany_core::SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_SCHEMA_VERSION; -use epiphany_core::SoulVerdictReceipt; -use epiphany_core::SubstrateGateRepoAccessGrantReceipt; use epiphany_core::hands_action_review_for_intent; use epiphany_core::hands_command_receipt_for_review; use epiphany_core::hands_commit_receipt_for_review; @@ -56,10 +34,32 @@ use epiphany_core::runtime_latest_hands_receipt_chain_after; use epiphany_core::write_epiphany_cultmesh_repo_work_map_entry; use epiphany_core::write_epiphany_cultmesh_repo_work_overview; use epiphany_core::write_epiphany_cultmesh_repo_work_public_proof; +use epiphany_core::EpiphanyCultMeshRepoWorkMapEntry; +use epiphany_core::EpiphanyCultMeshRepoWorkOverviewEntry; +use epiphany_core::EpiphanyCultMeshRepoWorkPublicProofEntry; +use epiphany_core::HandsActionIntent; +use epiphany_core::MindGatewayDecision; +use epiphany_core::MindGatewayReview; +use epiphany_core::RuntimeSpineInitOptions; +use epiphany_core::SoulVerdictReceipt; +use epiphany_core::SubstrateGateRepoAccessGrantReceipt; +use epiphany_core::EPIPHANY_CULTMESH_LOCAL_AREA_VERSE_ID; +use epiphany_core::EPIPHANY_CULTMESH_REPO_WORK_MAP_ENTRY_LATEST_KEY; +use epiphany_core::EPIPHANY_CULTMESH_REPO_WORK_MAP_ENTRY_SCHEMA_VERSION; +use epiphany_core::EPIPHANY_CULTMESH_REPO_WORK_OVERVIEW_SCHEMA_VERSION; +use epiphany_core::EPIPHANY_CULTMESH_REPO_WORK_PUBLIC_PROOF_SCHEMA_VERSION; +use epiphany_core::HANDS_ACTION_INTENT_SCHEMA_VERSION; +use epiphany_core::HANDS_COMMAND_RECEIPT_TYPE; +use epiphany_core::HANDS_COMMIT_RECEIPT_TYPE; +use epiphany_core::HANDS_PATCH_RECEIPT_TYPE; +use epiphany_core::HANDS_PR_RECEIPT_TYPE; +use epiphany_core::MIND_GATEWAY_REVIEW_SCHEMA_VERSION; +use epiphany_core::SOUL_VERDICT_RECEIPT_SCHEMA_VERSION; +use epiphany_core::SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_SCHEMA_VERSION; use serde::Deserialize; use serde::Serialize; -use serde_json::Value; use serde_json::json; +use serde_json::Value; use sha2::Digest; use sha2::Sha256; use std::env; @@ -124,6 +124,9 @@ fn main() -> Result<()> { "publish" => run_publish(parse_publish_args(args)?), "sync" | "sync-main" => run_sync(parse_sync_args(args)?), "overview" | "proof-bundle" | "status" => run_overview(parse_overview_args(args)?), + "readiness" | "readiness-report" | "mvp-readiness" => { + run_readiness(parse_readiness_args(args)?) + } "deployment-config-audit" | "audit-deployment-config" | "idunn-deployment-audit" => { run_deployment_config_audit(parse_deployment_config_audit_args(args)?) } @@ -334,6 +337,18 @@ struct OverviewArgs { write_receipt: bool, } +#[derive(Clone, Debug)] +struct ReadinessArgs { + workspace: PathBuf, + item: Option, + accept_receipt: Option, + artifact_dir: Option, + public_proof: Option, + idunn_lifecycle_receipt: Option, + tool_directory_receipt: Option, + write_receipt: bool, +} + #[derive(Clone, Debug)] struct DeploymentConfigAuditArgs { workspace: PathBuf, @@ -1134,6 +1149,48 @@ fn parse_overview_args(args: impl Iterator) -> Result) -> Result { + let mut workspace = None; + let mut item = None; + let mut accept_receipt = None; + let mut artifact_dir = None; + let mut public_proof = None; + let mut idunn_lifecycle_receipt = None; + let mut tool_directory_receipt = None; + let mut write_receipt = true; + + let mut args = args.peekable(); + while let Some(arg) = args.next() { + match arg.as_str() { + "--workspace" => workspace = Some(take_path(&mut args, "--workspace")?), + "--item" => item = Some(take_string(&mut args, "--item")?), + "--accept-receipt" => accept_receipt = Some(take_path(&mut args, "--accept-receipt")?), + "--artifact-dir" => artifact_dir = Some(take_path(&mut args, "--artifact-dir")?), + "--public-proof" | "--public-proof-ref" => { + public_proof = Some(take_path(&mut args, "--public-proof")?); + } + "--idunn-lifecycle-receipt" | "--service-lifecycle-receipt" => { + idunn_lifecycle_receipt = Some(take_path(&mut args, "--idunn-lifecycle-receipt")?); + } + "--tool-directory-receipt" | "--tool-directory-ref" => { + tool_directory_receipt = Some(take_path(&mut args, "--tool-directory-receipt")?); + } + "--no-write" | "--read-only" => write_receipt = false, + other => return Err(anyhow!("unexpected readiness argument {other:?}")), + } + } + Ok(ReadinessArgs { + workspace: workspace.context("missing --workspace")?, + item, + accept_receipt, + artifact_dir, + public_proof, + idunn_lifecycle_receipt, + tool_directory_receipt, + write_receipt, + }) +} + fn parse_deployment_config_audit_args( args: impl Iterator, ) -> Result { @@ -1467,9 +1524,9 @@ fn run_accept(args: AcceptArgs) -> Result { .join("swarm-online") .join("repo-swarm-online-receipt.json") }); - let online_receipt = read_json(&online_receipt_path).with_context( - || "repo swarm online receipt is required; run epiphany-swarm online first", - )?; + let online_receipt = read_json(&online_receipt_path).with_context(|| { + "repo swarm online receipt is required; run epiphany-swarm online first" + })?; let local_verse_store = args.local_verse_store.unwrap_or_else(|| { path_from_json(&online_receipt, &["localVerseStore"]) .unwrap_or_else(|| workspace.join(".epiphany").join("local-verse.ccmp")) @@ -1630,9 +1687,9 @@ fn run_persona_intake(args: PersonaIntakeArgs) -> Result { .join("swarm-online") .join("repo-swarm-online-receipt.json") }); - let online_receipt = read_json(&online_receipt_path).with_context( - || "repo swarm online receipt is required; run epiphany-swarm online first", - )?; + let online_receipt = read_json(&online_receipt_path).with_context(|| { + "repo swarm online receipt is required; run epiphany-swarm online first" + })?; let local_verse_store = args.local_verse_store.clone().unwrap_or_else(|| { path_from_json(&online_receipt, &["localVerseStore"]) .unwrap_or_else(|| workspace.join(".epiphany").join("local-verse.ccmp")) @@ -8391,8 +8448,7 @@ fn closure_family_assertions( &mut assertions, "readiness-review-request-private-seal", content.contains("private_state_exposed = false"), - "Committed readiness review request preserves the private-state seal." - .to_string(), + "Committed readiness review request preserves the private-state seal.".to_string(), ); } "repo.doctrine_update_request" => { @@ -11923,6 +11979,282 @@ fn run_overview(args: OverviewArgs) -> Result { })) } +fn run_readiness(args: ReadinessArgs) -> Result { + let workspace = args + .workspace + .canonicalize() + .with_context(|| format!("failed to resolve {}", args.workspace.display()))?; + ensure_git_repo(&workspace)?; + let artifact_dir = args + .artifact_dir + .clone() + .unwrap_or_else(|| workspace.join(".epiphany").join("work")); + fs::create_dir_all(&artifact_dir) + .with_context(|| format!("failed to create {}", artifact_dir.display()))?; + + let overview = run_overview(OverviewArgs { + workspace: workspace.clone(), + item: args.item.clone(), + accept_receipt: args.accept_receipt.clone(), + artifact_dir: Some(artifact_dir.clone()), + write_receipt: args.write_receipt, + })?; + let item = overview + .get("item") + .and_then(Value::as_str) + .unwrap_or("work-item") + .to_string(); + let item_slug = sanitize(&item); + let accept_receipt_path = resolve_accept_receipt(&workspace, Some(&item), args.accept_receipt)?; + let plan_receipt_path = work_receipt_path(&workspace, "plan", &item); + let execute_receipt_path = work_receipt_path(&workspace, "execute", &item); + let close_receipt_path = work_receipt_path(&workspace, "close", &item); + let queue_run_receipt_path = artifact_dir.join("work-queue-run.json"); + let publish_receipt_path = work_receipt_path(&workspace, "publish", &item); + let sync_receipt_path = work_receipt_path(&workspace, "sync", &item); + let init_receipt_path = workspace + .join(".epiphany") + .join("repo-init") + .join("repo-swarm-init-receipt.json"); + let online_receipt_path = workspace + .join(".epiphany") + .join("swarm-online") + .join("repo-swarm-online-receipt.json"); + let public_proof_path = args.public_proof.unwrap_or_else(|| { + workspace + .join(".epiphany") + .join("public") + .join("proof-bundles") + .join(format!("repo-work-public-proof-{item_slug}.json")) + }); + let readiness_receipt_path = artifact_dir.join(format!("work-readiness-{item_slug}.json")); + + let close_receipt = read_json_if_exists(&close_receipt_path)?; + let close_status = close_receipt + .as_ref() + .and_then(|receipt| receipt.get("status").and_then(Value::as_str)); + let soul_passed = close_receipt + .as_ref() + .and_then(|receipt| string_from_json(receipt, &["soul", "verdict"])) + .as_deref() + == Some("passed"); + let modeling_map_admitted = close_receipt + .as_ref() + .and_then(|receipt| bool_from_json(receipt, &["authority", "durableStateAdmitted"])) + .unwrap_or(false) + || close_receipt + .as_ref() + .and_then(|receipt| string_from_json(receipt, &["mind", "stateCommitReceiptId"])) + .is_some(); + let publish_receipt = read_json_if_exists(&publish_receipt_path)?; + let sync_receipt = read_json_if_exists(&sync_receipt_path)?; + let upstream_synced = sync_receipt + .as_ref() + .and_then(sync_receipt_upstream_main_synced) + .unwrap_or(false); + + let mut rows = vec![ + readiness_path_row( + "repo-init", + "Self", + "epiphany.repo_swarm_init_receipt.v0", + &init_receipt_path, + init_receipt_path.exists(), + "Fresh repo was initialized as an Epiphany Body.", + )?, + readiness_path_row( + "swarm-online", + "Self/Gjallar", + "epiphany.repo_swarm_online_receipt.v0", + &online_receipt_path, + online_receipt_path.exists(), + "Repo-local swarm published compact CultMesh sight.", + )?, + readiness_path_row( + "persona-intake", + "Persona/Imagination", + "epiphany.repo_work_accept_receipt.v0", + &accept_receipt_path, + accept_receipt_path.exists(), + "Persona or Bifrost pressure was accepted without Hands authority.", + )?, + readiness_path_row( + "imagination-plan", + "Imagination", + "epiphany.repo_work_action_plan_receipt.v0", + &plan_receipt_path, + plan_receipt_path.exists(), + "Imagination produced action-item/plan cargo.", + )?, + readiness_path_row( + "self-queue-run", + "Self", + "epiphany.repo_work_queue_run_receipt.v0", + &queue_run_receipt_path, + queue_run_receipt_path.exists(), + "Self queue-run pulse selected or advanced repo work.", + )?, + readiness_path_row( + "hands-branch-work", + "Hands", + "epiphany.repo_work_execute_receipt.v0", + &execute_receipt_path, + execute_receipt_path.exists(), + "Hands executed branch-local work with receipts.", + )?, + readiness_path_row( + "soul-closure", + "Soul", + "epiphany.repo_work_closure_receipt.v0", + &close_receipt_path, + close_status == Some("closed") && soul_passed, + "Soul passed closure for the Hands consequence.", + )?, + readiness_path_row( + "modeling-mind-admission", + "Modeling/Mind", + "epiphany.mind.state_commit_receipt", + &close_receipt_path, + modeling_map_admitted, + "Modeling map update was admitted through Mind.", + )?, + readiness_path_row( + "public-proof", + "Bifrost/Gjallar", + "epiphany.repo_work_public_proof_bundle.v0", + &public_proof_path, + public_proof_path.exists(), + "Redacted public proof bundle exists.", + )?, + readiness_path_row( + "bifrost-publication", + "Bifrost", + "gamecult.bifrost.publication_receipt.v0", + &publish_receipt_path, + publish_receipt.is_some(), + "Bifrost/GitHub publication receipt exists.", + )?, + readiness_path_row( + "upstream-main-sync", + "Bifrost/GitHub", + "epiphany.repo_work_sync.v0", + &sync_receipt_path, + upstream_synced, + "Published commit is contained by upstream main.", + )?, + ]; + if let Some(path) = args.idunn_lifecycle_receipt.as_ref() { + rows.push(readiness_path_row( + "idunn-lifecycle", + "Idunn", + "epiphany.cultmesh.daemon_service_lifecycle_receipt.v0", + path, + path.exists(), + "Idunn lifecycle receipt was supplied for the repo-work pulse.", + )?); + } else { + rows.push(readiness_missing_row( + "idunn-lifecycle", + "Idunn", + "epiphany.cultmesh.daemon_service_lifecycle_receipt.v0", + "Supply --idunn-lifecycle-receipt after Idunn service audit/readiness proof.", + )); + } + if let Some(path) = args.tool_directory_receipt.as_ref() { + rows.push(readiness_path_row( + "tool-directory", + "Odin/Gjallar", + "epiphany.cultmesh.daemon_tool_directory_readback.v0", + path, + path.exists(), + "Global daemon tool directory readback was supplied.", + )?); + } else { + rows.push(readiness_missing_row( + "tool-directory", + "Odin/Gjallar", + "epiphany.cultmesh.daemon_tool_directory_readback.v0", + "Supply --tool-directory-receipt from the compact daemon tool directory sight rite.", + )); + } + rows.push(json!({ + "kind": "private-state-redaction", + "owner": "Soul/Bifrost", + "requiredSchema": "epiphany.private_state_redaction_report.v0", + "evidenceRef": overview["proofBundle"].get("bundleId").cloned().unwrap_or(Value::String("missing".to_string())), + "satisfied": overview + .get("privateStateExposed") + .and_then(Value::as_bool) + .map(|exposed| !exposed) + .unwrap_or(false), + "status": if overview.get("privateStateExposed").and_then(Value::as_bool) == Some(false) { "satisfied" } else { "missing" }, + "note": "Readiness sight observed privateStateExposed=false on overview/proof-bundle projection." + })); + + let missing_rows = rows + .iter() + .filter(|row| row.get("satisfied").and_then(Value::as_bool) != Some(true)) + .count(); + let verdict = if missing_rows == 0 { + "ready" + } else { + "not-ready" + }; + let next_safe_move = if missing_rows == 0 { + "Route this sight receipt to Maintainer/Soul/Mind/Bifrost review; readiness approval is still external to this command." + } else { + "Complete the missing readiness rows, then rerun epiphany-work readiness before asking for review." + }; + let now = Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true); + let receipt = json!({ + "schemaVersion": "epiphany.repo_work_readiness_report.v0", + "createdAt": now, + "workspace": workspace, + "item": item, + "status": verdict, + "verdict": verdict, + "missingRequiredCount": missing_rows, + "overviewReceiptPath": overview["receiptPath"], + "proofBundleId": overview["proofBundle"]["bundleId"], + "rows": rows, + "allowedVerdicts": ["ready", "ready-with-caveats", "not-ready", "needs-human-review"], + "authority": { + "owner": "Soul/Mind/Bifrost/Maintainer", + "sightOnly": true, + "readinessApprovalAuthorized": false, + "durableStateCommitAuthorized": false, + "publicationAuthorized": false, + "bifrostPublicationAuthorized": false, + "githubPrAuthorized": false, + "mergeAuthorized": false, + "upstreamSyncAuthorized": false, + "deploymentAuthority": false, + "serviceLifecycleAuthority": false, + "handsActionAuthorized": false, + "crossBodyMutationAuthorized": false, + "privateVerseRummaging": false, + "privateStateExposed": false + }, + "privateStateExposed": false, + "nextSafeMove": next_safe_move + }); + if args.write_receipt { + write_json(&readiness_receipt_path, &receipt)?; + } + Ok(json!({ + "schemaVersion": "epiphany.repo_work_readiness.v0", + "status": verdict, + "workspace": receipt["workspace"], + "item": receipt["item"], + "receiptPath": if args.write_receipt { Value::String(readiness_receipt_path.display().to_string()) } else { Value::Null }, + "missingRequiredCount": missing_rows, + "rows": receipt["rows"], + "authority": receipt["authority"], + "privateStateExposed": false, + "nextSafeMove": receipt["nextSafeMove"] + })) +} + fn run_deployment_config_audit(args: DeploymentConfigAuditArgs) -> Result { let workspace = args .workspace @@ -13601,6 +13933,54 @@ fn existing_path_value(path: &Path) -> Value { } } +fn readiness_path_row( + kind: &str, + owner: &str, + required_schema: &str, + path: &Path, + satisfied: bool, + note: &str, +) -> Result { + let document = read_json_if_exists(path)?; + let schema_version = document + .as_ref() + .and_then(|value| string_from_json(value, &["schemaVersion"])) + .unwrap_or_else(|| "missing".to_string()); + let document_status = document + .as_ref() + .and_then(|value| string_from_json(value, &["status"])) + .unwrap_or_else(|| "missing".to_string()); + Ok(json!({ + "kind": kind, + "owner": owner, + "requiredSchema": required_schema, + "evidenceRef": existing_path_value(path), + "artifactStatus": if path.exists() { "present" } else { "missing" }, + "schemaVersion": schema_version, + "documentStatus": document_status, + "satisfied": satisfied, + "status": if satisfied { "satisfied" } else { "missing" }, + "note": note, + "privateStateExposed": false + })) +} + +fn readiness_missing_row(kind: &str, owner: &str, required_schema: &str, note: &str) -> Value { + json!({ + "kind": kind, + "owner": owner, + "requiredSchema": required_schema, + "evidenceRef": Value::Null, + "artifactStatus": "missing", + "schemaVersion": "missing", + "documentStatus": "missing", + "satisfied": false, + "status": "missing", + "note": note, + "privateStateExposed": false + }) +} + fn read_json_if_exists(path: &Path) -> Result> { if path.exists() { read_json(path).map(Some) @@ -14402,7 +14782,11 @@ fn string_array_field(value: &Value, field: &str) -> Vec { } fn default_if_empty(values: Vec, defaults: Vec) -> Vec { - if values.is_empty() { defaults } else { values } + if values.is_empty() { + defaults + } else { + values + } } fn adopted_action_item_from_plan(plan: &Value) -> Value { @@ -14633,7 +15017,7 @@ fn sanitize(value: &str) -> String { fn print_usage() { eprintln!( - "usage: epiphany-work ...\n\ + "usage: epiphany-work ...\n\ persona-intake --workspace --item --message [--topic ] [--store ] [--runtime-id ]\n\ accept --workspace --from --item [--summary ] [--topic ] [--store ] [--runtime-id ] [--online-receipt ] [--public-discussion-ref ] [--candidate-action-ref ]\n\ derive-plan --workspace [--item ] [--accept-receipt ] [--action-family append-worklog|planning-note|checklist-note|section-note|repo-status-section|task-card|repo-manifest|repo-tool-capabilities|repo-tool-request|repo-eve-surface|repo-collaboration-policy|repo-collaboration-topic|repo-consensus-brief|repo-planning-brief|repo-interpreter-brief|repo-objective-draft|repo-adoption-request|repo-scheduling-request|repo-work-order|repo-verification-request|repo-publication-request|repo-sync-request|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-dependency-policy-request|repo-deployment-config|repo-deployment-request] [--target-path ] [--model-ref ] [--model-authored] [--action-summary ] [--verification-ask ] [--stop-condition ] [--escalation-reason ] [--assumption ] [--constraint ] [--non-goal ] [--open-question ] [--decision-point ] [--evidence-need ]\n\ @@ -14645,6 +15029,7 @@ fn print_usage() { publish --workspace [--item ] --change-summary --justification --verification-receipt --review-receipt --ledger-entry-id --pull-request-url --pull-request-title \n\ sync --workspace [--item ] [--publish-receipt ] [--upstream-ref origin/main] --merge-receipt \n\ overview --workspace [--item ] [--accept-receipt ] [--no-write]\n\ + readiness --workspace [--item ] [--accept-receipt ] [--public-proof ] [--idunn-lifecycle-receipt ] [--tool-directory-receipt ] [--no-write]\n\ deployment-config-audit --workspace [--artifact-dir ] [--no-write]\n\ deployment-execution-runbook --workspace [--artifact-dir ] [--remote origin] [--no-write]\n\ deployment-aftercare-audit --workspace [--artifact-dir ] [--local-verse-store ] [--runtime-id ] [--runbook-receipt ] [--idunn-deployment-receipt-ref |--idunn-deployment-receipt ] [--aftercare-audit-receipt-ref |--aftercare-audit-receipt ] [--no-write]\n\ diff --git a/notes/epiphany-repo-swarm-mvp-contract.md b/notes/epiphany-repo-swarm-mvp-contract.md index 8b3d0193..f2af30ac 100644 --- a/notes/epiphany-repo-swarm-mvp-contract.md +++ b/notes/epiphany-repo-swarm-mvp-contract.md @@ -137,6 +137,7 @@ 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 epiphany-work publish --workspace --closure-receipt @@ -1604,6 +1605,21 @@ and `15-swarm-overview-public-proof.json` proved latest 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, redacted +public proof, Bifrost publication, upstream-main sync, Idunn lifecycle, daemon +tool directory, and private-state redaction rows, then writes +`.epiphany/work/work-readiness-.json` as +`epiphany.repo_work_readiness_report.v0`. 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-092053` proved a partial local +work path reports `not-ready` with four missing rows: Bifrost publication, +upstream-main sync, Idunn lifecycle, and tool directory. + Bifrost public-proof publication transport is now the closure after local proof sight. `epiphany-verse-query bifrost-public-proof --public-proof-id --ledger-entry-id --review-receipt --credit-receipt ` selects an @@ -1860,7 +1876,7 @@ temptation wearing clean robes. | 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 | `overview` emits compact proof rows, an `epiphany.repo_work_intake_consensus_readback.v0` from the accept/plan chain, and typed `epiphany.cultmesh.repo_work_overview.v0` event documents plus a latest key; the CultMesh TUI rows now include a compact `CONSENSUS` readback showing Persona/public feedback ids, Imagination consensus route, public/candidate ref counts, safe family, and authority denials without private payloads; `close` mirrors Mind-admitted map facts as `epiphany.cultmesh.repo_work_map_entry.v0`; Gjallar enumerates overview history, map rows, semantic rows, closure rows, family lens rows, path lens rows, branch lens rows, stage lens rows, gate lens rows, and public-proof rows without private-state exposure; Persona's Eve surface and Eve connection readbacks expose peer-readable gate/blocker/next-action rows; `queue-run` consumes the same overview queue for branch-local scheduler pulses. | Deepen Bifrost-first external work accounting and semantic map readback without moving action authority out of Hands/Self/Bifrost. | +| Repo work sight | `overview` emits compact proof rows, an `epiphany.repo_work_intake_consensus_readback.v0` from the accept/plan chain, and typed `epiphany.cultmesh.repo_work_overview.v0` event documents plus a latest key; `readiness` now emits `epiphany.repo_work_readiness_report.v0` over the MVP proof rows and names missing Bifrost/upstream/Idunn/tool-directory gates without approving readiness; the CultMesh TUI rows include compact `CONSENSUS` readback showing Persona/public feedback ids, Imagination consensus route, public/candidate ref counts, safe family, and authority denials without private payloads; `close` mirrors Mind-admitted map facts as `epiphany.cultmesh.repo_work_map_entry.v0`; Gjallar enumerates overview history, map rows, semantic rows, closure rows, family lens rows, path lens rows, branch lens rows, stage lens rows, gate lens rows, and public-proof rows without private-state exposure; Persona's Eve surface and Eve connection readbacks expose peer-readable gate/blocker/next-action rows; `queue-run` consumes the same overview queue for branch-local scheduler pulses. | Deepen Bifrost-first external work accounting and semantic map readback without moving action authority out of Hands/Self/Bifrost. | | Publication | `publish` routes Bifrost/GitHub receipts from closure or explicit Soul/Mind refs. | Keep publication Bifrost-owned; do not let scheduler publish. | | Upstream main sync | `sync` proves the published commit is contained by upstream main after explicit merge/sync authority. | Treat upstream-main sync as a required final proof for published work. | | 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. | diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 11426ac3..0f4d64d8 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -159,6 +159,7 @@ remembers doctrine; the branch remembers the blade. - 2026-06-20: `derive-plan` action-item receipts now carry richer model-authored planning facets without widening command authority, and `adopt --from-plan` now preserves the chosen Imagination action item in the Mind adoption receipt. New CLI fields `--assumption`, `--constraint`, `--non-goal`, `--open-question`, `--decision-point`, and `--evidence-need` are recorded under `planningFacets` in `epiphany.repo_work_imagination_action_items_receipt.v0`, mirrored into the plan derivation's `actionItemReceipt`, then copied into `epiphany.repo_work_adoption_receipt.v0` as `adoptedActionItem`; deterministic defaults are emitted when a model does not supply them. Focused smoke `.epiphany-smoke\repo-planning-facets-20260620-164958` ran init -> online -> accept -> model-authored `derive-plan --action-family planning-note` with all facet fields -> tick run/adopt/execute/close; the action-item receipt preserved assumptions, constraints, non-goals, open questions, decision points, and evidence needs while `handsAuthorityGranted=false`, `durableStateAdmitted=false`, `handsCommandAuthority=false`, `durableStateAuthority=false`, and `privateStateExposed=false`; the adoption receipt preserved `adoptedActionItem.safeActionFamily=repo.markdown_planning_note`, planning facets, `adoptedActionItem.handsCommandAuthority=false`, `adoptedActionItem.durableStateAuthority=false`, `adoptedActionItem.publicationAuthorized=false`, `authority.handsAuthorityGranted=true`, `authority.durableStateAdmitted=false`, and `privateStateExposed=false`; Soul closure passed with family assertions and path scope matched. Current remaining repo-swarm MVP organs are deeper interpreter/adoption semantics, more useful repo-owned configuration/action families, and Idunn-owned queue-run service lifecycle under explicit operator authority. - 2026-06-21: `repo-planning-brief` is live as richer family-friendly Imagination decomposition cargo, and Soul closure now emits a typed safe-family planning readback for its full matrix. `epiphany-work derive-plan --action-family repo-planning-brief` writes `.epiphany/planning-briefs/.toml` with safe family `repo.planning_brief`, status `draft`, twenty-one candidate next safe families grouped as preparation, adoption/queue, execution/review, publication/accounting, and policy/deployment, requested-path / verification-ask / evidence-need / owner / authority-denial / closure-proof requirements, and Mind/Self/Substrate/Hands/Soul/Bifrost/Idunn gate ordering while denying objective adoption, scheduling, substrate access, Hands action, shell, commit, publication, deployment execution, service lifecycle, cross-body mutation, private transcripts, raw-result payloads, and private-state exposure. Closure `familyAssertions.safeFamilyPlanning` now carries `epiphany.repo_work_safe_family_planning_readback.v0` with candidate-family rows/count, grouped matrix rows, matrix control flags, closure proof flags, planning requirement flags, gate flags, authority-denial flags, and the private-state seal. Focused smoke `.epiphany-smoke\repo-planning-brief-family-20260621-090158` ran init -> online -> accept -> repo-planning-brief derive-plan -> tick run/adopt/execute/close; Hands committed only `.epiphany/planning-briefs/repo-planning-brief-family.toml`, Soul passed with `pathScopeMatched=true`, `familyAssertions.status=passed`, `candidateNextSafeFamilyCount=21`, `allMatrixGroupsComplete=true`, `matrixControlsPresent=true`, `allClosureProofsPresent=true`, `allRequiredGatesPresent=true`, `authorityDenied=true`, and `privateStateExposed=false`. Current remaining repo-swarm MVP organs are deeper interpreter semantics, richer closure/map readback, another useful repo-owned safe family if needed, or Idunn-owned queue-run service lifecycle under explicit operator authority. - 2026-06-21: `repo-readiness-review-request` is live as the family-friendly MVP readiness review gate. `epiphany-work derive-plan --action-family repo-readiness-review-request` writes `.epiphany/readiness-reviews/.toml` with safe family `repo.readiness_review_request`, status `awaiting-mvp-readiness-review`, requested owner `Maintainer/Soul/Mind/Bifrost`, proof-bundle/readiness packet requirements for 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, while denying readiness approval, durable state commit, publication, Bifrost publication, PR, merge, upstream sync, deployment, service lifecycle, Hands action, cross-body mutation, private Verse rummaging, and private-state exposure. Focused smoke `.epiphany-smoke\repo-readiness-review-request-family-20260621-090003` ran init -> online -> accept -> repo-readiness-review-request derive-plan -> tick run/adopt/execute/close; Hands committed only `.epiphany/readiness-reviews/repo-readiness-review-request-family.toml`, Soul passed with `pathScopeMatched=true`, `familyAssertions.status=passed`, `awaitingMvpReadinessReview=true`, `idunnLifecycleRequired=true`, `toolDirectoryRequired=true`, and `privateStateExposed=false`. The planning-brief matrix now counts twenty-one families and includes this readiness review in publication/accounting; smoke `.epiphany-smoke\repo-planning-brief-family-20260621-090158` proved `candidateNextSafeFamilyCount=21`. +- 2026-06-21: `epiphany-work readiness` is live as a non-mutating repo-work MVP readiness sight rite. It reuses overview/proof-bundle state, inspects repo init, online swarm, Persona intake, Imagination plan, Self queue-run, Hands execute, Soul closure, Modeling/Mind admission, redacted public proof, Bifrost publication, upstream-main sync, Idunn lifecycle, daemon tool directory, and private-state redaction rows, then writes `.epiphany/work/work-readiness-.json` with schema `epiphany.repo_work_readiness_report.v0`. It can return `ready` only when all rows are satisfied, but it does not approve readiness or grant durable state, publication, PR, merge, sync, deployment, service lifecycle, Hands action, cross-body, or private Verse authority. Focused smoke `.epiphany-smoke\repo-work-readiness-20260621-092053` ran fresh repo init -> online -> accept -> checklist plan/tick close -> queue-run dry-run -> public-proof export -> readiness; it reported `not-ready`, `missingRequiredCount=4` for Bifrost publication, upstream-main sync, Idunn lifecycle, and tool directory, while local work/proof rows were satisfied, `sightOnly=true`, and `privateStateExposed=false`. - 2026-06-21: `repo-interpreter-brief` is live as Mind-owned interpretation cargo between Imagination consensus and downstream action-family adoption. `epiphany-work derive-plan --action-family repo-interpreter-brief` writes `.epiphany/interpreter-briefs/.toml` with safe family `repo.interpreter_brief`, status `draft`, owner `Mind`, source `Imagination`, consensus/safe-family/path/verification/evidence requirements, semantic checks for intent/scope/paths/verification/evidence/rollback/non-goals/questions/consensus, candidate next safe families, and Mind/Soul/Bifrost/Hands/Substrate/Idunn gates while denying direct state commit, objective adoption, scheduling, substrate access, Hands action, shell, commit, publication, deployment execution, service lifecycle, cross-body mutation, private transcripts, raw-result payloads, and private-state exposure. Focused smoke `.epiphany-smoke\repo-interpreter-brief-family-20260621-010858` ran init -> online -> accept -> repo-interpreter-brief derive-plan -> tick run/adopt/execute/close; Hands committed only `.epiphany/interpreter-briefs/repo-interpreter-brief-family.toml`, Soul passed with `pathScopeMatched=true`, `familyAssertions.status=passed`, `mindOwnsInterpretation=true`, `candidateActionsNonAuthoritative=true`, `directStateCommitAuthorized=false`, `handsActionAuthorized=false`, and `privateStateExposed=false`. - 2026-06-20: Gjallar repo-work map sight now includes semantic stage lenses. `epiphany-verse-query gjallar`, swarm overview, and swarm triage expose `repoWorkMapStageLensRows` plus `REPO-WORK-MAP-STAGE` rows derived from Mind-admitted local Verse map entries, grouping safe families into stages and owning organs such as `imagination-planning` / `Imagination`, `hands-execution-request` / `Hands`, `soul-verification-request` / `Soul`, `bifrost-publication-accounting` / `Bifrost`, and `idunn-deployment` / `Idunn`. Focused smoke `.epiphany-smoke\repo-close-mind-adoption-guard-20260620-232414` proves the stage lens for accepted `repo.markdown_planning_note` work reports `stage=imagination-planning`, `owner=Imagination`, latest item/commit/Mind commit receipt, Bifrost gate, and `privateStateExposed=false`. This is sight only; Gjallar still does not schedule, publish, merge, deploy, keep daemons alive, or admit Mind state. - 2026-06-20: Gjallar repo-work map sight now includes next-publication-gate lenses. `epiphany-verse-query gjallar`, swarm overview, and swarm triage expose `repoWorkMapGateLensRows` plus `REPO-WORK-MAP-GATE` rows derived from the same Mind-admitted local Verse map entries, grouping accepted consequences by publication gate and naming the owning gate organ. Focused smoke `.epiphany-smoke\repo-close-mind-adoption-guard-20260620-233353` proves accepted `repo.markdown_planning_note` work reports `gate=Bifrost`, `owner=Bifrost`, latest item/commit/Mind commit receipt, `sightOnly=true`, `publicationAuthorized=false`, and `privateStateExposed=false`. This is sight only; Bifrost still owns publication/credit/upstream review and Gjallar does not schedule, publish, merge, deploy, keep daemons alive, or admit Mind state. diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index d97257f6..655fe9c2 100644 Binary files a/state/ledgers.msgpack and b/state/ledgers.msgpack differ diff --git a/state/map.yaml b/state/map.yaml index e2aedb7c..8e30f2c3 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -91,6 +91,7 @@ active_subgoals: - Use `notes/epiphany-repo-swarm-mvp-contract.md` as the repo-swarm autonomy authority map. Autonomous branch-local work inside an Epiphany-owned repo Body is in scope and desired; the safety boundary is typed authority, branch containment, interruption, receipts, private-state sealing, and Bifrost-gated publication/merge/escalation, not asking a human before every local edit. Current safe-family cargo includes repo manifest, tool capability manifest, daemon-hosted tool request cargo, Eve/CultUI surface contract, repo collaboration policy, collaboration topics, consensus/objective/adoption/scheduling/work-order/verification/publication/sync/review/PR/credit/artifact/metrics/doctrine-update/secret-policy/deployment requests, repo deployment config, action-item receipts, Mind adoption interpretation/decision receipts, explicit Mind safe-family refusal proof `.epiphany-smoke\repo-mind-safe-family-guard-20260620-194425`, explicit Mind requested-path/plan changed-path refusal proof `.epiphany-smoke\repo-mind-path-guard-20260620-194239`, overview/public-proof/Gjallar/Eve readbacks, typed swarm-run stop classification, Idunn non-mutating queue-run lifecycle receipts, and fresh-repo upstream-main proof. Latest deployment proof `.epiphany-smoke\repo-deployment-config-family-20260620-221205` shows `repo.deployment_config` closes through Soul, `deployment-config-audit` reports `ready-for-idunn-review`, `deployment-execution-runbook` reports `ready-for-operator-git-push`, and `deployment-aftercare-audit` ingests local Verse/CultMesh Idunn deployment/aftercare receipt refs with source `cultmesh`, status `complete`, `deploymentComplete=true`, `daemonOwnsExecution=true`, no repo-agent execution/deployment/SSH/git-push/service/Hands/publication/merge/cross-body authority, and `privateStateExposed=false`; explicit Idunn receipt files remain only compatibility reliquaries. Latest deployment request proof `.epiphany-smoke\repo-deployment-request-family-20260620-195605` shows `repo.deployment_request` closes through Soul while requesting reviewed git-push-observed-by-Idunn deployment with script/host/secret/rollback/aftercare requirements and no deployment/SSH/git-push/service authority. Latest security cargo proof `.epiphany-smoke\repo-secret-policy-request-family-20260620-193046` shows `repo.secret_policy_request` closes through Soul while requesting reviewed secret/write/export/deployment policy without exposing secrets or granting write/deployment/publication authority. Governance proof `.epiphany-smoke\repo-doctrine-update-request-family-20260620-192140` shows `repo.doctrine_update_request` closes through Soul while requesting reviewed `AGENTS.md` doctrine work without mutating doctrine or granting Hands/service/publication authority. Swarm-run stop proofs now include `.epiphany-smoke\fresh-repo-mvp-20260620-180024` for `iteration-limit` / Self / `self.scheduler-iteration-limit` with upstream-main sync and public-proof export, plus `.epiphany-smoke\swarm-stop-classification-20260620-184717` for `queue-empty` / Gjallar / `repo.work.overview`, `dry-run-preview` / Self / `ready-to-run`, `authority-gated` / Bifrost / `awaiting-publication`, and `authority-gated` / Bifrost/GitHub / `awaiting-upstream-sync`; all stop packets are non-mutating, non-elevated, and `privateStateExposed=false`. Native proof `.epiphany-smoke\repo-work-service-lifecycle-20260620-185853` now proves Idunn-owned non-mutating repo-work queue-run service plan/runbook receipts for exact service id `epiphany-repo-work-queue-runner`, receipt-directory readback, runbook SHA-256 `f2ec67c4f8243a3ebc2f9e915b6b9f5ae2ac2d99d2cdb86905b54667adc456f8`, `hostedBody=repo-work`, no service-manager mutation, no launch, no elevation, and `privateStateExposed=false`. - Repo dependency/supply-chain policy request cargo is live. `repo-dependency-policy-request` lowers to safe family `repo.dependency_policy_request` and writes `.epiphany/dependency-policy-requests/.toml` with manifest inventory, lockfile policy, package-manager command review, network fetch policy, vulnerability/license/vendored-code/update-cadence requirements, Eyes/Soul/Mind/maintainer/Bifrost/dependency-audit receipt refs, and denials for dependency update, package install, lockfile mutation, network fetch, CI mutation, Hands, publication, merge, deployment, service lifecycle, cross-body mutation, private Verse rummaging, and private-state exposure. Proof `.epiphany-smoke\repo-dependency-policy-request-family-20260621-083433` closes through Soul with `familyAssertionsStatus=passed`, `pathScopeMatched=true`, `awaitingDependencyPolicyReview=true`, all direct dependency/package/network/CI authorities false, and `privateStateExposed=false`. - Repo planning brief cargo now carries the family-friendly safe-family matrix. `repo-planning-brief` still lowers to safe family `repo.planning_brief` and writes `.epiphany/planning-briefs/.toml`, but it now names twenty-one candidate next safe families across preparation, adoption/queue, execution/review, publication/accounting, and policy/deployment groups, including `repo.readiness_review_request` for redacted repo-swarm MVP proof-bundle review before readiness claims. Each candidate must name owner, requested paths, verification asks, evidence needs, authority denials, and closure proofs. Closure `safeFamilyPlanning` readback now reports grouped matrix completeness, matrix controls, and closure proof presence while granting no scheduling, substrate, Hands, shell, commit, publication, deployment, service lifecycle, cross-body, private-transcript, raw-result, or private-state authority. Proof `.epiphany-smoke\repo-planning-brief-family-20260621-090158` closes through Soul with `candidateNextSafeFamilyCount=21`, `allMatrixGroupsComplete=true`, `matrixControlsPresent=true`, `allClosureProofsPresent=true`, `authorityDenied=true`, and `privateStateExposed=false`. + - Repo-work readiness sight is live. `epiphany-work readiness` reuses the existing overview/proof-bundle artery, inspects repo init, swarm online, Persona intake, Imagination plan, Self queue-run, Hands execute, Soul closure, Modeling/Mind admission, public proof, Bifrost publication, upstream-main sync, Idunn lifecycle, daemon tool directory, and private-state redaction rows, then writes `.epiphany/work/work-readiness-.json` as `epiphany.repo_work_readiness_report.v0`. It is sight only: no readiness approval, durable state commit, publication, PR, merge, sync, deployment, service lifecycle, Hands action, cross-body mutation, private Verse rummaging, or private-state exposure authority. Proof `.epiphany-smoke\repo-work-readiness-20260621-092053` drove a fresh repo through init/online/intake/checklist execution/closure/queue-run/public-proof export; readiness reported `not-ready`, `missingRequiredCount=4` for Bifrost publication, upstream-main sync, Idunn lifecycle, and tool directory, while local work/proof rows were satisfied and authority denials held. - Repo-swarm Mind interpretation cargo is live. `repo-interpreter-brief` lowers to safe family `repo.interpreter_brief` and writes `.epiphany/interpreter-briefs/.toml` as draft Mind-owned interpretation from Imagination consensus, with semantic checks, candidate safe families, required Mind/Soul/Bifrost/Hands/Substrate/Idunn gates, and explicit denials for direct state commit, objective adoption, scheduling, substrate access, Hands action, shell, commit, publication, deployment execution, service lifecycle, cross-body mutation, private transcripts/raw results, and private-state exposure. Proof `.epiphany-smoke\repo-interpreter-brief-family-20260621-010858` closes through Soul with `familyAssertions.status=passed`, `mindOwnsInterpretation=true`, `candidateActionsNonAuthoritative=true`, `directStateCommitAuthorized=false`, `handsActionAuthorized=false`, and `privateStateExposed=false`. - Idunn-hosted repo-work queue-run lifecycle now has plan/runbook/launch/audit closure under explicit operator authority. `epiphany-daemon-supervisor repo-work-service-audit` reads all typed service lifecycle receipts for `epiphany-repo-work-queue-runner`, requires planned install-plan, written local runbook artifact, completed launch with exit code `0`, and no private-state exposure, then writes a `repo-work-service-audit` receipt with `status=complete` or `incomplete`. Native proof `.epiphany-smoke\repo-work-service-lifecycle-20260621-004202` proves plan/runbook/launch/audit with `auditStatus=complete`, `auditRunbookArtifactStatus=present`, `auditLaunchExitCode=0`, `mutatesServiceManager=false`, `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` prove operator plan/runbook/launch/audit ergonomics, with audit reporting no missing or failed checks. Basic queue-run service lifecycle readiness is sealed; remaining Idunn work is optional elevated install/start execution under explicit operator authority, not Self scheduling authority. - Gjallar/Odin sight now exposes Idunn deployment receipt refs without taking Idunn's execution throne. `EpiphanyLocalVerseContext`, `receipt-directory`, `swarm-overview`, `swarm-triage`, and `gjallar` surface latest `gamecult.idunn.deployment_receipt.v0` and `gamecult.idunn.deployment_aftercare_audit.v0` as compact `idunnDeploymentReceiptRows` / `IDUNN-DEPLOYMENT` rows with owner `Idunn`, watched/checked refs, source/result refs, aftercare ids, follow-up readback command, and `privateStateExposed=false`. Smoke `.epiphany-smoke\repo-deployment-config-family-20260620-223203` proves the aftercare audit still ingests CultMesh refs and Gjallar reports `gjallarIdunnDeploymentReceiptCount=2`, latest deployment receipt `fixture-idunn-deployment`, latest aftercare receipt `fixture-idunn-aftercare`, and no private-state exposure. This is citeable discovery only; deployment still happens by explicit operator `git push` observed by Idunn.