Add model verdict gate for repo closure

This commit is contained in:
Meta Vens 2026-06-20 15:01:05 +01:00
commit 80cf753d58
6 changed files with 570 additions and 29 deletions

View file

@ -0,0 +1,409 @@
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,
smoke_root: PathBuf,
}
impl Args {
fn parse() -> Result<Self> {
let mut root = env::current_dir().context("failed to resolve current directory")?;
let mut smoke_root = root.join(".epiphany-smoke");
let mut args = env::args().skip(1).peekable();
while let Some(arg) = args.next() {
match arg.as_str() {
"--root" => root = take_path(&mut args, "--root")?,
"--smoke-root" => smoke_root = take_path(&mut args, "--smoke-root")?,
other => return Err(anyhow!("unexpected argument {other:?}")),
}
}
Ok(Self { root, smoke_root })
}
}
fn run_smoke(args: Args) -> Result<Value> {
let root = args
.root
.canonicalize()
.with_context(|| format!("failed to resolve {}", args.root.display()))?;
let manifest = root.join("epiphany-core").join("Cargo.toml");
if !manifest.exists() {
return Err(anyhow!(
"could not find epiphany-core manifest at {}",
manifest.display()
));
}
fs::create_dir_all(&args.smoke_root)
.with_context(|| format!("failed to create {}", args.smoke_root.display()))?;
let stamp = Utc::now().format("%Y%m%d-%H%M%S").to_string();
let smoke_dir = args.smoke_root.join(format!("closure-model-gate-{stamp}"));
if smoke_dir.exists() {
fs::remove_dir_all(&smoke_dir)
.with_context(|| format!("failed to clear {}", smoke_dir.display()))?;
}
fs::create_dir_all(&smoke_dir)
.with_context(|| format!("failed to create {}", smoke_dir.display()))?;
let repo = smoke_dir.join("repo-body");
fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?;
git(["init"], &repo)?;
git(
["config", "user.email", "epiphany-smoke@example.invalid"],
&repo,
)?;
git(["config", "user.name", "Epiphany Smoke"], &repo)?;
fs::write(
repo.join("README.md"),
"# Closure Model Gate Smoke\n\nThis repository proves model-authored closure verdicts.\n",
)
.with_context(|| format!("failed to seed {}", repo.join("README.md").display()))?;
git(["add", "README.md"], &repo)?;
git(["commit", "-m", "Seed closure model smoke repo"], &repo)?;
git(["switch", "-c", "epiphany/closure-model-gate"], &repo)?;
let local_verse = repo.join(".epiphany").join("local-verse.ccmp");
cargo_json(
&manifest,
"epiphany-repo",
&[
"init",
"--workspace",
path_str(&repo)?,
"--epiphany-root",
path_str(&root)?,
"--swarm-id",
"closure-model-gate-smoke",
"--topic",
"closure-model-gate",
],
&root,
)?;
cargo_json(
&manifest,
"epiphany-swarm",
&[
"online",
"--workspace",
path_str(&repo)?,
"--epiphany-root",
path_str(&root)?,
"--runtime-id",
"closure-model-gate-smoke",
"--local-verse-store",
path_str(&local_verse)?,
],
&root,
)?;
let good = drive_item(
&manifest,
&root,
&repo,
"closure-model-pass",
"Prove a model-authored closure verdict can explicitly pass after deterministic family checks.",
"passed",
"Modeling reviewed the verified status section, path scope, family assertions, and authority seal.",
)?;
let bad = drive_item(
&manifest,
&root,
&repo,
"closure-model-block",
"Prove a model-authored closure verdict can block publication even when deterministic family checks pass.",
"needs-work",
"Modeling found the status text too thin and requires another branch-local pass before publication.",
)?;
require_eq(&good.close, &["status"], "closed")?;
require_eq(
&good.close,
&["closureReview", "modelingReview", "closureReview", "status"],
"passed",
)?;
require_bool(
&good.close,
&[
"closureReview",
"modelingReview",
"closureReview",
"gateEnforced",
],
true,
)?;
require_eq(&bad.close, &["status"], "verification-failed")?;
require_eq(&bad.close, &["soul", "verdict"], "failed")?;
require_eq(
&bad.close,
&["closureReview", "modelingReview", "closureReview", "status"],
"failed",
)?;
require_bool(
&bad.close,
&["closureReview", "modelingReview", "closureReview", "passed"],
false,
)?;
require_eq(
&bad.close,
&["closureReview", "familyAssertions", "status"],
"passed",
)?;
require_bool(
&bad.close,
&["closureReview", "sourceGrounding", "pathScopeMatched"],
true,
)?;
require_bool(&good.close, &["privateStateExposed"], false)?;
require_bool(&bad.close, &["privateStateExposed"], false)?;
let summary = json!({
"schemaVersion": "epiphany.repo_work_closure_model_gate_smoke.v0",
"status": "ok",
"smokeDir": smoke_dir,
"repo": repo,
"branch": git_output(["branch", "--show-current"], &repo)?,
"passItem": good.item,
"passCloseStatus": good.close["status"],
"passModelStatus": good.close["closureReview"]["modelingReview"]["closureReview"]["status"],
"passModelGateEnforced": good.close["closureReview"]["modelingReview"]["closureReview"]["gateEnforced"],
"blockItem": bad.item,
"blockCloseStatus": bad.close["status"],
"blockSoulVerdict": bad.close["soul"]["verdict"],
"blockModelStatus": bad.close["closureReview"]["modelingReview"]["closureReview"]["status"],
"blockFamilyAssertionsStatus": bad.close["closureReview"]["familyAssertions"]["status"],
"blockPathScopeMatched": bad.close["closureReview"]["sourceGrounding"]["pathScopeMatched"],
"privateStateExposed": false,
});
write_json(&smoke_dir.join("summary.json"), &summary)?;
Ok(summary)
}
struct ItemProof {
item: String,
close: Value,
}
fn drive_item(
manifest: &Path,
root: &Path,
repo: &Path,
item: &str,
summary: &str,
model_verdict: &str,
model_finding: &str,
) -> Result<ItemProof> {
cargo_json(
manifest,
"epiphany-work",
&[
"accept",
"--workspace",
path_str(repo)?,
"--from",
"persona",
"--item",
item,
"--summary",
summary,
],
root,
)?;
cargo_json(
manifest,
"epiphany-work",
&[
"derive-plan",
"--workspace",
path_str(repo)?,
"--item",
item,
"--action-family",
"repo-status-section",
"--model-ref",
"closure-model-gate-smoke-imagination-v1",
"--model-authored",
],
root,
)?;
for _ in 0..3 {
cargo_json(
manifest,
"epiphany-work",
&[
"tick",
"--workspace",
path_str(repo)?,
"--epiphany-root",
path_str(root)?,
"--item",
item,
"--cooldown-seconds",
"0",
],
root,
)?;
}
let close = cargo_json(
manifest,
"epiphany-work",
&[
"close",
"--workspace",
path_str(repo)?,
"--item",
item,
"--closure-model-ref",
"closure-model-gate-smoke-soul-v1",
"--model-authored",
"--require-closure-model-verdict",
"--closure-model-verdict",
model_verdict,
"--closure-model-finding",
model_finding,
],
root,
)?;
Ok(ItemProof {
item: item.to_string(),
close,
})
}
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 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 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()))
}

View file

@ -220,6 +220,9 @@ struct CloseArgs {
verification_summary: Option<String>,
modeling_summary: Option<String>,
closure_model_ref: Option<String>,
closure_model_verdict: Option<String>,
closure_model_finding: Option<String>,
require_closure_model_verdict: bool,
model_authored: bool,
state_revision: u64,
}
@ -729,6 +732,9 @@ fn parse_close_args(args: impl Iterator<Item = String>) -> Result<CloseArgs> {
let mut verification_summary = None;
let mut modeling_summary = None;
let mut closure_model_ref = None;
let mut closure_model_verdict = None;
let mut closure_model_finding = None;
let mut require_closure_model_verdict = false;
let mut model_authored = false;
let mut state_revision = 0_u64;
@ -754,6 +760,15 @@ fn parse_close_args(args: impl Iterator<Item = String>) -> Result<CloseArgs> {
"--closure-model-ref" | "--model-ref" => {
closure_model_ref = Some(take_string(&mut args, "--closure-model-ref")?);
}
"--closure-model-verdict" | "--model-verdict" => {
closure_model_verdict = Some(take_string(&mut args, "--closure-model-verdict")?);
}
"--closure-model-finding" | "--model-finding" => {
closure_model_finding = Some(take_string(&mut args, "--closure-model-finding")?);
}
"--require-closure-model-verdict" | "--require-model-verdict" => {
require_closure_model_verdict = true;
}
"--model-authored" => model_authored = true,
"--state-revision" => state_revision = take_u64(&mut args, "--state-revision")?,
other => return Err(anyhow!("unexpected close argument {other:?}")),
@ -769,6 +784,9 @@ fn parse_close_args(args: impl Iterator<Item = String>) -> Result<CloseArgs> {
verification_summary,
modeling_summary,
closure_model_ref,
closure_model_verdict,
closure_model_finding,
require_closure_model_verdict,
model_authored,
state_revision,
})
@ -2459,6 +2477,78 @@ fn push_assertion(assertions: &mut Vec<Value>, id: &str, passed: bool, summary:
}));
}
fn closure_model_review(
model_authored: bool,
model_ref: Option<&str>,
verdict: Option<&str>,
finding: Option<&str>,
required: bool,
) -> Result<(Value, bool)> {
let normalized_verdict = verdict
.map(|value| value.trim().to_ascii_lowercase())
.filter(|value| !value.is_empty());
if let Some(verdict) = normalized_verdict.as_deref() {
match verdict {
"passed" | "failed" | "needs-work" | "blocked" => {}
other => {
return Err(anyhow!(
"unsupported closure model verdict {other:?}; expected passed, failed, needs-work, or blocked"
));
}
}
}
let gate_enforced = required || normalized_verdict.is_some();
let passed = match normalized_verdict.as_deref() {
Some("passed") => true,
Some("failed" | "needs-work" | "blocked") => false,
Some(_) => unreachable!(),
None => !required,
};
let status = match normalized_verdict.as_deref() {
Some("passed") => "passed",
Some("failed" | "needs-work" | "blocked") => "failed",
Some(_) => unreachable!(),
None if required => "missing-required-verdict",
None if model_authored || model_ref.is_some() => "provenance-only",
None => "deterministic-fallback",
};
let reason = match status {
"passed" => "model-authored closure verdict passed",
"failed" => "model-authored closure verdict refused closure",
"missing-required-verdict" => {
"closure required a model-authored verdict but none was supplied"
}
"provenance-only" => "model provenance was recorded without making it a hard closure gate",
_ => "deterministic closure checks are the active gate",
};
Ok((
json!({
"schemaVersion": "epiphany.repo_work_model_closure_review.v0",
"status": status,
"passed": passed,
"required": required,
"gateEnforced": gate_enforced,
"modelAuthored": model_authored || model_ref.is_some(),
"modelRef": model_ref,
"verdict": normalized_verdict,
"finding": finding.map(compact_line),
"reason": reason,
"reviewedInputs": [
"Hands commit receipt matched execute receipt",
"Verification command result",
"Declared versus actual git changed paths",
"Safe-family committed-content assertions",
"Authority seal and private-state exposure flag"
],
"operatorAuthoredShellDetails": false,
"privateStateExposed": false
}),
passed,
))
}
fn read_committed_file(
workspace: &Path,
commit_sha: &str,
@ -3191,6 +3281,13 @@ fn run_close(args: CloseArgs) -> Result<Value> {
let (family_assertions, family_assertions_passed) =
closure_family_assertions(&workspace, &commit_sha, &execute_receipt, &item)?;
let closure_model_authored = args.model_authored || args.closure_model_ref.is_some();
let (model_closure_review, model_closure_passed) = closure_model_review(
closure_model_authored,
args.closure_model_ref.as_deref(),
args.closure_model_verdict.as_deref(),
args.closure_model_finding.as_deref(),
args.require_closure_model_verdict,
)?;
let closure_review_id = format!("repo-work-close-{item_slug}-closure-review");
let closure_review_path = artifact_dir.join(format!("work-close-{item_slug}-review.json"));
let closure_review = json!({
@ -3222,15 +3319,17 @@ fn run_close(args: CloseArgs) -> Result<Value> {
"pathScopeMatched": path_scope_matched,
"commitStat": compact_multiline(&commit_stat),
"commitReceiptMatchedExecuteReceipt": true,
"familyAssertionsPassed": family_assertions_passed
"familyAssertionsPassed": family_assertions_passed,
"modelClosurePassed": model_closure_passed
},
"familyAssertions": family_assertions,
"modelingReview": {
"modelAuthored": closure_model_authored,
"modelRef": args.closure_model_ref,
"modelRef": args.closure_model_ref.clone(),
"deterministicFallback": !closure_model_authored,
"operatorAuthoredShellDetails": false,
"summary": args.modeling_summary.clone()
"summary": args.modeling_summary.clone(),
"closureReview": model_closure_review
},
"authoritySeal": {
"branchLocalOnly": true,
@ -3241,11 +3340,13 @@ fn run_close(args: CloseArgs) -> Result<Value> {
"privateStateExposed": false
},
"privateStateExposed": false,
"nextSafeMove": "Soul may pass closure only when verification succeeds, actual git changed paths match the Hands-declared path scope, and safe-family assertions pass for known Imagination families."
"nextSafeMove": "Soul may pass closure only when verification succeeds, actual git changed paths match the Hands-declared path scope, safe-family assertions pass for known Imagination families, and any supplied or required model-authored closure verdict passes."
});
write_json(&closure_review_path, &closure_review)?;
let verification_passed =
verification.status.success() && path_scope_matched && family_assertions_passed;
let verification_passed = verification.status.success()
&& path_scope_matched
&& family_assertions_passed
&& model_closure_passed;
let soul_verdict_id = format!("repo-work-close-{item_slug}-soul-verdict");
let soul_summary = args.verification_summary.unwrap_or_else(|| {
if verification_passed {
@ -3258,6 +3359,10 @@ fn run_close(args: CloseArgs) -> Result<Value> {
format!(
"Soul verification failed for branch-local commit {commit_sha}: safe-family assertions did not pass."
)
} else if !model_closure_passed {
format!(
"Soul verification failed for branch-local commit {commit_sha}: model-authored closure review did not pass."
)
} else {
format!("Soul verification failed for branch-local commit {commit_sha}.")
}
@ -3295,6 +3400,11 @@ fn run_close(args: CloseArgs) -> Result<Value> {
"Closure refused because the committed content did not satisfy known safe-family assertions."
.to_string(),
]
} else if !model_closure_passed {
vec![
"Closure refused because the model-authored closure review was missing or non-passing."
.to_string(),
]
} else {
vec!["Closure verification command failed; publication remains blocked.".to_string()]
},
@ -4925,6 +5035,9 @@ fn run_tick(args: TickArgs) -> Result<Value> {
"Modeling records scheduler-closed repo work item {item}; publication remains gated by Bifrost."
)),
closure_model_ref: None,
closure_model_verdict: None,
closure_model_finding: None,
require_closure_model_verdict: false,
model_authored: false,
state_revision: 0,
})?;
@ -6181,7 +6294,7 @@ fn print_usage() {
run --workspace <repo> [--item <id>] [--accept-receipt <path>] [--runtime-store <path>] [--requested-path <path>]\n\
adopt --workspace <repo> [--item <id>] [--run-receipt <path>] [--from-plan <path>] [--plan-summary <text>] [--adoption-evidence-ref <ref>]\n\
execute --workspace <repo> [--item <id>] [--from-plan <path>] [--command <command>] [--changed-path <path>] [--commit-message <text>]\n\
close --workspace <repo> [--item <id>] [--execute-receipt <path>] [--verification-command <command>] [--closure-model-ref <ref>] [--model-authored]\n\
close --workspace <repo> [--item <id>] [--execute-receipt <path>] [--verification-command <command>] [--closure-model-ref <ref>] [--model-authored] [--closure-model-verdict passed|failed|needs-work|blocked] [--closure-model-finding <text>] [--require-closure-model-verdict]\n\
publish --workspace <repo> [--item <id>] --change-summary <text> --justification <text> --verification-receipt <ref> --review-receipt <ref> --ledger-entry-id <id> --pull-request-url <url> --pull-request-title <text>\n\
sync --workspace <repo> [--item <id>] [--publish-receipt <path>] [--upstream-ref origin/main] --merge-receipt <ref>\n\
overview --workspace <repo> [--item <id>] [--accept-receipt <path>] [--no-write]\n\

View file

@ -659,10 +659,15 @@ sealed under `.epiphany/work/`. Closure also writes a structured
`.epiphany/work/work-close-<item>-review.json`, recording the verification
command/exit code, declared Hands paths, actual git commit paths, commit stat,
optional model-review provenance from `--closure-model-ref` / `--model-authored`,
safe-family assertions for known Imagination families, and authority seals. Soul
passes only when the verification command succeeds, actual git changed paths
match the Hands-declared path scope, and the committed target content satisfies
the deterministic safe-family assertions when the plan family is known. Soul
optional hard model closure verdicts from `--closure-model-verdict`, safe-family
assertions for known Imagination families, and authority seals. Soul passes only
when the verification command succeeds, actual git changed paths match the
Hands-declared path scope, the committed target content satisfies the
deterministic safe-family assertions when the plan family is known, and any
supplied or required model-authored closure verdict passes.
`--require-closure-model-verdict` turns missing model verdicts into a closure
refusal; without that flag, older provenance-only model refs remain
compatibility evidence instead of a hard gate. Soul
then writes `epiphany.soul.verdict_receipt`, Modeling records the
execution/commit/closure-review summary, and Mind writes gateway review plus
state-commit receipts into runtime-spine. The final
@ -713,6 +718,18 @@ Soul `failed`, `familyAssertions.status=failed`, and failed assertions
`status-section-summary-present`, and `status-section-private-seal`, with
`privateStateExposed=false`.
A model-closure gate smoke proved the new source-grounded review tooth:
`.epiphany-smoke\closure-model-gate-20260620-135103` created a disposable repo,
ran `epiphany-repo init`, `epiphany-swarm online`, two `repo-status-section`
items through accept -> derive-plan -> tick run/adopt/execute, then closed one
item with `--require-closure-model-verdict --closure-model-verdict passed` and
one with `--closure-model-verdict needs-work`. The pass item returned
`closed` with `modelingReview.closureReview.status=passed` and
`gateEnforced=true`. The blocked item returned `verification-failed`, Soul
`failed`, `modelingReview.closureReview.status=failed`, while
`familyAssertions.status=passed` and `pathScopeMatched=true`; private state
remained sealed.
`epiphany-work overview` is the first compact repo work sight/proof surface:
```powershell
@ -967,15 +984,16 @@ sync proof.
`tick` now also proves brake refusal, active-turn refusal,
completion-anchored cooldown refusal, and stale active-turn recovery through
typed scheduler receipts; `serve` now proves bounded cadence around that same
tick artery; `close` now proves first deterministic Soul/Modeling/Mind closure
over Hands commit receipts; `derive-plan` now proves a deterministic
tick artery; `close` now proves deterministic Soul/Modeling/Mind closure over
Hands commit receipts plus an optional/required model-authored closure verdict
gate; `derive-plan` now proves a deterministic
Persona/Bifrost pressure-to-plan bridge with no operator-authored shell
details; `overview` now proves compact local proof-bundle sight over the
receipt chain; `queue-run` now proves that Self can select tick-actionable rows
from the typed repo-work overview queue without reading private thought or
taking publication/service authority. The chain does not yet prove fully
model-authored Imagination planning, deeper model-authored Soul/Modeling
closure, optional Idunn-hosted lifecycle for queue pulses, or richer published
model-authored Imagination planning beyond allowlisted safe-family lowering,
optional Idunn-hosted lifecycle for queue pulses, or richer published
proof bundles.
The MVP target is narrower than the full Perfect Machine and wider than a demo:
@ -1033,14 +1051,12 @@ No: the machine is not yet Epiphany Online in the full MVP sense. The remaining
gap is not "more permission prompts." The remaining gap is to make the
existing typed artery run as a living repo organism:
- deeper model-authored closure review beyond the first typed
`epiphany.repo_work_closure_review.v0` packet
- further non-planning action classes where Imagination can propose useful
branch-local work beyond planning cargo without arbitrary shell authority
- an Idunn-owned service lifecycle path for the queue-run pulse when the
operator explicitly grants service mutation or elevated host authority
- a repeatable fresh-repo acceptance smoke that proves the whole path without
supervisor target-repo edits
- richer model-authored planning depth beyond deterministic safe-family command
lowering
The MVP is functional when a human can talk to the repo Persona, the swarm can
turn that pressure into typed work, keep advancing lawful branch-local steps
@ -1332,17 +1348,19 @@ Required organs before MVP:
`section-note`, and `task-card` now prove richer model-authored planning cargo
without operator shell details. `repo-status-section` now proves the first
repo-visible non-planning safe family by updating a marker-bounded README
status section with the same authority seals. Remaining work is richer closure
depth and any later model-authored action classes that move beyond
documentation/status cargo while preserving the same authority seals.
status section with the same authority seals. Remaining work is later
model-authored action classes that move beyond documentation/status cargo
while preserving the same authority seals.
- Closure depth: `close` now writes a structured
`epiphany.repo_work_closure_review.v0` packet for Hands commits, records
model-review provenance when supplied, refuses actual-vs-declared path-scope
mismatch before Soul passes, runs committed-content assertions for known safe
families, refuses assertion failures even when the path scope matches, and
exposes the closure-review artifact through overview/export proof rows.
Deeper model-authored Soul/Modeling review remains a later upgrade on top of
this typed packet.
families, refuses assertion failures even when the path scope matches, can
require a model-authored closure verdict before Soul passes, and exposes the
closure-review artifact through overview/export proof rows. The verdict packet
records reviewed inputs, finding text, enforced/skipped gate state, and
private-state seals; focused smoke proves a model `needs-work` verdict blocks
closure even when path scope and safe-family assertions pass.
- Repo work projection/run surface: local `epiphany-work overview` proof bundles, typed
`epiphany.cultmesh.repo_work_overview.v0` event history plus latest-key
projection, and Gjallar multi-item `repo-work-overview` action/readback rows

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long