Bind repo work adoption to Mind authority
This commit is contained in:
parent
fcbab316e6
commit
2f22dbbca7
17 changed files with 1174 additions and 4232 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,454 +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<Self> {
|
||||
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<Value> {
|
||||
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-doctrine-update-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 Doctrine Update Request Family Smoke\n\nThis repository proves branch-local doctrine update request cargo without direct doctrine mutation authority.\n",
|
||||
)
|
||||
.with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?;
|
||||
fs::write(
|
||||
repo.join("AGENTS.md"),
|
||||
"# Repo Agent Doctrine\n\nExisting doctrine stays untouched by the request artifact.\n",
|
||||
)
|
||||
.with_context(|| format!("failed to seed {}", repo.join("AGENTS.md").display()))?;
|
||||
git(["add", "README.md", "AGENTS.md"], &repo)?;
|
||||
git(
|
||||
[
|
||||
"commit",
|
||||
"-m",
|
||||
"Seed repo doctrine update request smoke body",
|
||||
],
|
||||
&repo,
|
||||
)?;
|
||||
git(
|
||||
[
|
||||
"switch",
|
||||
"-c",
|
||||
"epiphany/repo-doctrine-update-request-family",
|
||||
],
|
||||
&repo,
|
||||
)?;
|
||||
|
||||
let item = "repo-doctrine-update-request-family";
|
||||
let target_path = ".epiphany/doctrine-update-requests/repo-doctrine-update-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-doctrine-update-request-family-smoke",
|
||||
"--topic",
|
||||
"repo-doctrine-update-request-family",
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
cargo_json(
|
||||
&manifest,
|
||||
"epiphany-swarm",
|
||||
&[
|
||||
"online",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--epiphany-root",
|
||||
path_str(&root)?,
|
||||
"--runtime-id",
|
||||
"repo-doctrine-update-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 AGENTS.md doctrine update while preserving Mind, Soul, and maintainer gates.",
|
||||
"--candidate-action-ref",
|
||||
"candidate-action://repo-doctrine-update-request-family/agents-update",
|
||||
"--public-discussion-ref",
|
||||
"epiphany-global/persona-collaboration/repo-doctrine-update-request-family",
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
let plan = cargo_json(
|
||||
&manifest,
|
||||
"epiphany-work",
|
||||
&[
|
||||
"derive-plan",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--item",
|
||||
item,
|
||||
"--action-family",
|
||||
"repo-doctrine-update-request",
|
||||
"--model-ref",
|
||||
"repo-doctrine-update-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 agents_text = fs::read_to_string(repo.join("AGENTS.md"))
|
||||
.with_context(|| format!("failed to read {}", repo.join("AGENTS.md").display()))?;
|
||||
|
||||
require_eq(
|
||||
&plan,
|
||||
&["derivation", "safeActionFamily"],
|
||||
"repo.doctrine_update_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_doctrine_update_request.v0\"",
|
||||
)?;
|
||||
require_text(
|
||||
&request_text,
|
||||
"safe_action_family = \"repo.doctrine_update_request\"",
|
||||
)?;
|
||||
require_text(&request_text, "[request]")?;
|
||||
require_text(&request_text, "status = \"awaiting-doctrine-review\"")?;
|
||||
require_text(&request_text, "routing_owner = \"Self\"")?;
|
||||
require_text(
|
||||
&request_text,
|
||||
"required_reviewers = [\"Maintainer\", \"Mind\", \"Soul\"]",
|
||||
)?;
|
||||
require_text(&request_text, "doctrine_admission_owner = \"Mind\"")?;
|
||||
require_text(&request_text, "mutation_owner = \"Hands\"")?;
|
||||
require_text(
|
||||
&request_text,
|
||||
"requested_effect = \"review-repo-agent-doctrine-update\"",
|
||||
)?;
|
||||
require_text(&request_text, "doctrine_target = \"AGENTS.md\"")?;
|
||||
require_text(&request_text, "requires_source_grounding = true")?;
|
||||
require_text(&request_text, "maintainer_review_required = true")?;
|
||||
require_text(&request_text, "[antecedents]")?;
|
||||
require_text(&request_text, "persona_or_human_feedback_required = true")?;
|
||||
require_text(&request_text, "imagination_plan_required = true")?;
|
||||
require_text(&request_text, "mind_adoption_required = true")?;
|
||||
require_text(&request_text, "soul_review_required = true")?;
|
||||
require_text(&request_text, "maintainer_review_required = true")?;
|
||||
require_text(&request_text, "[required_receipts]")?;
|
||||
require_text(
|
||||
&request_text,
|
||||
"imagination_plan = \"epiphany.repo_work_imagination_action_items_receipt.v0\"",
|
||||
)?;
|
||||
require_text(
|
||||
&request_text,
|
||||
"mind_adoption = \"epiphany.repo_work_mind_adoption_decision.v0\"",
|
||||
)?;
|
||||
require_text(
|
||||
&request_text,
|
||||
"soul_review = \"epiphany.repo_work_closure_review.v0\"",
|
||||
)?;
|
||||
require_text(
|
||||
&request_text,
|
||||
"maintainer_review = \"gamecult.maintainer.review_receipt.v0\"",
|
||||
)?;
|
||||
require_text(
|
||||
&request_text,
|
||||
"hands_commit = \"epiphany.hands.commit_receipt\"",
|
||||
)?;
|
||||
require_text(&request_text, "[doctrine_packet]")?;
|
||||
require_text(&request_text, "requires_current_doctrine_ref = true")?;
|
||||
require_text(&request_text, "requires_proposed_change_summary = true")?;
|
||||
require_text(&request_text, "requires_invariant_impact = true")?;
|
||||
require_text(&request_text, "requires_rehydration_impact = true")?;
|
||||
require_text(&request_text, "requires_rollback_plan = true")?;
|
||||
require_text(
|
||||
&request_text,
|
||||
"requires_private_state_redaction_check = true",
|
||||
)?;
|
||||
require_text(&request_text, "[authority]")?;
|
||||
require_text(&request_text, "direct_doctrine_mutation_authority = false")?;
|
||||
require_text(&request_text, "direct_hands_authority = false")?;
|
||||
require_text(&request_text, "direct_mind_state_commit = false")?;
|
||||
require_text(&request_text, "publication_authorized = false")?;
|
||||
require_text(&request_text, "merge_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, "maintainer_review_required = true")?;
|
||||
require_text(&request_text, "mind_admission_required = true")?;
|
||||
require_text(&request_text, "hands_receipts_required = true")?;
|
||||
require_text(&request_text, "private_state_exposed = false")?;
|
||||
require_text(
|
||||
&agents_text,
|
||||
"Existing doctrine stays untouched by the request artifact.",
|
||||
)?;
|
||||
|
||||
let summary = json!({
|
||||
"schemaVersion": "epiphany.repo_doctrine_update_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"],
|
||||
"awaitingDoctrineReview": true,
|
||||
"doctrineMutationAuthorized": false,
|
||||
"handsAuthorityGranted": false,
|
||||
"publicationAuthorized": false,
|
||||
"mergeAuthorized": 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<Value> {
|
||||
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<const N: usize>(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<const N: usize>(args: [&str; N], cwd: &Path) -> Result<String> {
|
||||
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<Value> {
|
||||
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("<missing>");
|
||||
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 doctrine update request text to contain {needle:?}"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn take_path(
|
||||
args: &mut std::iter::Peekable<impl Iterator<Item = String>>,
|
||||
flag: &str,
|
||||
) -> Result<PathBuf> {
|
||||
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()))
|
||||
}
|
||||
|
|
@ -1,468 +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<Self> {
|
||||
let mut root = env::current_dir().context("failed to resolve current directory")?;
|
||||
let mut args = env::args().skip(1);
|
||||
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<Value> {
|
||||
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-mind-evidence-guard-{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 Mind Evidence Guard Smoke\n\nThis repository proves Mind refuses proofless action items before Hands authority.\n",
|
||||
)
|
||||
.with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?;
|
||||
git(["add", "README.md"], &repo)?;
|
||||
git(
|
||||
["commit", "-m", "Seed repo mind evidence guard smoke body"],
|
||||
&repo,
|
||||
)?;
|
||||
git(["switch", "-c", "epiphany/repo-mind-evidence-guard"], &repo)?;
|
||||
|
||||
let item = "repo-mind-evidence-guard";
|
||||
let planned_path = "notes/epiphany-work/repo-mind-evidence-guard.md";
|
||||
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-mind-evidence-guard-smoke",
|
||||
"--topic",
|
||||
"repo-mind-evidence-guard",
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
cargo_json(
|
||||
&manifest,
|
||||
"epiphany-swarm",
|
||||
&[
|
||||
"online",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--epiphany-root",
|
||||
path_str(&root)?,
|
||||
"--runtime-id",
|
||||
"repo-mind-evidence-guard-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 reject a proofless action item before Hands receives authority.",
|
||||
"--candidate-action-ref",
|
||||
"candidate-action://repo-mind-evidence-guard/proofless-action",
|
||||
"--public-discussion-ref",
|
||||
"epiphany-global/persona-collaboration/repo-mind-evidence-guard",
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
let plan = cargo_json(
|
||||
&manifest,
|
||||
"epiphany-work",
|
||||
&[
|
||||
"derive-plan",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--item",
|
||||
item,
|
||||
"--action-family",
|
||||
"planning-note",
|
||||
"--model-ref",
|
||||
"repo-mind-evidence-guard-smoke-imagination-v1",
|
||||
"--model-authored",
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
let run = cargo_json(
|
||||
&manifest,
|
||||
"epiphany-work",
|
||||
&[
|
||||
"run",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--epiphany-root",
|
||||
path_str(&root)?,
|
||||
"--item",
|
||||
item,
|
||||
"--path",
|
||||
planned_path,
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
|
||||
let plan_path = value_at_path(&plan, &["receiptPath"])
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| anyhow!("plan output had no receiptPath"))?;
|
||||
let mut tampered_plan = read_json(Path::new(plan_path))?;
|
||||
tampered_plan["derivation"]["actionItemReceipt"]["verificationAsks"] = json!([]);
|
||||
tampered_plan["derivation"]["actionItemReceipt"]["planningFacets"]["evidenceNeeds"] = json!([]);
|
||||
let tampered_path = smoke_dir.join("tampered-plan.json");
|
||||
write_json(&tampered_path, &tampered_plan)?;
|
||||
|
||||
let adopt_failure = cargo_json_expect_failure(
|
||||
&manifest,
|
||||
"epiphany-work",
|
||||
&[
|
||||
"adopt",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--epiphany-root",
|
||||
path_str(&root)?,
|
||||
"--item",
|
||||
item,
|
||||
"--run-receipt",
|
||||
value_at_path(&run, &["receiptPath"])
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| anyhow!("run output had no receiptPath"))?,
|
||||
"--from-plan",
|
||||
path_str(&tampered_path)?,
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
if !adopt_failure.contains("lacks verification or evidence needs") {
|
||||
return Err(anyhow!(
|
||||
"adopt failure did not name missing evidence needs: {adopt_failure}"
|
||||
));
|
||||
}
|
||||
|
||||
let refusal_path = repo
|
||||
.join(".epiphany")
|
||||
.join("work")
|
||||
.join("work-mind-adopt-repo-mind-evidence-guard.json");
|
||||
let refusal = read_json(&refusal_path)?;
|
||||
require_eq(
|
||||
&refusal,
|
||||
&["schemaVersion"],
|
||||
"epiphany.repo_work_mind_adoption_decision.v0",
|
||||
)?;
|
||||
require_eq(&refusal, &["status"], "refused-missing-evidence-needs")?;
|
||||
require_eq(
|
||||
&refusal,
|
||||
&["interpretation", "inputSummary", "safeActionFamily"],
|
||||
"repo.markdown_planning_note",
|
||||
)?;
|
||||
require_eq(
|
||||
&refusal,
|
||||
&["interpretation", "inputSummary", "planChangedPaths", "0"],
|
||||
planned_path,
|
||||
)?;
|
||||
require_bool(
|
||||
&refusal,
|
||||
&["interpretation", "classification", "actionItemAccepted"],
|
||||
false,
|
||||
)?;
|
||||
require_bool(
|
||||
&refusal,
|
||||
&["interpretation", "classification", "safeFamilyRecognized"],
|
||||
true,
|
||||
)?;
|
||||
require_bool(
|
||||
&refusal,
|
||||
&["interpretation", "classification", "requestedPathsDeclared"],
|
||||
true,
|
||||
)?;
|
||||
require_bool(
|
||||
&refusal,
|
||||
&[
|
||||
"interpretation",
|
||||
"classification",
|
||||
"requestedPathsMatchPlan",
|
||||
],
|
||||
true,
|
||||
)?;
|
||||
require_bool(
|
||||
&refusal,
|
||||
&[
|
||||
"interpretation",
|
||||
"classification",
|
||||
"verificationAsksPresent",
|
||||
],
|
||||
false,
|
||||
)?;
|
||||
require_bool(
|
||||
&refusal,
|
||||
&["interpretation", "classification", "evidenceNeedsPresent"],
|
||||
false,
|
||||
)?;
|
||||
require_text_field(
|
||||
&refusal,
|
||||
&["interpretation", "refusalReasons", "0"],
|
||||
"lacks verification or evidence needs",
|
||||
)?;
|
||||
require_bool(&refusal, &["gates", "safeFamilyRecognized"], true)?;
|
||||
require_bool(&refusal, &["gates", "requestedPathsMatchPlan"], true)?;
|
||||
require_bool(&refusal, &["gates", "verificationAsksPresent"], false)?;
|
||||
require_bool(&refusal, &["gates", "evidenceNeedsPresent"], false)?;
|
||||
require_bool(&refusal, &["gates", "branchLocalOnly"], false)?;
|
||||
require_bool(&refusal, &["authority", "handsAuthorityGranted"], false)?;
|
||||
require_bool(&refusal, &["authority", "durableStateAdmitted"], false)?;
|
||||
require_bool(&refusal, &["authority", "serviceLifecycleAuthority"], false)?;
|
||||
require_bool(&refusal, &["authority", "privateStateExposed"], false)?;
|
||||
require_eq(
|
||||
&refusal,
|
||||
&["authority", "nextGate"],
|
||||
"imagination.replan_with_explicit_soul_evidence_needs",
|
||||
)?;
|
||||
require_bool(&refusal, &["privateStateExposed"], false)?;
|
||||
let adoption_receipt_path = repo
|
||||
.join(".epiphany")
|
||||
.join("work")
|
||||
.join("work-adopt-repo-mind-evidence-guard.json");
|
||||
if adoption_receipt_path.exists() {
|
||||
return Err(anyhow!(
|
||||
"adoption receipt was written despite missing-evidence refusal: {}",
|
||||
adoption_receipt_path.display()
|
||||
));
|
||||
}
|
||||
|
||||
Ok(json!({
|
||||
"schemaVersion": "epiphany.repo_work_mind_evidence_guard_smoke.v0",
|
||||
"status": "ok",
|
||||
"smokeDir": smoke_dir,
|
||||
"repo": repo,
|
||||
"item": item,
|
||||
"planChangedPath": planned_path,
|
||||
"mindDecisionStatus": refusal["status"],
|
||||
"actionItemAccepted": false,
|
||||
"safeFamilyRecognized": true,
|
||||
"requestedPathsMatchPlan": true,
|
||||
"verificationAsksPresent": false,
|
||||
"evidenceNeedsPresent": false,
|
||||
"handsAuthorityGranted": false,
|
||||
"durableStateAdmitted": false,
|
||||
"serviceLifecycleAuthority": false,
|
||||
"adoptionReceiptWritten": false,
|
||||
"privateStateExposed": false
|
||||
}))
|
||||
}
|
||||
|
||||
fn cargo_json(manifest_path: &Path, bin_name: &str, args: &[&str], cwd: &Path) -> Result<Value> {
|
||||
let output = Command::new("cargo")
|
||||
.arg("run")
|
||||
.arg("--quiet")
|
||||
.arg("--manifest-path")
|
||||
.arg(manifest_path)
|
||||
.arg("--bin")
|
||||
.arg(bin_name)
|
||||
.arg("--")
|
||||
.args(args)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.with_context(|| format!("failed to spawn cargo run --bin {bin_name}"))?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"cargo run --bin {bin_name} failed:\nstdout:\n{}\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
serde_json::from_slice(&output.stdout)
|
||||
.with_context(|| format!("{bin_name} returned invalid JSON"))
|
||||
}
|
||||
|
||||
fn cargo_json_expect_failure(
|
||||
manifest_path: &Path,
|
||||
bin_name: &str,
|
||||
args: &[&str],
|
||||
cwd: &Path,
|
||||
) -> Result<String> {
|
||||
let output = Command::new("cargo")
|
||||
.arg("run")
|
||||
.arg("--quiet")
|
||||
.arg("--manifest-path")
|
||||
.arg(manifest_path)
|
||||
.arg("--bin")
|
||||
.arg(bin_name)
|
||||
.arg("--")
|
||||
.args(args)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.with_context(|| format!("failed to spawn cargo run --bin {bin_name}"))?;
|
||||
if output.status.success() {
|
||||
return Err(anyhow!("{bin_name} unexpectedly succeeded"));
|
||||
}
|
||||
Ok(format!(
|
||||
"{}\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
))
|
||||
}
|
||||
|
||||
fn git<const N: usize>(args: [&str; N], cwd: &Path) -> Result<()> {
|
||||
let output = Command::new("git")
|
||||
.args(args)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.with_context(|| format!("failed to spawn git in {}", cwd.display()))?;
|
||||
if output.status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"git failed in {}:\n{}",
|
||||
cwd.display(),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn read_json(path: &Path) -> Result<Value> {
|
||||
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)
|
||||
.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_field(value: &Value, path: &[&str], needle: &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.contains(needle) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"expected {} to contain {:?}, got {:?}",
|
||||
path.join("."),
|
||||
needle,
|
||||
actual
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
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::<usize>() {
|
||||
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<Item = String>, name: &str) -> Result<PathBuf> {
|
||||
Ok(PathBuf::from(
|
||||
args.next()
|
||||
.ok_or_else(|| anyhow!("missing value for {name}"))?,
|
||||
))
|
||||
}
|
||||
|
|
@ -1,457 +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<Self> {
|
||||
let mut root = env::current_dir().context("failed to resolve current directory")?;
|
||||
let mut args = env::args().skip(1);
|
||||
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<Value> {
|
||||
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-mind-path-guard-{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 Mind Path Guard Smoke\n\nThis repository proves Mind refuses mismatched requested paths before Hands authority.\n",
|
||||
)
|
||||
.with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?;
|
||||
git(["add", "README.md"], &repo)?;
|
||||
git(
|
||||
["commit", "-m", "Seed repo mind path guard smoke body"],
|
||||
&repo,
|
||||
)?;
|
||||
git(["switch", "-c", "epiphany/repo-mind-path-guard"], &repo)?;
|
||||
|
||||
let item = "repo-mind-path-guard";
|
||||
let planned_path = "notes/epiphany-work/repo-mind-path-guard.md";
|
||||
let tampered_requested_path = "notes/epiphany-work/alien-path.md";
|
||||
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-mind-path-guard-smoke",
|
||||
"--topic",
|
||||
"repo-mind-path-guard",
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
cargo_json(
|
||||
&manifest,
|
||||
"epiphany-swarm",
|
||||
&[
|
||||
"online",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--epiphany-root",
|
||||
path_str(&root)?,
|
||||
"--runtime-id",
|
||||
"repo-mind-path-guard-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 reject a tampered requested path before Hands receives authority.",
|
||||
"--candidate-action-ref",
|
||||
"candidate-action://repo-mind-path-guard/tampered-path",
|
||||
"--public-discussion-ref",
|
||||
"epiphany-global/persona-collaboration/repo-mind-path-guard",
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
let plan = cargo_json(
|
||||
&manifest,
|
||||
"epiphany-work",
|
||||
&[
|
||||
"derive-plan",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--item",
|
||||
item,
|
||||
"--action-family",
|
||||
"planning-note",
|
||||
"--model-ref",
|
||||
"repo-mind-path-guard-smoke-imagination-v1",
|
||||
"--model-authored",
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
let run = cargo_json(
|
||||
&manifest,
|
||||
"epiphany-work",
|
||||
&[
|
||||
"run",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--epiphany-root",
|
||||
path_str(&root)?,
|
||||
"--item",
|
||||
item,
|
||||
"--path",
|
||||
planned_path,
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
|
||||
let plan_path = value_at_path(&plan, &["receiptPath"])
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| anyhow!("plan output had no receiptPath"))?;
|
||||
let mut tampered_plan = read_json(Path::new(plan_path))?;
|
||||
tampered_plan["derivation"]["actionItemReceipt"]["requestedPaths"] =
|
||||
json!([tampered_requested_path]);
|
||||
let tampered_path = smoke_dir.join("tampered-plan.json");
|
||||
write_json(&tampered_path, &tampered_plan)?;
|
||||
|
||||
let adopt_failure = cargo_json_expect_failure(
|
||||
&manifest,
|
||||
"epiphany-work",
|
||||
&[
|
||||
"adopt",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--epiphany-root",
|
||||
path_str(&root)?,
|
||||
"--item",
|
||||
item,
|
||||
"--run-receipt",
|
||||
value_at_path(&run, &["receiptPath"])
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| anyhow!("run output had no receiptPath"))?,
|
||||
"--from-plan",
|
||||
path_str(&tampered_path)?,
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
if !adopt_failure.contains("do not match plan changed paths") {
|
||||
return Err(anyhow!(
|
||||
"adopt failure did not name requested-path mismatch: {adopt_failure}"
|
||||
));
|
||||
}
|
||||
|
||||
let refusal_path = repo
|
||||
.join(".epiphany")
|
||||
.join("work")
|
||||
.join("work-mind-adopt-repo-mind-path-guard.json");
|
||||
let refusal = read_json(&refusal_path)?;
|
||||
require_eq(
|
||||
&refusal,
|
||||
&["schemaVersion"],
|
||||
"epiphany.repo_work_mind_adoption_decision.v0",
|
||||
)?;
|
||||
require_eq(&refusal, &["status"], "refused-requested-path-mismatch")?;
|
||||
require_eq(
|
||||
&refusal,
|
||||
&["interpretation", "inputSummary", "safeActionFamily"],
|
||||
"repo.markdown_planning_note",
|
||||
)?;
|
||||
require_eq(
|
||||
&refusal,
|
||||
&["interpretation", "inputSummary", "requestedPaths", "0"],
|
||||
tampered_requested_path,
|
||||
)?;
|
||||
require_eq(
|
||||
&refusal,
|
||||
&["interpretation", "inputSummary", "planChangedPaths", "0"],
|
||||
planned_path,
|
||||
)?;
|
||||
require_bool(
|
||||
&refusal,
|
||||
&["interpretation", "classification", "actionItemAccepted"],
|
||||
false,
|
||||
)?;
|
||||
require_bool(
|
||||
&refusal,
|
||||
&["interpretation", "classification", "safeFamilyRecognized"],
|
||||
true,
|
||||
)?;
|
||||
require_bool(
|
||||
&refusal,
|
||||
&["interpretation", "classification", "requestedPathsDeclared"],
|
||||
true,
|
||||
)?;
|
||||
require_bool(
|
||||
&refusal,
|
||||
&[
|
||||
"interpretation",
|
||||
"classification",
|
||||
"requestedPathsMatchPlan",
|
||||
],
|
||||
false,
|
||||
)?;
|
||||
require_text_field(
|
||||
&refusal,
|
||||
&["interpretation", "refusalReasons", "0"],
|
||||
"do not match plan changed paths",
|
||||
)?;
|
||||
require_bool(&refusal, &["gates", "safeFamilyRecognized"], true)?;
|
||||
require_bool(&refusal, &["gates", "requestedPathsMatchPlan"], false)?;
|
||||
require_bool(&refusal, &["gates", "branchLocalOnly"], false)?;
|
||||
require_bool(&refusal, &["authority", "handsAuthorityGranted"], false)?;
|
||||
require_bool(&refusal, &["authority", "durableStateAdmitted"], false)?;
|
||||
require_bool(&refusal, &["authority", "serviceLifecycleAuthority"], false)?;
|
||||
require_bool(&refusal, &["authority", "privateStateExposed"], false)?;
|
||||
require_eq(
|
||||
&refusal,
|
||||
&["authority", "nextGate"],
|
||||
"imagination.replan_with_matching_requested_paths",
|
||||
)?;
|
||||
require_bool(&refusal, &["privateStateExposed"], false)?;
|
||||
let adoption_receipt_path = repo
|
||||
.join(".epiphany")
|
||||
.join("work")
|
||||
.join("work-adopt-repo-mind-path-guard.json");
|
||||
if adoption_receipt_path.exists() {
|
||||
return Err(anyhow!(
|
||||
"adoption receipt was written despite requested-path mismatch refusal: {}",
|
||||
adoption_receipt_path.display()
|
||||
));
|
||||
}
|
||||
|
||||
Ok(json!({
|
||||
"schemaVersion": "epiphany.repo_work_mind_path_guard_smoke.v0",
|
||||
"status": "ok",
|
||||
"smokeDir": smoke_dir,
|
||||
"repo": repo,
|
||||
"item": item,
|
||||
"tamperedRequestedPath": tampered_requested_path,
|
||||
"planChangedPath": planned_path,
|
||||
"mindDecisionStatus": refusal["status"],
|
||||
"actionItemAccepted": false,
|
||||
"safeFamilyRecognized": true,
|
||||
"requestedPathsMatchPlan": false,
|
||||
"handsAuthorityGranted": false,
|
||||
"durableStateAdmitted": false,
|
||||
"serviceLifecycleAuthority": false,
|
||||
"adoptionReceiptWritten": false,
|
||||
"privateStateExposed": false
|
||||
}))
|
||||
}
|
||||
|
||||
fn cargo_json(manifest_path: &Path, bin_name: &str, args: &[&str], cwd: &Path) -> Result<Value> {
|
||||
let output = Command::new("cargo")
|
||||
.arg("run")
|
||||
.arg("--quiet")
|
||||
.arg("--manifest-path")
|
||||
.arg(manifest_path)
|
||||
.arg("--bin")
|
||||
.arg(bin_name)
|
||||
.arg("--")
|
||||
.args(args)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.with_context(|| format!("failed to spawn cargo run --bin {bin_name}"))?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"cargo run --bin {bin_name} failed:\nstdout:\n{}\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
serde_json::from_slice(&output.stdout)
|
||||
.with_context(|| format!("{bin_name} returned invalid JSON"))
|
||||
}
|
||||
|
||||
fn cargo_json_expect_failure(
|
||||
manifest_path: &Path,
|
||||
bin_name: &str,
|
||||
args: &[&str],
|
||||
cwd: &Path,
|
||||
) -> Result<String> {
|
||||
let output = Command::new("cargo")
|
||||
.arg("run")
|
||||
.arg("--quiet")
|
||||
.arg("--manifest-path")
|
||||
.arg(manifest_path)
|
||||
.arg("--bin")
|
||||
.arg(bin_name)
|
||||
.arg("--")
|
||||
.args(args)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.with_context(|| format!("failed to spawn cargo run --bin {bin_name}"))?;
|
||||
if output.status.success() {
|
||||
return Err(anyhow!("{bin_name} unexpectedly succeeded"));
|
||||
}
|
||||
Ok(format!(
|
||||
"{}\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
))
|
||||
}
|
||||
|
||||
fn git<const N: usize>(args: [&str; N], cwd: &Path) -> Result<()> {
|
||||
let output = Command::new("git")
|
||||
.args(args)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.with_context(|| format!("failed to spawn git in {}", cwd.display()))?;
|
||||
if output.status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"git failed in {}:\n{}",
|
||||
cwd.display(),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn read_json(path: &Path) -> Result<Value> {
|
||||
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)
|
||||
.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_field(value: &Value, path: &[&str], needle: &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.contains(needle) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"expected {} to contain {:?}, got {:?}",
|
||||
path.join("."),
|
||||
needle,
|
||||
actual
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
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::<usize>() {
|
||||
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<Item = String>, name: &str) -> Result<PathBuf> {
|
||||
Ok(PathBuf::from(
|
||||
args.next()
|
||||
.ok_or_else(|| anyhow!("missing value for {name}"))?,
|
||||
))
|
||||
}
|
||||
|
|
@ -1,433 +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<Self> {
|
||||
let mut root = env::current_dir().context("failed to resolve current directory")?;
|
||||
let mut args = env::args().skip(1);
|
||||
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<Value> {
|
||||
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-mind-safe-family-guard-{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 Mind Safe Family Guard Smoke\n\nThis repository proves Mind refuses alien safe-family labels before Hands authority.\n",
|
||||
)
|
||||
.with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?;
|
||||
git(["add", "README.md"], &repo)?;
|
||||
git(
|
||||
[
|
||||
"commit",
|
||||
"-m",
|
||||
"Seed repo mind safe family guard smoke body",
|
||||
],
|
||||
&repo,
|
||||
)?;
|
||||
git(
|
||||
["switch", "-c", "epiphany/repo-mind-safe-family-guard"],
|
||||
&repo,
|
||||
)?;
|
||||
|
||||
let item = "repo-mind-safe-family-guard";
|
||||
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-mind-safe-family-guard-smoke",
|
||||
"--topic",
|
||||
"repo-mind-safe-family-guard",
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
cargo_json(
|
||||
&manifest,
|
||||
"epiphany-swarm",
|
||||
&[
|
||||
"online",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--epiphany-root",
|
||||
path_str(&root)?,
|
||||
"--runtime-id",
|
||||
"repo-mind-safe-family-guard-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 reject a tampered safe-family label before Hands receives authority.",
|
||||
"--candidate-action-ref",
|
||||
"candidate-action://repo-mind-safe-family-guard/tampered-family",
|
||||
"--public-discussion-ref",
|
||||
"epiphany-global/persona-collaboration/repo-mind-safe-family-guard",
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
let plan = cargo_json(
|
||||
&manifest,
|
||||
"epiphany-work",
|
||||
&[
|
||||
"derive-plan",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--item",
|
||||
item,
|
||||
"--action-family",
|
||||
"planning-note",
|
||||
"--model-ref",
|
||||
"repo-mind-safe-family-guard-smoke-imagination-v1",
|
||||
"--model-authored",
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
let run = cargo_json(
|
||||
&manifest,
|
||||
"epiphany-work",
|
||||
&[
|
||||
"run",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--epiphany-root",
|
||||
path_str(&root)?,
|
||||
"--item",
|
||||
item,
|
||||
"--path",
|
||||
"notes/epiphany-work/repo-mind-safe-family-guard.md",
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
|
||||
let plan_path = value_at_path(&plan, &["receiptPath"])
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| anyhow!("plan output had no receiptPath"))?;
|
||||
let mut tampered_plan = read_json(Path::new(plan_path))?;
|
||||
tampered_plan["derivation"]["actionItemReceipt"]["safeActionFamily"] =
|
||||
json!("idunn.service_lifecycle");
|
||||
let tampered_path = smoke_dir.join("tampered-plan.json");
|
||||
write_json(&tampered_path, &tampered_plan)?;
|
||||
|
||||
let adopt_failure = cargo_json_expect_failure(
|
||||
&manifest,
|
||||
"epiphany-work",
|
||||
&[
|
||||
"adopt",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--epiphany-root",
|
||||
path_str(&root)?,
|
||||
"--item",
|
||||
item,
|
||||
"--run-receipt",
|
||||
value_at_path(&run, &["receiptPath"])
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| anyhow!("run output had no receiptPath"))?,
|
||||
"--from-plan",
|
||||
path_str(&tampered_path)?,
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
if !adopt_failure.contains("unsupported repo-work safe family") {
|
||||
return Err(anyhow!(
|
||||
"adopt failure did not name unsupported safe family: {adopt_failure}"
|
||||
));
|
||||
}
|
||||
|
||||
let refusal_path = repo
|
||||
.join(".epiphany")
|
||||
.join("work")
|
||||
.join("work-mind-adopt-repo-mind-safe-family-guard.json");
|
||||
let refusal = read_json(&refusal_path)?;
|
||||
require_eq(
|
||||
&refusal,
|
||||
&["schemaVersion"],
|
||||
"epiphany.repo_work_mind_adoption_decision.v0",
|
||||
)?;
|
||||
require_eq(&refusal, &["status"], "refused-unsupported-safe-family")?;
|
||||
require_eq(
|
||||
&refusal,
|
||||
&["interpretation", "inputSummary", "safeActionFamily"],
|
||||
"idunn.service_lifecycle",
|
||||
)?;
|
||||
require_bool(
|
||||
&refusal,
|
||||
&["interpretation", "classification", "actionItemAccepted"],
|
||||
false,
|
||||
)?;
|
||||
require_bool(
|
||||
&refusal,
|
||||
&["interpretation", "classification", "safeFamilyRecognized"],
|
||||
false,
|
||||
)?;
|
||||
require_text_field(
|
||||
&refusal,
|
||||
&["interpretation", "refusalReasons", "0"],
|
||||
"Unsupported repo-work safe action family idunn.service_lifecycle",
|
||||
)?;
|
||||
require_bool(&refusal, &["authority", "handsAuthorityGranted"], false)?;
|
||||
require_bool(&refusal, &["authority", "durableStateAdmitted"], false)?;
|
||||
require_bool(&refusal, &["authority", "serviceLifecycleAuthority"], false)?;
|
||||
require_bool(&refusal, &["authority", "privateStateExposed"], false)?;
|
||||
require_eq(
|
||||
&refusal,
|
||||
&["authority", "nextGate"],
|
||||
"imagination.replan_with_allowed_safe_family",
|
||||
)?;
|
||||
require_bool(&refusal, &["privateStateExposed"], false)?;
|
||||
let adoption_receipt_path = repo
|
||||
.join(".epiphany")
|
||||
.join("work")
|
||||
.join("work-adopt-repo-mind-safe-family-guard.json");
|
||||
if adoption_receipt_path.exists() {
|
||||
return Err(anyhow!(
|
||||
"adoption receipt was written despite unsupported safe-family refusal: {}",
|
||||
adoption_receipt_path.display()
|
||||
));
|
||||
}
|
||||
|
||||
Ok(json!({
|
||||
"schemaVersion": "epiphany.repo_work_mind_safe_family_guard_smoke.v0",
|
||||
"status": "ok",
|
||||
"smokeDir": smoke_dir,
|
||||
"repo": repo,
|
||||
"item": item,
|
||||
"tamperedSafeActionFamily": "idunn.service_lifecycle",
|
||||
"mindDecisionStatus": refusal["status"],
|
||||
"actionItemAccepted": false,
|
||||
"safeFamilyRecognized": false,
|
||||
"handsAuthorityGranted": false,
|
||||
"durableStateAdmitted": false,
|
||||
"serviceLifecycleAuthority": false,
|
||||
"adoptionReceiptWritten": false,
|
||||
"privateStateExposed": false
|
||||
}))
|
||||
}
|
||||
|
||||
fn cargo_json(manifest_path: &Path, bin_name: &str, args: &[&str], cwd: &Path) -> Result<Value> {
|
||||
let output = Command::new("cargo")
|
||||
.arg("run")
|
||||
.arg("--quiet")
|
||||
.arg("--manifest-path")
|
||||
.arg(manifest_path)
|
||||
.arg("--bin")
|
||||
.arg(bin_name)
|
||||
.arg("--")
|
||||
.args(args)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.with_context(|| format!("failed to spawn cargo run --bin {bin_name}"))?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"cargo run --bin {bin_name} failed:\nstdout:\n{}\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
serde_json::from_slice(&output.stdout)
|
||||
.with_context(|| format!("{bin_name} returned invalid JSON"))
|
||||
}
|
||||
|
||||
fn cargo_json_expect_failure(
|
||||
manifest_path: &Path,
|
||||
bin_name: &str,
|
||||
args: &[&str],
|
||||
cwd: &Path,
|
||||
) -> Result<String> {
|
||||
let output = Command::new("cargo")
|
||||
.arg("run")
|
||||
.arg("--quiet")
|
||||
.arg("--manifest-path")
|
||||
.arg(manifest_path)
|
||||
.arg("--bin")
|
||||
.arg(bin_name)
|
||||
.arg("--")
|
||||
.args(args)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.with_context(|| format!("failed to spawn cargo run --bin {bin_name}"))?;
|
||||
if output.status.success() {
|
||||
return Err(anyhow!("{bin_name} unexpectedly succeeded"));
|
||||
}
|
||||
Ok(format!(
|
||||
"{}\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
))
|
||||
}
|
||||
|
||||
fn git<const N: usize>(args: [&str; N], cwd: &Path) -> Result<()> {
|
||||
let output = Command::new("git")
|
||||
.args(args)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.with_context(|| format!("failed to spawn git in {}", cwd.display()))?;
|
||||
if output.status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"git failed in {}:\n{}",
|
||||
cwd.display(),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn read_json(path: &Path) -> Result<Value> {
|
||||
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)
|
||||
.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_field(value: &Value, path: &[&str], needle: &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.contains(needle) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"expected {} to contain {:?}, got {:?}",
|
||||
path.join("."),
|
||||
needle,
|
||||
actual
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
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::<usize>() {
|
||||
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<Item = String>, name: &str) -> Result<PathBuf> {
|
||||
Ok(PathBuf::from(
|
||||
args.next()
|
||||
.ok_or_else(|| anyhow!("missing value for {name}"))?,
|
||||
))
|
||||
}
|
||||
|
|
@ -1,616 +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<Self> {
|
||||
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<Value> {
|
||||
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-planning-facets-{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 Planning Facets Smoke\n\nThis repository proves richer Imagination planning cargo without command authority.\n",
|
||||
)
|
||||
.with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?;
|
||||
git(["add", "README.md"], &repo)?;
|
||||
git(
|
||||
["commit", "-m", "Seed repo planning facets smoke body"],
|
||||
&repo,
|
||||
)?;
|
||||
git(["switch", "-c", "epiphany/repo-planning-facets"], &repo)?;
|
||||
|
||||
let item = "repo-planning-facets";
|
||||
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-planning-facets-smoke",
|
||||
"--topic",
|
||||
"repo-planning-facets",
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
cargo_json(
|
||||
&manifest,
|
||||
"epiphany-swarm",
|
||||
&[
|
||||
"online",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--epiphany-root",
|
||||
path_str(&root)?,
|
||||
"--runtime-id",
|
||||
"repo-planning-facets-smoke",
|
||||
"--local-verse-store",
|
||||
path_str(&local_verse)?,
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
cargo_json(
|
||||
&manifest,
|
||||
"epiphany-work",
|
||||
&[
|
||||
"accept",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--from",
|
||||
"persona",
|
||||
"--item",
|
||||
item,
|
||||
"--summary",
|
||||
"Let Imagination preserve richer planning facets before Self chooses the safe move.",
|
||||
"--candidate-action-ref",
|
||||
"candidate-action://repo-planning-facets/action-items",
|
||||
"--public-discussion-ref",
|
||||
"epiphany-global/persona-collaboration/repo-planning-facets",
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
let plan = cargo_json(
|
||||
&manifest,
|
||||
"epiphany-work",
|
||||
&[
|
||||
"derive-plan",
|
||||
"--workspace",
|
||||
path_str(&repo)?,
|
||||
"--item",
|
||||
item,
|
||||
"--action-family",
|
||||
"planning-note",
|
||||
"--model-ref",
|
||||
"repo-planning-facets-smoke-imagination-v1",
|
||||
"--model-authored",
|
||||
"--assumption",
|
||||
"Persona feedback is public enough to route into Imagination.",
|
||||
"--constraint",
|
||||
"Hands must only write the declared planning note path.",
|
||||
"--non-goal",
|
||||
"Do not publish, merge, deploy, or mutate service lifecycle state.",
|
||||
"--open-question",
|
||||
"Should Self adopt this as the next branch-local move?",
|
||||
"--decision-point",
|
||||
"Self may adopt, defer, or ask Imagination for a narrower safe family.",
|
||||
"--evidence-need",
|
||||
"Soul needs the action-item receipt, plan receipt, changed-path proof, and private-state seal.",
|
||||
],
|
||||
&root,
|
||||
)?;
|
||||
|
||||
let action_items_path = repo
|
||||
.join(".epiphany")
|
||||
.join("work")
|
||||
.join("work-action-items-repo-planning-facets.json");
|
||||
let action_items = read_json(&action_items_path)?;
|
||||
require_eq(
|
||||
&action_items,
|
||||
&["schemaVersion"],
|
||||
"epiphany.repo_work_imagination_action_items_receipt.v0",
|
||||
)?;
|
||||
require_eq(&action_items, &["status"], "proposed-for-self-mind-review")?;
|
||||
require_eq(
|
||||
&action_items,
|
||||
&["actionItems", "0", "safeActionFamily"],
|
||||
"repo.markdown_planning_note",
|
||||
)?;
|
||||
require_bool(
|
||||
&action_items,
|
||||
&["model", "operatorAuthoredShellDetails"],
|
||||
false,
|
||||
)?;
|
||||
require_bool(
|
||||
&action_items,
|
||||
&["authority", "handsAuthorityGranted"],
|
||||
false,
|
||||
)?;
|
||||
require_bool(&action_items, &["authority", "durableStateAdmitted"], false)?;
|
||||
require_bool(&action_items, &["privateStateExposed"], false)?;
|
||||
require_text_field(
|
||||
&action_items,
|
||||
&["actionItems", "0", "planningFacets", "assumptions", "0"],
|
||||
"Persona feedback is public enough",
|
||||
)?;
|
||||
require_text_field(
|
||||
&action_items,
|
||||
&["actionItems", "0", "planningFacets", "constraints", "0"],
|
||||
"declared planning note path",
|
||||
)?;
|
||||
require_text_field(
|
||||
&action_items,
|
||||
&["actionItems", "0", "planningFacets", "nonGoals", "0"],
|
||||
"Do not publish",
|
||||
)?;
|
||||
require_text_field(
|
||||
&action_items,
|
||||
&["actionItems", "0", "planningFacets", "openQuestions", "0"],
|
||||
"Should Self adopt",
|
||||
)?;
|
||||
require_text_field(
|
||||
&action_items,
|
||||
&["actionItems", "0", "planningFacets", "decisionPoints", "0"],
|
||||
"Self may adopt",
|
||||
)?;
|
||||
require_text_field(
|
||||
&action_items,
|
||||
&["actionItems", "0", "planningFacets", "evidenceNeeds", "0"],
|
||||
"Soul needs",
|
||||
)?;
|
||||
require_bool(
|
||||
&action_items,
|
||||
&[
|
||||
"actionItems",
|
||||
"0",
|
||||
"planningFacets",
|
||||
"handsCommandAuthority",
|
||||
],
|
||||
false,
|
||||
)?;
|
||||
require_bool(
|
||||
&action_items,
|
||||
&[
|
||||
"actionItems",
|
||||
"0",
|
||||
"planningFacets",
|
||||
"durableStateAuthority",
|
||||
],
|
||||
false,
|
||||
)?;
|
||||
require_eq(
|
||||
&plan,
|
||||
&[
|
||||
"derivation",
|
||||
"actionItemReceipt",
|
||||
"planningFacets",
|
||||
"constraints",
|
||||
"0",
|
||||
],
|
||||
"Hands must only write the declared planning note path.",
|
||||
)?;
|
||||
|
||||
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 adoption = read_json(
|
||||
&repo
|
||||
.join(".epiphany")
|
||||
.join("work")
|
||||
.join(format!("work-adopt-{item}.json")),
|
||||
)?;
|
||||
require_eq(
|
||||
&adoption,
|
||||
&["schemaVersion"],
|
||||
"epiphany.repo_work_adoption_receipt.v0",
|
||||
)?;
|
||||
require_eq(
|
||||
&adoption,
|
||||
&["status"],
|
||||
"approved-for-branch-local-hands-action",
|
||||
)?;
|
||||
require_eq(
|
||||
&adoption,
|
||||
&["mindAdoptionDecision", "schemaVersion"],
|
||||
"epiphany.repo_work_mind_adoption_decision.v0",
|
||||
)?;
|
||||
require_eq(
|
||||
&adoption,
|
||||
&["mindAdoptionDecision", "status"],
|
||||
"adopted-for-branch-local-hands",
|
||||
)?;
|
||||
require_eq(&adoption, &["mindAdoptionDecision", "owner"], "Mind")?;
|
||||
require_bool(
|
||||
&adoption,
|
||||
&["mindAdoptionDecision", "gates", "selfPresentedActionItem"],
|
||||
true,
|
||||
)?;
|
||||
require_bool(
|
||||
&adoption,
|
||||
&["mindAdoptionDecision", "gates", "mindReviewedEvidence"],
|
||||
true,
|
||||
)?;
|
||||
require_bool(
|
||||
&adoption,
|
||||
&["mindAdoptionDecision", "gates", "mindInterpretedActionItem"],
|
||||
true,
|
||||
)?;
|
||||
require_bool(
|
||||
&adoption,
|
||||
&["mindAdoptionDecision", "gates", "branchLocalOnly"],
|
||||
true,
|
||||
)?;
|
||||
require_bool(
|
||||
&adoption,
|
||||
&["mindAdoptionDecision", "authority", "handsAuthorityGranted"],
|
||||
false,
|
||||
)?;
|
||||
require_bool(
|
||||
&adoption,
|
||||
&["mindAdoptionDecision", "authority", "durableStateAdmitted"],
|
||||
false,
|
||||
)?;
|
||||
require_bool(
|
||||
&adoption,
|
||||
&["mindAdoptionDecision", "authority", "publicationAuthorized"],
|
||||
false,
|
||||
)?;
|
||||
require_bool(
|
||||
&adoption,
|
||||
&[
|
||||
"mindAdoptionDecision",
|
||||
"authority",
|
||||
"serviceLifecycleAuthority",
|
||||
],
|
||||
false,
|
||||
)?;
|
||||
require_bool(
|
||||
&adoption,
|
||||
&["mindAdoptionDecision", "authority", "privateStateExposed"],
|
||||
false,
|
||||
)?;
|
||||
let mind_adoption_path = value_at_path(&adoption, &["mindAdoptionDecision", "receiptPath"])
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| anyhow!("missing Mind adoption decision receipt path"))?;
|
||||
let mind_adoption = read_json(Path::new(mind_adoption_path))?;
|
||||
require_eq(
|
||||
&mind_adoption,
|
||||
&["schemaVersion"],
|
||||
"epiphany.repo_work_mind_adoption_decision.v0",
|
||||
)?;
|
||||
require_eq(
|
||||
&mind_adoption,
|
||||
&["interpretation", "schemaVersion"],
|
||||
"epiphany.repo_work_mind_interpretation.v0",
|
||||
)?;
|
||||
require_eq(
|
||||
&mind_adoption,
|
||||
&["interpretation", "classification", "decisionKind"],
|
||||
"branch-local-hands-adoption",
|
||||
)?;
|
||||
require_eq(
|
||||
&mind_adoption,
|
||||
&["interpretation", "inputSummary", "safeActionFamily"],
|
||||
"repo.markdown_planning_note",
|
||||
)?;
|
||||
require_bool(
|
||||
&mind_adoption,
|
||||
&["interpretation", "classification", "actionItemAccepted"],
|
||||
true,
|
||||
)?;
|
||||
require_bool(
|
||||
&mind_adoption,
|
||||
&["interpretation", "classification", "safeFamilyRecognized"],
|
||||
true,
|
||||
)?;
|
||||
require_bool(
|
||||
&mind_adoption,
|
||||
&["interpretation", "classification", "requestedPathsDeclared"],
|
||||
true,
|
||||
)?;
|
||||
require_eq(
|
||||
&mind_adoption,
|
||||
&["interpretation", "classification", "durableStateAdmission"],
|
||||
"not-admitted",
|
||||
)?;
|
||||
require_eq(
|
||||
&mind_adoption,
|
||||
&["interpretation", "allowedTransitions", "0"],
|
||||
"hands.branch_local_action",
|
||||
)?;
|
||||
require_eq(
|
||||
&mind_adoption,
|
||||
&["interpretation", "forbiddenTransitions", "0"],
|
||||
"mind.durable_state_commit",
|
||||
)?;
|
||||
require_bool(
|
||||
&mind_adoption,
|
||||
&["interpretation", "privateStateExposed"],
|
||||
false,
|
||||
)?;
|
||||
require_eq(
|
||||
&mind_adoption,
|
||||
&["adoptedActionItem", "safeActionFamily"],
|
||||
"repo.markdown_planning_note",
|
||||
)?;
|
||||
require_text_field(&mind_adoption, &["rationale"], "Mind adopted")?;
|
||||
require_bool(&mind_adoption, &["privateStateExposed"], false)?;
|
||||
require_eq(
|
||||
&adoption,
|
||||
&["adoptedActionItem", "safeActionFamily"],
|
||||
"repo.markdown_planning_note",
|
||||
)?;
|
||||
require_text_field(
|
||||
&adoption,
|
||||
&["adoptedActionItem", "planningFacets", "decisionPoints", "0"],
|
||||
"Self may adopt",
|
||||
)?;
|
||||
require_bool(
|
||||
&adoption,
|
||||
&["adoptedActionItem", "handsCommandAuthority"],
|
||||
false,
|
||||
)?;
|
||||
require_bool(
|
||||
&adoption,
|
||||
&["adoptedActionItem", "durableStateAuthority"],
|
||||
false,
|
||||
)?;
|
||||
require_bool(
|
||||
&adoption,
|
||||
&["adoptedActionItem", "publicationAuthorized"],
|
||||
false,
|
||||
)?;
|
||||
require_bool(
|
||||
&adoption,
|
||||
&["adoptedActionItem", "privateStateExposed"],
|
||||
false,
|
||||
)?;
|
||||
require_bool(&adoption, &["authority", "handsAuthorityGranted"], true)?;
|
||||
require_bool(&adoption, &["authority", "durableStateAdmitted"], false)?;
|
||||
require_bool(&adoption, &["privateStateExposed"], false)?;
|
||||
let close = read_json(
|
||||
&repo
|
||||
.join(".epiphany")
|
||||
.join("work")
|
||||
.join(format!("work-close-{item}.json")),
|
||||
)?;
|
||||
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)?;
|
||||
|
||||
Ok(json!({
|
||||
"schemaVersion": "epiphany.repo_planning_facets_smoke.v0",
|
||||
"status": "ok",
|
||||
"smokeDir": smoke_dir,
|
||||
"repo": repo,
|
||||
"item": item,
|
||||
"safeActionFamily": "repo.markdown_planning_note",
|
||||
"modelAuthored": true,
|
||||
"assumptionsRecorded": true,
|
||||
"constraintsRecorded": true,
|
||||
"nonGoalsRecorded": true,
|
||||
"openQuestionsRecorded": true,
|
||||
"decisionPointsRecorded": true,
|
||||
"evidenceNeedsRecorded": true,
|
||||
"adoptedActionItemRecorded": true,
|
||||
"mindAdoptionDecisionRecorded": true,
|
||||
"mindInterpretationRecorded": true,
|
||||
"mindDecisionHandsAuthorityGranted": false,
|
||||
"mindDecisionDurableStateAdmitted": false,
|
||||
"adoptionFacetsRecorded": true,
|
||||
"handsAuthorityGranted": false,
|
||||
"durableStateAdmitted": false,
|
||||
"handsCommandAuthority": false,
|
||||
"durableStateAuthority": false,
|
||||
"adoptionHandsAuthorityGranted": true,
|
||||
"adoptionDurableStateAdmitted": false,
|
||||
"closeStatus": "closed",
|
||||
"soulVerdict": "passed",
|
||||
"familyAssertionsStatus": "passed",
|
||||
"pathScopeMatched": true,
|
||||
"privateStateExposed": false
|
||||
}))
|
||||
}
|
||||
|
||||
fn cargo_json(manifest_path: &Path, bin_name: &str, args: &[&str], cwd: &Path) -> Result<Value> {
|
||||
let output = Command::new("cargo")
|
||||
.arg("run")
|
||||
.arg("--quiet")
|
||||
.arg("--manifest-path")
|
||||
.arg(manifest_path)
|
||||
.arg("--bin")
|
||||
.arg(bin_name)
|
||||
.arg("--")
|
||||
.args(args)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.with_context(|| format!("failed to spawn cargo run --bin {bin_name}"))?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"cargo run --bin {bin_name} failed:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
serde_json::from_slice(&output.stdout)
|
||||
.with_context(|| format!("{bin_name} returned invalid JSON"))
|
||||
}
|
||||
|
||||
fn git<const N: usize>(args: [&str; N], cwd: &Path) -> Result<()> {
|
||||
let output = Command::new("git")
|
||||
.args(args)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.with_context(|| format!("failed to spawn git in {}", cwd.display()))?;
|
||||
if output.status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"git failed in {}:\n{}",
|
||||
cwd.display(),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn read_json(path: &Path) -> Result<Value> {
|
||||
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 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_field(value: &Value, path: &[&str], needle: &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.contains(needle) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"expected {} to contain {:?}, got {:?}",
|
||||
path.join("."),
|
||||
needle,
|
||||
actual
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
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::<usize>() {
|
||||
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<Item = String>, name: &str) -> Result<PathBuf> {
|
||||
Ok(PathBuf::from(
|
||||
args.next()
|
||||
.ok_or_else(|| anyhow!("missing value for {name}"))?,
|
||||
))
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -45,7 +45,7 @@ impl RepoDeploymentConfig {
|
|||
|
||||
pub(super) fn has_required_receipt_contract(&self) -> bool {
|
||||
let receipts = &self.required_receipts;
|
||||
receipts.mind_adoption == "epiphany.repo_work_mind_adoption_decision.v0"
|
||||
receipts.mind_adoption == "epiphany.repo_work_plan_adoption_review.v0"
|
||||
&& receipts.soul_review == "epiphany.repo_work_closure_review.v0"
|
||||
&& receipts.maintainer_review == "gamecult.maintainer.review_receipt.v0"
|
||||
&& receipts.secret_policy == "epiphany.repo_secret_policy_request.v0"
|
||||
|
|
@ -226,7 +226,7 @@ impl RepoSecretPolicyRequest {
|
|||
let receipts = &self.required_receipts;
|
||||
receipts.source_grounding == "epiphany.eyes.evidence_packet"
|
||||
&& receipts.soul_review == "epiphany.repo_work_closure_review.v0"
|
||||
&& receipts.mind_adoption == "epiphany.repo_work_mind_adoption_decision.v0"
|
||||
&& receipts.mind_adoption == "epiphany.repo_work_plan_adoption_review.v0"
|
||||
&& receipts.maintainer_review == "gamecult.maintainer.review_receipt.v0"
|
||||
&& receipts.bifrost_publication_review
|
||||
== "gamecult.bifrost.publication_review_receipt.v0"
|
||||
|
|
@ -382,7 +382,7 @@ impl RepoDependencyPolicyRequest {
|
|||
let value = &self.required_receipts;
|
||||
value.source_grounding == "epiphany.eyes.evidence_packet"
|
||||
&& value.soul_review == "epiphany.repo_work_closure_review.v0"
|
||||
&& value.mind_adoption == "epiphany.repo_work_mind_adoption_decision.v0"
|
||||
&& value.mind_adoption == "epiphany.repo_work_plan_adoption_review.v0"
|
||||
&& value.maintainer_review == "gamecult.maintainer.review_receipt.v0"
|
||||
&& value.bifrost_publication_review == "gamecult.bifrost.publication_review_receipt.v0"
|
||||
&& value.dependency_audit == "gamecult.supply_chain.dependency_audit_receipt.v0"
|
||||
|
|
@ -536,7 +536,7 @@ impl RepoDeploymentRequest {
|
|||
pub(super) fn has_receipt_contract(&self) -> bool {
|
||||
let value = &self.required_receipts;
|
||||
value.source_grounding == "epiphany.eyes.evidence_packet"
|
||||
&& value.mind_adoption == "epiphany.repo_work_mind_adoption_decision.v0"
|
||||
&& value.mind_adoption == "epiphany.repo_work_plan_adoption_review.v0"
|
||||
&& value.soul_review == "epiphany.repo_work_closure_review.v0"
|
||||
&& value.maintainer_review == "gamecult.maintainer.review_receipt.v0"
|
||||
&& value.secret_policy == "epiphany.repo_secret_policy_request.v0"
|
||||
|
|
@ -626,7 +626,7 @@ impl RepoDoctrineUpdateRequest {
|
|||
),
|
||||
(
|
||||
"mind_adoption",
|
||||
"epiphany.repo_work_mind_adoption_decision.v0",
|
||||
"epiphany.repo_work_plan_adoption_review.v0",
|
||||
),
|
||||
("soul_review", "epiphany.repo_work_closure_review.v0"),
|
||||
("maintainer_review", "gamecult.maintainer.review_receipt.v0"),
|
||||
|
|
|
|||
|
|
@ -654,6 +654,13 @@ pub use mind_gateway::MindGatewayDecision;
|
|||
pub use mind_gateway::MindGatewayReview;
|
||||
pub use mind_gateway::MindInterpreterPromptInput;
|
||||
pub use mind_gateway::MindStateCommitReceipt;
|
||||
pub use mind_gateway::REPO_WORK_HANDS_GRANT_SCHEMA_VERSION;
|
||||
pub use mind_gateway::REPO_WORK_HANDS_GRANT_TYPE;
|
||||
pub use mind_gateway::REPO_WORK_PLAN_ADOPTION_REVIEW_SCHEMA_VERSION;
|
||||
pub use mind_gateway::REPO_WORK_PLAN_ADOPTION_REVIEW_TYPE;
|
||||
pub use mind_gateway::RepoWorkHandsGrant;
|
||||
pub use mind_gateway::RepoWorkPlanAdoptionDecision;
|
||||
pub use mind_gateway::RepoWorkPlanAdoptionReview;
|
||||
pub use mind_gateway::build_mind_interpreter_prompt;
|
||||
pub use mind_gateway::default_mind_cultnet_contracts;
|
||||
pub use mind_gateway::mind_review_allows_state;
|
||||
|
|
@ -787,6 +794,7 @@ pub use runtime_spine::advance_repo_work_modeling_route;
|
|||
pub use runtime_spine::append_runtime_event;
|
||||
pub use runtime_spine::clear_epiphany_job_binding_backend;
|
||||
pub use runtime_spine::commit_initial_repo_work_modeling_route;
|
||||
pub use runtime_spine::commit_repo_work_hands_grant;
|
||||
pub use runtime_spine::commit_repo_work_map_admission;
|
||||
pub use runtime_spine::complete_runtime_job;
|
||||
pub use runtime_spine::create_runtime_job;
|
||||
|
|
@ -810,6 +818,7 @@ pub use runtime_spine::put_mind_gateway_review;
|
|||
pub use runtime_spine::put_mind_state_commit_receipt;
|
||||
pub use runtime_spine::put_repo_work_modeling_finding;
|
||||
pub use runtime_spine::put_repo_work_modeling_request;
|
||||
pub use runtime_spine::put_repo_work_plan_adoption_review;
|
||||
pub use runtime_spine::put_runtime_reorient_worker_result;
|
||||
pub use runtime_spine::put_runtime_role_worker_result;
|
||||
pub use runtime_spine::put_soul_verdict_receipt;
|
||||
|
|
@ -831,10 +840,12 @@ pub use runtime_spine::runtime_mind_gateway_review;
|
|||
pub use runtime_spine::runtime_mind_state_commit_receipt;
|
||||
pub use runtime_spine::runtime_registered_document_types;
|
||||
pub use runtime_spine::runtime_reorient_worker_result;
|
||||
pub use runtime_spine::runtime_repo_work_hands_grant;
|
||||
pub use runtime_spine::runtime_repo_work_map_entry;
|
||||
pub use runtime_spine::runtime_repo_work_modeling_finding;
|
||||
pub use runtime_spine::runtime_repo_work_modeling_request;
|
||||
pub use runtime_spine::runtime_repo_work_modeling_route;
|
||||
pub use runtime_spine::runtime_repo_work_plan_adoption_review;
|
||||
pub use runtime_spine::runtime_role_worker_result;
|
||||
pub use runtime_spine::runtime_schema_catalog_response;
|
||||
pub use runtime_spine::runtime_soul_verdict_receipt;
|
||||
|
|
@ -843,6 +854,7 @@ pub use runtime_spine::runtime_spine_status;
|
|||
pub use runtime_spine::runtime_substrate_gate_repo_access_grant_receipt;
|
||||
pub use runtime_spine::runtime_tool_invocation_statuses;
|
||||
pub use runtime_spine::runtime_worker_launch_request;
|
||||
pub use runtime_spine::validate_hands_action_authority;
|
||||
pub use runtime_spine::write_runtime_hello_frame;
|
||||
pub use runtime_spine::write_runtime_schema_catalog_json;
|
||||
pub use soul_gateway::*;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,118 @@ pub const MIND_STATE_REJECTION_RECEIPT_SCHEMA_VERSION: &str =
|
|||
"epiphany.mind.state_rejection_receipt.v0";
|
||||
pub const MIND_VERSE_ADOPTION_RECEIPT_SCHEMA_VERSION: &str =
|
||||
"epiphany.mind.verse_adoption_receipt.v0";
|
||||
pub const REPO_WORK_PLAN_ADOPTION_REVIEW_TYPE: &str = "epiphany.repo_work.plan_adoption_review";
|
||||
pub const REPO_WORK_PLAN_ADOPTION_REVIEW_SCHEMA_VERSION: &str =
|
||||
"epiphany.repo_work_plan_adoption_review.v0";
|
||||
pub const REPO_WORK_HANDS_GRANT_TYPE: &str = "epiphany.repo_work.hands_grant";
|
||||
pub const REPO_WORK_HANDS_GRANT_SCHEMA_VERSION: &str = "epiphany.repo_work_hands_grant.v0";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum RepoWorkPlanAdoptionDecision {
|
||||
Adopt,
|
||||
Refuse,
|
||||
Hold,
|
||||
}
|
||||
|
||||
/// Mind-authored review of one immutable repo-work plan and its complete authority chain.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "epiphany.repo_work.plan_adoption_review",
|
||||
schema = "RepoWorkPlanAdoptionReview"
|
||||
)]
|
||||
pub struct RepoWorkPlanAdoptionReview {
|
||||
#[cultcache(key = 0)]
|
||||
pub schema_version: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub review_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub decision: RepoWorkPlanAdoptionDecision,
|
||||
#[cultcache(key = 3)]
|
||||
pub workspace_identity: String,
|
||||
#[cultcache(key = 4)]
|
||||
pub item: String,
|
||||
#[cultcache(key = 5)]
|
||||
pub plan_schema_version: String,
|
||||
#[cultcache(key = 6)]
|
||||
pub plan_id: String,
|
||||
#[cultcache(key = 7)]
|
||||
pub plan_sha256: String,
|
||||
#[cultcache(key = 8)]
|
||||
pub run_receipt_sha256: String,
|
||||
#[cultcache(key = 9)]
|
||||
pub plan_receipt_path: String,
|
||||
#[cultcache(key = 10)]
|
||||
pub run_receipt_path: String,
|
||||
#[cultcache(key = 11)]
|
||||
pub hands_intent_id: String,
|
||||
#[cultcache(key = 12)]
|
||||
pub queued_hands_review_id: String,
|
||||
#[cultcache(key = 13)]
|
||||
pub substrate_grant_receipt_id: String,
|
||||
#[cultcache(key = 14)]
|
||||
pub action_id: String,
|
||||
#[cultcache(key = 15)]
|
||||
pub action_command: String,
|
||||
#[cultcache(key = 16)]
|
||||
pub action_commit_message: String,
|
||||
#[cultcache(key = 17)]
|
||||
pub changed_paths: Vec<String>,
|
||||
#[cultcache(key = 18)]
|
||||
pub reviewed_at: String,
|
||||
#[cultcache(key = 19)]
|
||||
pub private_state_exposed: bool,
|
||||
}
|
||||
|
||||
/// The only capability that converts an accepted Mind review into Hands authority.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(type = "epiphany.repo_work.hands_grant", schema = "RepoWorkHandsGrant")]
|
||||
pub struct RepoWorkHandsGrant {
|
||||
#[cultcache(key = 0)]
|
||||
pub schema_version: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub grant_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub adoption_review_id: String,
|
||||
#[cultcache(key = 3)]
|
||||
pub adoption_review_sha256: String,
|
||||
#[cultcache(key = 4)]
|
||||
pub workspace_identity: String,
|
||||
#[cultcache(key = 5)]
|
||||
pub item: String,
|
||||
#[cultcache(key = 6)]
|
||||
pub plan_id: String,
|
||||
#[cultcache(key = 7)]
|
||||
pub plan_sha256: String,
|
||||
#[cultcache(key = 8)]
|
||||
pub run_receipt_sha256: String,
|
||||
#[cultcache(key = 9)]
|
||||
pub plan_receipt_path: String,
|
||||
#[cultcache(key = 10)]
|
||||
pub run_receipt_path: String,
|
||||
#[cultcache(key = 11)]
|
||||
pub hands_intent_id: String,
|
||||
#[cultcache(key = 12)]
|
||||
pub queued_hands_review_id: String,
|
||||
#[cultcache(key = 13)]
|
||||
pub approved_hands_review_id: String,
|
||||
#[cultcache(key = 14)]
|
||||
pub substrate_grant_receipt_id: String,
|
||||
#[cultcache(key = 15)]
|
||||
pub action_id: String,
|
||||
#[cultcache(key = 16)]
|
||||
pub action_command: String,
|
||||
#[cultcache(key = 17)]
|
||||
pub action_commit_message: String,
|
||||
#[cultcache(key = 18)]
|
||||
pub allowed_operations: Vec<String>,
|
||||
#[cultcache(key = 19)]
|
||||
pub changed_paths: Vec<String>,
|
||||
#[cultcache(key = 20)]
|
||||
pub granted_at: String,
|
||||
#[cultcache(key = 21)]
|
||||
pub private_state_exposed: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ use crate::mind_gateway::MIND_VERSE_ADOPTION_RECEIPT_TYPE;
|
|||
use crate::mind_gateway::MindGatewayDecision;
|
||||
use crate::mind_gateway::MindGatewayReview;
|
||||
use crate::mind_gateway::MindStateCommitReceipt;
|
||||
use crate::mind_gateway::RepoWorkHandsGrant;
|
||||
use crate::mind_gateway::RepoWorkPlanAdoptionDecision;
|
||||
use crate::mind_gateway::RepoWorkPlanAdoptionReview;
|
||||
use crate::modeling_gateway::REPO_WORK_MAP_ENTRY_SCHEMA_VERSION;
|
||||
use crate::modeling_gateway::REPO_WORK_MAP_ENTRY_TYPE;
|
||||
use crate::modeling_gateway::REPO_WORK_MODELING_FINDING_SCHEMA_VERSION;
|
||||
|
|
@ -106,6 +109,8 @@ use epiphany_tool_adapter::EpiphanyToolInvocationIntent;
|
|||
use epiphany_tool_adapter::EpiphanyToolInvocationReceipt;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use sha2::Digest;
|
||||
use sha2::Sha256;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs;
|
||||
|
|
@ -734,6 +739,8 @@ pub fn runtime_spine_cache(store_path: impl AsRef<Path>) -> Result<CultCache> {
|
|||
cache.register_entry_type::<EpiphanyCoordinatorRunReceipt>()?;
|
||||
cache.register_entry_type::<MindGatewayReview>()?;
|
||||
cache.register_entry_type::<MindStateCommitReceipt>()?;
|
||||
cache.register_entry_type::<RepoWorkPlanAdoptionReview>()?;
|
||||
cache.register_entry_type::<RepoWorkHandsGrant>()?;
|
||||
cache.register_entry_type::<RepoWorkModelingFinding>()?;
|
||||
cache.register_entry_type::<RepoWorkModelingRequest>()?;
|
||||
cache.register_entry_type::<RepoWorkModelingRoute>()?;
|
||||
|
|
@ -1508,6 +1515,204 @@ pub fn runtime_hands_action_review(
|
|||
cache.get::<HandsActionReview>(review_id)
|
||||
}
|
||||
|
||||
pub fn put_repo_work_plan_adoption_review(
|
||||
store_path: impl AsRef<Path>,
|
||||
review: &RepoWorkPlanAdoptionReview,
|
||||
) -> Result<()> {
|
||||
validate_repo_work_plan_adoption_review(review)?;
|
||||
let mut cache = runtime_spine_cache(store_path)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
require_identity(&cache)?;
|
||||
if let Some(existing) = cache.get::<RepoWorkPlanAdoptionReview>(&review.review_id)? {
|
||||
if existing == *review {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(anyhow!(
|
||||
"repo-work plan adoption review ids are immutable; {} already names different bytes",
|
||||
review.review_id
|
||||
));
|
||||
}
|
||||
cache.put(&review.review_id, review)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn runtime_repo_work_plan_adoption_review(
|
||||
store_path: impl AsRef<Path>,
|
||||
review_id: &str,
|
||||
) -> Result<Option<RepoWorkPlanAdoptionReview>> {
|
||||
validate_non_empty(review_id, "repo-work plan adoption review id")?;
|
||||
let mut cache = runtime_spine_cache(store_path)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
cache.get::<RepoWorkPlanAdoptionReview>(review_id)
|
||||
}
|
||||
|
||||
pub fn runtime_repo_work_hands_grant(
|
||||
store_path: impl AsRef<Path>,
|
||||
grant_id: &str,
|
||||
) -> Result<Option<RepoWorkHandsGrant>> {
|
||||
validate_non_empty(grant_id, "repo-work Hands grant id")?;
|
||||
let mut cache = runtime_spine_cache(store_path)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
cache.get::<RepoWorkHandsGrant>(grant_id)
|
||||
}
|
||||
|
||||
/// Atomically publishes the capability and its approved generic Hands review.
|
||||
/// Neither document is observable unless both pass validation and the backing-store swap succeeds.
|
||||
pub fn commit_repo_work_hands_grant(
|
||||
store_path: impl AsRef<Path>,
|
||||
grant: &RepoWorkHandsGrant,
|
||||
approved_review: &HandsActionReview,
|
||||
) -> Result<()> {
|
||||
let mut cache = runtime_spine_cache(store_path)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
require_identity(&cache)?;
|
||||
let adoption = cache
|
||||
.get::<RepoWorkPlanAdoptionReview>(&grant.adoption_review_id)?
|
||||
.ok_or_else(|| {
|
||||
anyhow!("repo-work Hands grant requires its persisted Mind adoption review")
|
||||
})?;
|
||||
validate_repo_work_plan_adoption_review(&adoption)?;
|
||||
let persisted_adoption_sha256 = format!("{:x}", Sha256::digest(serde_json::to_vec(&adoption)?));
|
||||
if adoption.decision != RepoWorkPlanAdoptionDecision::Adopt
|
||||
|| grant.schema_version != crate::mind_gateway::REPO_WORK_HANDS_GRANT_SCHEMA_VERSION
|
||||
|| grant.private_state_exposed
|
||||
|| !valid_lower_sha256(&grant.adoption_review_sha256)
|
||||
|| grant.adoption_review_sha256 != persisted_adoption_sha256
|
||||
|| !valid_lower_sha256(&grant.plan_sha256)
|
||||
|| !valid_lower_sha256(&grant.run_receipt_sha256)
|
||||
|| chrono::DateTime::parse_from_rfc3339(&grant.granted_at).is_err()
|
||||
|| !safe_sorted_unique_paths(&grant.changed_paths)
|
||||
|| grant.adoption_review_id != adoption.review_id
|
||||
|| grant.workspace_identity != adoption.workspace_identity
|
||||
|| grant.item != adoption.item
|
||||
|| grant.plan_id != adoption.plan_id
|
||||
|| grant.plan_sha256 != adoption.plan_sha256
|
||||
|| grant.run_receipt_sha256 != adoption.run_receipt_sha256
|
||||
|| grant.plan_receipt_path != adoption.plan_receipt_path
|
||||
|| grant.run_receipt_path != adoption.run_receipt_path
|
||||
|| grant.hands_intent_id != adoption.hands_intent_id
|
||||
|| grant.queued_hands_review_id != adoption.queued_hands_review_id
|
||||
|| grant.substrate_grant_receipt_id != adoption.substrate_grant_receipt_id
|
||||
|| grant.action_id != adoption.action_id
|
||||
|| grant.action_command != adoption.action_command
|
||||
|| grant.action_commit_message != adoption.action_commit_message
|
||||
|| grant.changed_paths != adoption.changed_paths
|
||||
|| approved_review.review_id != grant.approved_hands_review_id
|
||||
|| approved_review.intent_id != grant.hands_intent_id
|
||||
|| approved_review.decision != "approved"
|
||||
|| approved_review.allowed_operations != grant.allowed_operations
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"repo-work Hands grant does not exactly bind its accepted Mind review and approved Hands review"
|
||||
));
|
||||
}
|
||||
let intent = cache
|
||||
.get::<HandsActionIntent>(&grant.hands_intent_id)?
|
||||
.ok_or_else(|| anyhow!("repo-work Hands grant requires its persisted Hands intent"))?;
|
||||
let queued = cache
|
||||
.get::<HandsActionReview>(&grant.queued_hands_review_id)?
|
||||
.ok_or_else(|| anyhow!("repo-work Hands grant requires its queued Hands review"))?;
|
||||
if intent.intent_id != queued.intent_id
|
||||
|| queued.decision != "queued-for-adoption"
|
||||
|| intent.substrate_gate_grant_receipt_id != grant.substrate_grant_receipt_id
|
||||
|| intent.requested_paths != grant.changed_paths
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"repo-work Hands grant authority chain is inconsistent"
|
||||
));
|
||||
}
|
||||
let existing_grant = cache.get::<RepoWorkHandsGrant>(&grant.grant_id)?;
|
||||
let existing_review = cache.get::<HandsActionReview>(&approved_review.review_id)?;
|
||||
match (existing_grant, existing_review) {
|
||||
(None, None) => {}
|
||||
(Some(existing_grant), Some(existing_review))
|
||||
if existing_grant == *grant && existing_review == *approved_review =>
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"repo-work Hands grant and approved review ids are an immutable pair"
|
||||
));
|
||||
}
|
||||
}
|
||||
let (grant_entry, _) = cache.prepare_entry(&grant.grant_id, grant)?;
|
||||
let (review_entry, _) = cache.prepare_entry(&approved_review.review_id, approved_review)?;
|
||||
cache.put_prepared_batch(vec![grant_entry, review_entry])
|
||||
}
|
||||
|
||||
fn validate_repo_work_plan_adoption_review(review: &RepoWorkPlanAdoptionReview) -> Result<()> {
|
||||
validate_non_empty(&review.review_id, "repo-work plan adoption review id")?;
|
||||
validate_non_empty(&review.workspace_identity, "repo-work workspace identity")?;
|
||||
validate_non_empty(&review.item, "repo-work item")?;
|
||||
validate_non_empty(&review.plan_id, "repo-work plan id")?;
|
||||
validate_non_empty(&review.plan_sha256, "repo-work plan SHA-256")?;
|
||||
validate_non_empty(&review.run_receipt_sha256, "repo-work run receipt SHA-256")?;
|
||||
validate_non_empty(&review.plan_receipt_path, "repo-work plan receipt path")?;
|
||||
validate_non_empty(&review.run_receipt_path, "repo-work run receipt path")?;
|
||||
validate_non_empty(&review.hands_intent_id, "repo-work Hands intent")?;
|
||||
validate_non_empty(
|
||||
&review.queued_hands_review_id,
|
||||
"repo-work queued Hands review",
|
||||
)?;
|
||||
validate_non_empty(
|
||||
&review.substrate_grant_receipt_id,
|
||||
"repo-work Substrate grant",
|
||||
)?;
|
||||
validate_non_empty(&review.action_id, "repo-work action id")?;
|
||||
validate_non_empty(&review.action_command, "repo-work action command")?;
|
||||
validate_non_empty(
|
||||
&review.action_commit_message,
|
||||
"repo-work action commit message",
|
||||
)?;
|
||||
validate_non_empty(&review.reviewed_at, "repo-work adoption review timestamp")?;
|
||||
let valid_sha256 =
|
||||
|value: &str| value.len() == 64 && value.bytes().all(|byte| byte.is_ascii_hexdigit());
|
||||
let normalized_paths = review
|
||||
.changed_paths
|
||||
.iter()
|
||||
.map(|path| path.replace('\\', "/"))
|
||||
.collect::<Vec<_>>();
|
||||
let mut sorted_paths = normalized_paths.clone();
|
||||
sorted_paths.sort();
|
||||
sorted_paths.dedup();
|
||||
if review.schema_version != crate::mind_gateway::REPO_WORK_PLAN_ADOPTION_REVIEW_SCHEMA_VERSION
|
||||
|| review.plan_schema_version != "epiphany.repo_work_action_plan_receipt.v0"
|
||||
|| !valid_sha256(&review.plan_sha256)
|
||||
|| !valid_sha256(&review.run_receipt_sha256)
|
||||
|| review.private_state_exposed
|
||||
|| review.changed_paths.is_empty()
|
||||
|| !valid_lower_sha256(&review.plan_sha256)
|
||||
|| !valid_lower_sha256(&review.run_receipt_sha256)
|
||||
|| !Path::new(&review.plan_receipt_path).is_absolute()
|
||||
|| !Path::new(&review.run_receipt_path).is_absolute()
|
||||
|| chrono::DateTime::parse_from_rfc3339(&review.reviewed_at).is_err()
|
||||
|| !safe_sorted_unique_paths(&review.changed_paths)
|
||||
|| review.changed_paths != sorted_paths
|
||||
{
|
||||
return Err(anyhow!("invalid repo-work plan adoption review contract"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn valid_lower_sha256(value: &str) -> bool {
|
||||
value.len() == 64
|
||||
&& value
|
||||
.bytes()
|
||||
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
|
||||
}
|
||||
|
||||
fn safe_sorted_unique_paths(paths: &[String]) -> bool {
|
||||
paths.windows(2).all(|pair| pair[0] < pair[1])
|
||||
&& paths.iter().all(|path| {
|
||||
!path.is_empty()
|
||||
&& !Path::new(path).is_absolute()
|
||||
&& !Path::new(path)
|
||||
.components()
|
||||
.any(|part| matches!(part, std::path::Component::ParentDir))
|
||||
})
|
||||
}
|
||||
|
||||
fn validate_hands_consequence_grant(
|
||||
store_path: &Path,
|
||||
intent_id: &str,
|
||||
|
|
@ -1561,6 +1766,30 @@ fn validate_hands_consequence_grant(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Revalidates the persisted Hands/Substrate authority chain before an actuator
|
||||
/// performs a consequence. Receipt writers call the same primitive again after
|
||||
/// the consequence; this preflight prevents a stale or substituted grant from
|
||||
/// authorizing the consequence in the first place.
|
||||
pub fn validate_hands_action_authority(
|
||||
store_path: impl AsRef<Path>,
|
||||
intent_id: &str,
|
||||
review_id: &str,
|
||||
runtime_job_id: &str,
|
||||
operation: &str,
|
||||
changed_paths: &[String],
|
||||
stated_grant_id: &str,
|
||||
) -> Result<()> {
|
||||
validate_hands_consequence_grant(
|
||||
store_path.as_ref(),
|
||||
intent_id,
|
||||
review_id,
|
||||
runtime_job_id,
|
||||
operation,
|
||||
changed_paths,
|
||||
Some(stated_grant_id),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn put_hands_patch_receipt(
|
||||
store_path: impl AsRef<Path>,
|
||||
receipt: &HandsPatchReceipt,
|
||||
|
|
@ -4052,6 +4281,243 @@ mod tests {
|
|||
use cultnet_rs::decode_cultnet_message_from_slice;
|
||||
use tempfile::tempdir;
|
||||
|
||||
struct RepoWorkAdoptionFixture {
|
||||
store: PathBuf,
|
||||
review: RepoWorkPlanAdoptionReview,
|
||||
grant: RepoWorkHandsGrant,
|
||||
approved: HandsActionReview,
|
||||
}
|
||||
|
||||
fn repo_work_adoption_fixture() -> Result<(tempfile::TempDir, RepoWorkAdoptionFixture)> {
|
||||
let temp = tempdir()?;
|
||||
let store = temp.path().join("runtime.msgpack");
|
||||
initialize_runtime_spine(
|
||||
&store,
|
||||
RuntimeSpineInitOptions {
|
||||
runtime_id: "epiphany-adoption-test".to_string(),
|
||||
display_name: "Epiphany Adoption Test".to_string(),
|
||||
created_at: "2026-07-13T00:00:00Z".to_string(),
|
||||
},
|
||||
)?;
|
||||
let paths = vec!["src/lib.rs".to_string()];
|
||||
let substrate = crate::substrate_gate_coordinator_implementation_grant(
|
||||
"substrate-adoption-1".to_string(),
|
||||
"hands-job-adoption-1".to_string(),
|
||||
paths.clone(),
|
||||
"2026-07-13T00:00:01Z".to_string(),
|
||||
);
|
||||
put_substrate_gate_repo_access_grant_receipt(&store, &substrate)?;
|
||||
let intent = HandsActionIntent {
|
||||
schema_version: crate::HANDS_ACTION_INTENT_SCHEMA_VERSION.to_string(),
|
||||
intent_id: "hands-intent-adoption-1".to_string(),
|
||||
runtime_job_id: "hands-job-adoption-1".to_string(),
|
||||
binding_id: "implementation-worker".to_string(),
|
||||
role: "epiphany-hands".to_string(),
|
||||
authority_scope: "epiphany.role.implementation".to_string(),
|
||||
requested_action: "runAcceptedWorkItem".to_string(),
|
||||
requested_paths: paths.clone(),
|
||||
substrate_gate_grant_receipt_id: substrate.receipt_id.clone(),
|
||||
requested_at: "2026-07-13T00:00:02Z".to_string(),
|
||||
contract: "Mind adoption test intent.".to_string(),
|
||||
};
|
||||
put_hands_action_intent(&store, &intent)?;
|
||||
let queued = crate::hands_action_review_for_intent(
|
||||
"hands-review-queued-adoption-1".to_string(),
|
||||
&intent,
|
||||
"queued-for-adoption".to_string(),
|
||||
vec![
|
||||
"patch".to_string(),
|
||||
"command".to_string(),
|
||||
"commit".to_string(),
|
||||
],
|
||||
vec!["Mind decision required.".to_string()],
|
||||
"2026-07-13T00:00:03Z".to_string(),
|
||||
);
|
||||
put_hands_action_review(&store, &queued)?;
|
||||
let review = RepoWorkPlanAdoptionReview {
|
||||
schema_version: crate::mind_gateway::REPO_WORK_PLAN_ADOPTION_REVIEW_SCHEMA_VERSION
|
||||
.to_string(),
|
||||
review_id: "mind-adoption-1".to_string(),
|
||||
decision: crate::mind_gateway::RepoWorkPlanAdoptionDecision::Adopt,
|
||||
workspace_identity: "workspace-1".to_string(),
|
||||
item: "item-1".to_string(),
|
||||
plan_schema_version: "epiphany.repo_work_action_plan_receipt.v0".to_string(),
|
||||
plan_id: "plan-1".to_string(),
|
||||
plan_sha256: "a".repeat(64),
|
||||
run_receipt_sha256: "b".repeat(64),
|
||||
plan_receipt_path: temp.path().join("plan.json").display().to_string(),
|
||||
run_receipt_path: temp.path().join("run.json").display().to_string(),
|
||||
hands_intent_id: intent.intent_id.clone(),
|
||||
queued_hands_review_id: queued.review_id.clone(),
|
||||
substrate_grant_receipt_id: substrate.receipt_id.clone(),
|
||||
action_id: "action-1".to_string(),
|
||||
action_command: "cargo test --lib".to_string(),
|
||||
action_commit_message: "Test immutable adoption".to_string(),
|
||||
changed_paths: paths.clone(),
|
||||
reviewed_at: "2026-07-13T00:00:04Z".to_string(),
|
||||
private_state_exposed: false,
|
||||
};
|
||||
put_repo_work_plan_adoption_review(&store, &review)?;
|
||||
let approved = crate::hands_action_review_for_intent(
|
||||
"hands-review-approved-adoption-1".to_string(),
|
||||
&intent,
|
||||
"approved".to_string(),
|
||||
vec![
|
||||
"patch".to_string(),
|
||||
"command".to_string(),
|
||||
"commit".to_string(),
|
||||
],
|
||||
vec!["Bound to immutable Mind review.".to_string()],
|
||||
"2026-07-13T00:00:05Z".to_string(),
|
||||
);
|
||||
let grant = RepoWorkHandsGrant {
|
||||
schema_version: crate::mind_gateway::REPO_WORK_HANDS_GRANT_SCHEMA_VERSION.to_string(),
|
||||
grant_id: "hands-grant-adoption-1".to_string(),
|
||||
adoption_review_id: review.review_id.clone(),
|
||||
adoption_review_sha256: format!("{:x}", Sha256::digest(serde_json::to_vec(&review)?)),
|
||||
workspace_identity: review.workspace_identity.clone(),
|
||||
item: review.item.clone(),
|
||||
plan_id: review.plan_id.clone(),
|
||||
plan_sha256: review.plan_sha256.clone(),
|
||||
run_receipt_sha256: review.run_receipt_sha256.clone(),
|
||||
plan_receipt_path: review.plan_receipt_path.clone(),
|
||||
run_receipt_path: review.run_receipt_path.clone(),
|
||||
hands_intent_id: review.hands_intent_id.clone(),
|
||||
queued_hands_review_id: review.queued_hands_review_id.clone(),
|
||||
approved_hands_review_id: approved.review_id.clone(),
|
||||
substrate_grant_receipt_id: review.substrate_grant_receipt_id.clone(),
|
||||
action_id: review.action_id.clone(),
|
||||
action_command: review.action_command.clone(),
|
||||
action_commit_message: review.action_commit_message.clone(),
|
||||
allowed_operations: approved.allowed_operations.clone(),
|
||||
changed_paths: review.changed_paths.clone(),
|
||||
granted_at: "2026-07-13T00:00:05Z".to_string(),
|
||||
private_state_exposed: false,
|
||||
};
|
||||
Ok((
|
||||
temp,
|
||||
RepoWorkAdoptionFixture {
|
||||
store,
|
||||
review,
|
||||
grant,
|
||||
approved,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repo_work_mind_review_is_create_once_and_idempotent() -> Result<()> {
|
||||
let (_temp, fixture) = repo_work_adoption_fixture()?;
|
||||
put_repo_work_plan_adoption_review(&fixture.store, &fixture.review)?;
|
||||
let mut counterfeit = fixture.review.clone();
|
||||
counterfeit.action_command = "cargo test --all".to_string();
|
||||
assert!(put_repo_work_plan_adoption_review(&fixture.store, &counterfeit).is_err());
|
||||
assert_eq!(
|
||||
runtime_repo_work_plan_adoption_review(&fixture.store, &fixture.review.review_id)?,
|
||||
Some(fixture.review)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repo_work_refuse_and_hold_cannot_mint_hands_authority() -> Result<()> {
|
||||
for decision in [
|
||||
crate::mind_gateway::RepoWorkPlanAdoptionDecision::Refuse,
|
||||
crate::mind_gateway::RepoWorkPlanAdoptionDecision::Hold,
|
||||
] {
|
||||
let (_temp, mut fixture) = repo_work_adoption_fixture()?;
|
||||
fixture.review.review_id = format!("mind-{decision:?}").to_lowercase();
|
||||
fixture.review.decision = decision;
|
||||
fixture.grant.adoption_review_id = fixture.review.review_id.clone();
|
||||
put_repo_work_plan_adoption_review(&fixture.store, &fixture.review)?;
|
||||
assert!(
|
||||
commit_repo_work_hands_grant(&fixture.store, &fixture.grant, &fixture.approved)
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
runtime_repo_work_hands_grant(&fixture.store, &fixture.grant.grant_id)?.is_none()
|
||||
);
|
||||
assert!(
|
||||
runtime_hands_action_review(&fixture.store, &fixture.approved.review_id)?.is_none()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repo_work_swapped_binding_fails_atomically() -> Result<()> {
|
||||
let (_temp, mut fixture) = repo_work_adoption_fixture()?;
|
||||
fixture.grant.plan_receipt_path = fixture.review.run_receipt_path.clone();
|
||||
assert!(
|
||||
commit_repo_work_hands_grant(&fixture.store, &fixture.grant, &fixture.approved)
|
||||
.is_err()
|
||||
);
|
||||
assert!(runtime_repo_work_hands_grant(&fixture.store, &fixture.grant.grant_id)?.is_none());
|
||||
assert!(
|
||||
runtime_hands_action_review(&fixture.store, &fixture.approved.review_id)?.is_none()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repo_work_counterfeit_review_digest_fails_atomically() -> Result<()> {
|
||||
let (_temp, mut fixture) = repo_work_adoption_fixture()?;
|
||||
fixture.grant.adoption_review_sha256 = "c".repeat(64);
|
||||
assert!(
|
||||
commit_repo_work_hands_grant(&fixture.store, &fixture.grant, &fixture.approved)
|
||||
.is_err()
|
||||
);
|
||||
assert!(runtime_repo_work_hands_grant(&fixture.store, &fixture.grant.grant_id)?.is_none());
|
||||
assert!(
|
||||
runtime_hands_action_review(&fixture.store, &fixture.approved.review_id)?.is_none()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repo_work_exact_adoption_chain_commits_grant_and_review_together() -> Result<()> {
|
||||
let (_temp, fixture) = repo_work_adoption_fixture()?;
|
||||
commit_repo_work_hands_grant(&fixture.store, &fixture.grant, &fixture.approved)?;
|
||||
assert_eq!(
|
||||
runtime_repo_work_hands_grant(&fixture.store, &fixture.grant.grant_id)?,
|
||||
Some(fixture.grant)
|
||||
);
|
||||
assert_eq!(
|
||||
runtime_hands_action_review(&fixture.store, &fixture.approved.review_id)?,
|
||||
Some(fixture.approved)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repo_work_grant_and_approved_review_are_create_once_as_a_pair() -> Result<()> {
|
||||
let (_temp, fixture) = repo_work_adoption_fixture()?;
|
||||
commit_repo_work_hands_grant(&fixture.store, &fixture.grant, &fixture.approved)?;
|
||||
commit_repo_work_hands_grant(&fixture.store, &fixture.grant, &fixture.approved)?;
|
||||
|
||||
let mut counterfeit_grant = fixture.grant.clone();
|
||||
counterfeit_grant.action_command = "cargo test --all".to_string();
|
||||
assert!(
|
||||
commit_repo_work_hands_grant(&fixture.store, &counterfeit_grant, &fixture.approved)
|
||||
.is_err()
|
||||
);
|
||||
let mut counterfeit_review = fixture.approved.clone();
|
||||
counterfeit_review.reasons = vec!["replacement opinion".to_string()];
|
||||
assert!(
|
||||
commit_repo_work_hands_grant(&fixture.store, &fixture.grant, &counterfeit_review)
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(
|
||||
runtime_repo_work_hands_grant(&fixture.store, &fixture.grant.grant_id)?,
|
||||
Some(fixture.grant)
|
||||
);
|
||||
assert_eq!(
|
||||
runtime_hands_action_review(&fixture.store, &fixture.approved.review_id)?,
|
||||
Some(fixture.approved)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_spine_initializes_sessions_events_and_status() -> Result<()> {
|
||||
let temp = tempdir()?;
|
||||
|
|
|
|||
|
|
@ -63,10 +63,23 @@ validator; missing/changed node, edge, or link anchors stale their summaries.
|
|||
There is still no production semantic index for graph claims, so Modeling index
|
||||
availability is honestly unavailable rather than inferred from code search.
|
||||
|
||||
Next P0 cut: `epiphany-work run_adopt` currently synthesizes Mind ownership and
|
||||
an approved Hands review from local JSON predicates. Route adoption through the
|
||||
existing typed Mind gateway, immutably bound to the exact plan, and delete the
|
||||
scheduler's ability to author Mind/Hands consequence.
|
||||
The repo-work adoption authority rebuild is now implemented. `run_adopt`
|
||||
consumes an immutable typed `RepoWorkPlanAdoptionReview` bound to exact
|
||||
workspace, plan/run paths and SHA-256 bytes, action, command, commit message,
|
||||
changed paths, queued Hands review, and Substrate grant. It atomically creates
|
||||
an immutable `RepoWorkHandsGrant` plus approved Hands review; Refuse/Hold,
|
||||
counterfeit digests, swapped paths, and same-ID replacement are rejected.
|
||||
`run_tick` can only report `awaiting-mind-review`. `run_execute` rereads the
|
||||
whole chain and revalidates persisted Substrate authority before PowerShell.
|
||||
Soul closure now reloads typed runtime truth and cross-binds it to the execute
|
||||
receipt; a swapped valid adoption chain fails. Six compiled smokes that taught
|
||||
the deleted caller-writable `mindAdoptionDecision` model were removed.
|
||||
|
||||
Next architectural cut: finish the Aetheria lesson rather than polishing this
|
||||
gate. Modeling needs a production semantic index over typed graph claims and a
|
||||
durable frontier projection that automatically targets later Eyes/Hands
|
||||
prompts. Memory-graph freshness is substrate; the living migration map is the
|
||||
organ.
|
||||
|
||||
Epiphany is in an authority-provenance purification pass. The live question is not whether a command can produce a plausible document; it is whether the subsystem writing that document owns the fact it asserts.
|
||||
|
||||
|
|
|
|||
20
schemas/cultnet/epiphany.repo_work_hands_grant.schema.json
Normal file
20
schemas/cultnet/epiphany.repo_work_hands_grant.schema.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.repo_work_hands_grant.schema.json",
|
||||
"title": "Repo Work Hands Grant",
|
||||
"type": "object", "additionalProperties": false,
|
||||
"required": ["schemaVersion","grantId","adoptionReviewId","adoptionReviewSha256","workspaceIdentity","item","planId","planSha256","runReceiptSha256","planReceiptPath","runReceiptPath","handsIntentId","queuedHandsReviewId","approvedHandsReviewId","substrateGrantReceiptId","actionId","actionCommand","actionCommitMessage","allowedOperations","changedPaths","grantedAt","privateStateExposed"],
|
||||
"properties": {
|
||||
"schemaVersion":{"const":"epiphany.repo_work_hands_grant.v0"}, "grantId":{"type":"string","minLength":1},
|
||||
"adoptionReviewId":{"type":"string","minLength":1}, "adoptionReviewSha256":{"type":"string","pattern":"^[0-9a-f]{64}$"},
|
||||
"workspaceIdentity":{"type":"string","minLength":1}, "item":{"type":"string","minLength":1}, "planId":{"type":"string","minLength":1},
|
||||
"planSha256":{"type":"string","pattern":"^[0-9a-f]{64}$"}, "runReceiptSha256":{"type":"string","pattern":"^[0-9a-f]{64}$"},
|
||||
"planReceiptPath":{"type":"string","minLength":1}, "runReceiptPath":{"type":"string","minLength":1},
|
||||
"handsIntentId":{"type":"string","minLength":1}, "queuedHandsReviewId":{"type":"string","minLength":1}, "approvedHandsReviewId":{"type":"string","minLength":1},
|
||||
"substrateGrantReceiptId":{"type":"string","minLength":1}, "actionId":{"type":"string","minLength":1},
|
||||
"actionCommand":{"type":"string","minLength":1}, "actionCommitMessage":{"type":"string","minLength":1},
|
||||
"allowedOperations":{"type":"array","minItems":1,"uniqueItems":true,"items":{"enum":["patch","command","commit"]}},
|
||||
"changedPaths":{"type":"array","minItems":1,"uniqueItems":true,"items":{"type":"string","minLength":1}},
|
||||
"grantedAt":{"type":"string","format":"date-time"}, "privateStateExposed":{"const":false}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.repo_work_plan_adoption_review.schema.json",
|
||||
"title": "Repo Work Plan Adoption Review",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["schemaVersion","reviewId","decision","workspaceIdentity","item","planSchemaVersion","planId","planSha256","runReceiptSha256","planReceiptPath","runReceiptPath","handsIntentId","queuedHandsReviewId","substrateGrantReceiptId","actionId","actionCommand","actionCommitMessage","changedPaths","reviewedAt","privateStateExposed"],
|
||||
"properties": {
|
||||
"schemaVersion": {"const":"epiphany.repo_work_plan_adoption_review.v0"},
|
||||
"reviewId":{"type":"string","minLength":1}, "decision":{"enum":["adopt","refuse","hold"]},
|
||||
"workspaceIdentity":{"type":"string","minLength":1}, "item":{"type":"string","minLength":1},
|
||||
"planSchemaVersion":{"type":"string","minLength":1}, "planId":{"type":"string","minLength":1},
|
||||
"planSha256":{"type":"string","pattern":"^[0-9a-f]{64}$"}, "runReceiptSha256":{"type":"string","pattern":"^[0-9a-f]{64}$"},
|
||||
"planReceiptPath":{"type":"string","minLength":1}, "runReceiptPath":{"type":"string","minLength":1},
|
||||
"handsIntentId":{"type":"string","minLength":1}, "queuedHandsReviewId":{"type":"string","minLength":1},
|
||||
"substrateGrantReceiptId":{"type":"string","minLength":1}, "actionId":{"type":"string","minLength":1},
|
||||
"actionCommand":{"type":"string","minLength":1}, "actionCommitMessage":{"type":"string","minLength":1},
|
||||
"changedPaths":{"type":"array","minItems":1,"uniqueItems":true,"items":{"type":"string","minLength":1}},
|
||||
"reviewedAt":{"type":"string","format":"date-time"}, "privateStateExposed":{"const":false}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,20 @@
|
|||
{
|
||||
"schemaVersion": "epiphany.cultnet_catalog.v0",
|
||||
"schemas": [
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.repo_work_plan_adoption_review.schema.json",
|
||||
"kind": "document_payload", "wireContracts": ["cultnet.schema.v0"],
|
||||
"schemaVersion": "epiphany.repo_work_plan_adoption_review.v0",
|
||||
"documentType": "epiphany.repo_work.plan_adoption_review",
|
||||
"title": "Repo Work Plan Adoption Review", "path": "epiphany.repo_work_plan_adoption_review.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.repo_work_hands_grant.schema.json",
|
||||
"kind": "document_payload", "wireContracts": ["cultnet.schema.v0"],
|
||||
"schemaVersion": "epiphany.repo_work_hands_grant.v0",
|
||||
"documentType": "epiphany.repo_work.hands_grant",
|
||||
"title": "Repo Work Hands Grant", "path": "epiphany.repo_work_hands_grant.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.runtime.identity.schema.json",
|
||||
"kind": "document_payload",
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -661,4 +661,4 @@ current_status:
|
|||
- Service lifecycle receipt history is now event-only: `load_epiphany_cultmesh_daemon_service_lifecycle_receipts` filters out the `epiphany-local/daemon-service-lifecycle-receipt/latest` mirror, while `load_latest_epiphany_cultmesh_daemon_service_lifecycle_receipt` remains the latest-state reader. Focused unit test `service_lifecycle_receipt_history_excludes_latest_mirror` proves two per-receipt writes return exactly two history records and latest points at the second. Fresh smoke `verse-query-lifecycle-history-filter-smoke` and live overview/triage `local-20260618-145632-497-efac6f0d` / `local-20260618-145632-321-867947af` prove compact swarm sights still expose service lifecycle attention plus the present elevated runbook artifact without daemon pokes or service mutation. Do not use a broad unscoped cargo test filter for this check on Windows; it fans out across bin harnesses and hit a broken pipe during this pass.
|
||||
- `epiphany-verse-query seed-compact` is the compact seed receipt for wrappers and read-only rites. The older `seed` command still prints the full local Verse context for explicit debugging, but compact wrapper modes now avoid full `seed`/final `query` readback after the combined overview proof exposed Windows debug stack overflow from full-context serialization pressure.
|
||||
last_completed_repo_swarm_cut: Huginn live-fire has crossed the first real repo Body from Persona pressure to branch-local Hands commit and Soul/Mind closure, and now has repeatable closure and redacted-public-proof mouths. On `E:\Projects\Huginn` branch `epiphany/huginn/first-awakening`, `tools/epiphany_local_run.ps1 -Mode repo-persona-intake` accepted item `huginn-inspection-readback-livefire`, Weksa/Persona intake remained sealed, and `epiphany-work derive-plan` lowered it to model-authored safe family `repo.status_section` for `README.md` only. `tools/epiphany_local_run.ps1 -Mode repo-swarm-run` advanced four bounded pulses: run-from-plan, adopt-from-plan, execute-from-plan, and close-from-execute. Huginn commit `e9b07c38fceabbdc89ea35c2156326c80c0393a2` updated only `README.md`; closure receipt `.epiphany\work\work-close-huginn-inspection-readback-livefire.json` records status `closed`, Soul verdict `passed`, Mind state commit `repo-work-close-huginn-inspection-readback-livefire-mind-commit`, safe family `repo.status_section`, path scope `README.md`, Bifrost publication gate, and `privateStateExposed=false`. Verification also ran Huginn's TypeScript compiler through concrete Node path: `C:\Program Files\nodejs\node.exe E:\Projects\Huginn\node_modules\typescript\bin\tsc -p tsconfig.json` passed. Native verifier `epiphany-repo-livefire-closure-smoke --workspace E:\Projects\Huginn --item huginn-inspection-readback-livefire --expected-path README.md --expected-family repo.status_section --expected-gate awaiting-publication --expected-blocker bifrost-publication-missing` checks closure receipt, Mind map entry, overview gate/blocker, authority seals, and `git show` changed paths, returning `status=ok`, `soulVerdict=passed`, `publicationGate=Bifrost`, and `privateStateExposed=false`. Wrapper `tools/epiphany_local_run.ps1 -Mode repo-livefire-closure -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exposes the same read-only proof through the local operator mouth; artifact `local-20260621-190156-803-2f85ba15` printed commit, path, gate, blocker, Soul verdict, Mind commit receipt, and `privateStateExposed=False` without push, merge, publication, or target mutation. New wrapper `tools/epiphany_local_run.ps1 -Mode repo-work-public-proof -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exports the redacted proof bundle and repo-local Verse index without publishing; artifact `local-20260621-190645-345-4555b1dc` wrote `.epiphany\public\proof-bundles\repo-work-public-proof-huginn-inspection-readback-livefire.json`, proof id `repo-work-public-proof-huginn-inspection-readback-livefire`, SHA-256 `857545b199f3e37eb06c2e757e5cfc201958e46667bb1f8403fd333a22620f63`, nine artifact rows, zero publication rows, and `privateStateExposed=False`. swarm overview readback `local-20260621-190701-140-b9519ec6` sees the `REPO-WORK-PUBLIC-PROOF` row and action row from Huginn's repo-local Verse with the same SHA-256 while Bifrost publication remains missing. Launcher compact modes are now named compact operator-context modes instead of read-only modes, and `bifrost-public-proof` summaries print item, target public Verse, room, proof SHA-256, review receipts, and credit receipts so publication receipts are auditable without claiming upstream sync.
|
||||
next_action: Cut repo-work Mind impersonation. `epiphany-work run_adopt` currently manufactures a Mind-owned adoption document and approved Hands review from local JSON predicates. Self/scheduler may route a plan, but only a resolved typed Mind gateway review immutably bound to the exact plan may authorize the admission primitive that creates Hands review. Delete the synthetic owner/interpreter path and prove that a structurally plausible plan without Mind receipt creates neither adoption nor Hands authority. Preserve the Aetheria lesson: memory-graph v1 now binds Modeling anatomy to state-store identity, accepted revision, and SHA-256 source bytes; launch and explicit refresh share validation; missing/changed anchors stale summaries. Modeling semantic graph indexing remains honestly unavailable until a real graph-claim indexer exists. The intentionally absent Other-world action still requires a typed intent and real owner. Keep closure's zero-substring invariant.
|
||||
next_action: Build the Aetheria-shaped Modeling organ, not another freshness badge. Repo-work Mind impersonation is cut: adoption consumes an immutable typed Mind review, atomically publishes an immutable Hands grant/review pair, execution revalidates plan/run/Substrate authority before consequence, closure cross-binds runtime truth to the execute receipt, and hostile substitution/overwrite tests pass. The next owner is a production semantic index over typed Modeling graph claims plus a durable migration frontier automatically projected into Eyes/Hands prompts. Memory-graph v1 already binds anatomy to state-store identity, accepted revision, and source SHA-256; use that as substrate, then make Modeling name current migration bodies, invalidated beliefs, dependency order, and next bounded cuts across rehydration. The intentionally absent Other-world action still requires a typed intent and real owner. Keep closure's zero-substring invariant.
|
||||
|
|
|
|||
Reference in a new issue