Add repo planning facets receipts

This commit is contained in:
Meta Vens 2026-06-20 17:43:18 +01:00
commit fca5c759b0
6 changed files with 596 additions and 13 deletions

View file

@ -0,0 +1,441 @@
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!("repo-planning-facets-{stamp}"));
if smoke_dir.exists() {
fs::remove_dir_all(&smoke_dir)
.with_context(|| format!("failed to clear {}", smoke_dir.display()))?;
}
fs::create_dir_all(&smoke_dir)
.with_context(|| format!("failed to create {}", smoke_dir.display()))?;
let repo = smoke_dir.join("repo-body");
fs::create_dir_all(&repo).with_context(|| format!("failed to create {}", repo.display()))?;
git(["init"], &repo)?;
git(
["config", "user.email", "epiphany-smoke@example.invalid"],
&repo,
)?;
git(["config", "user.name", "Epiphany Smoke"], &repo)?;
fs::write(
repo.join("README.md"),
"# Repo 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 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,
"handsAuthorityGranted": false,
"durableStateAdmitted": false,
"handsCommandAuthority": false,
"durableStateAuthority": 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}"))?,
))
}

View file

@ -165,6 +165,12 @@ struct DerivePlanArgs {
verification_asks: Vec<String>,
stop_conditions: Vec<String>,
escalation_reasons: Vec<String>,
assumptions: Vec<String>,
constraints: Vec<String>,
non_goals: Vec<String>,
open_questions: Vec<String>,
decision_points: Vec<String>,
evidence_needs: Vec<String>,
}
#[derive(Clone, Debug)]
@ -567,6 +573,12 @@ fn parse_derive_plan_args(args: impl Iterator<Item = String>) -> Result<DerivePl
let mut verification_asks = Vec::new();
let mut stop_conditions = Vec::new();
let mut escalation_reasons = Vec::new();
let mut assumptions = Vec::new();
let mut constraints = Vec::new();
let mut non_goals = Vec::new();
let mut open_questions = Vec::new();
let mut decision_points = Vec::new();
let mut evidence_needs = Vec::new();
let mut args = args.peekable();
while let Some(arg) = args.next() {
@ -597,6 +609,24 @@ fn parse_derive_plan_args(args: impl Iterator<Item = String>) -> Result<DerivePl
"--escalation-reason" => {
escalation_reasons.push(take_string(&mut args, "--escalation-reason")?);
}
"--assumption" | "--planning-assumption" => {
assumptions.push(take_string(&mut args, "--assumption")?);
}
"--constraint" | "--planning-constraint" => {
constraints.push(take_string(&mut args, "--constraint")?);
}
"--non-goal" | "--nongoal" => {
non_goals.push(take_string(&mut args, "--non-goal")?);
}
"--open-question" => {
open_questions.push(take_string(&mut args, "--open-question")?);
}
"--decision-point" => {
decision_points.push(take_string(&mut args, "--decision-point")?);
}
"--evidence-need" | "--evidence-needed" => {
evidence_needs.push(take_string(&mut args, "--evidence-need")?);
}
other => return Err(anyhow!("unexpected derive-plan argument {other:?}")),
}
}
@ -613,6 +643,12 @@ fn parse_derive_plan_args(args: impl Iterator<Item = String>) -> Result<DerivePl
verification_asks,
stop_conditions,
escalation_reasons,
assumptions,
constraints,
non_goals,
open_questions,
decision_points,
evidence_needs,
})
}
@ -1778,6 +1814,17 @@ fn run_derive_plan(args: DerivePlanArgs) -> Result<Value> {
} else {
args.escalation_reasons
};
let planning_facets = PlanningFacets::for_derive_plan(
source,
&derived_plan.safe_action_family,
&derived_plan.target_path,
args.assumptions,
args.constraints,
args.non_goals,
args.open_questions,
args.decision_points,
args.evidence_needs,
);
let action_item_summary = args
.action_summary
.unwrap_or_else(|| derived_plan.plan_summary.clone());
@ -1798,6 +1845,7 @@ fn run_derive_plan(args: DerivePlanArgs) -> Result<Value> {
stop_conditions: action_stop_conditions.clone(),
escalation_reasons: action_escalation_reasons,
rollback_hints: derived_plan.rollback_hints.clone(),
planning_facets: planning_facets.clone(),
model_ref: model_ref.as_deref(),
model_authored,
},
@ -1814,6 +1862,7 @@ fn run_derive_plan(args: DerivePlanArgs) -> Result<Value> {
"status": action_items["status"],
"modelAuthored": model_authored,
"safeActionFamily": derived_plan.safe_action_family,
"planningFacets": planning_facets.to_json(),
});
write_plan_receipt(
@ -6114,10 +6163,88 @@ struct ImaginationActionItemReceiptInputs<'a> {
stop_conditions: Vec<String>,
escalation_reasons: Vec<String>,
rollback_hints: Vec<String>,
planning_facets: PlanningFacets,
model_ref: Option<&'a str>,
model_authored: bool,
}
#[derive(Clone, Debug)]
struct PlanningFacets {
assumptions: Vec<String>,
constraints: Vec<String>,
non_goals: Vec<String>,
open_questions: Vec<String>,
decision_points: Vec<String>,
evidence_needs: Vec<String>,
}
impl PlanningFacets {
fn for_derive_plan(
source: &str,
safe_action_family: &str,
target_path: &str,
assumptions: Vec<String>,
constraints: Vec<String>,
non_goals: Vec<String>,
open_questions: Vec<String>,
decision_points: Vec<String>,
evidence_needs: Vec<String>,
) -> Self {
Self {
assumptions: default_if_empty(
assumptions,
vec![format!(
"Accepted {source} pressure can be represented by safe family {safe_action_family}."
)],
),
constraints: default_if_empty(
constraints,
vec![
format!("Hands may only change the requested path {target_path}."),
"Publication, merge, service lifecycle, elevation, cross-repo mutation, and private-state exposure remain outside this plan.".to_string(),
],
),
non_goals: default_if_empty(
non_goals,
vec![
"Do not convert model-authored planning text into arbitrary shell authority.".to_string(),
"Do not admit durable Mind state without the later Mind gate.".to_string(),
],
),
open_questions: default_if_empty(
open_questions,
vec!["Does Self/Mind accept this candidate as the next branch-local move?".to_string()],
),
decision_points: default_if_empty(
decision_points,
vec![
"Self/Mind may adopt this action item, ask Imagination for a narrower plan, or route it to Bifrost/Maintainer review.".to_string(),
],
),
evidence_needs: default_if_empty(
evidence_needs,
vec![
"Soul needs the derived plan receipt, changed-path proof, safe-family closure assertions, and private-state seal before closure.".to_string(),
],
),
}
}
fn to_json(&self) -> Value {
json!({
"assumptions": self.assumptions,
"constraints": self.constraints,
"nonGoals": self.non_goals,
"openQuestions": self.open_questions,
"decisionPoints": self.decision_points,
"evidenceNeeds": self.evidence_needs,
"handsCommandAuthority": false,
"durableStateAuthority": false,
"privateStateExposed": false
})
}
}
fn write_imagination_action_items_receipt(
artifact_dir: &Path,
workspace: &Path,
@ -6135,6 +6262,7 @@ fn write_imagination_action_items_receipt(
let receipt_id = format!("repo-work-action-items-{item_slug}");
let action_item_id = format!("{receipt_id}-action-1");
let now = Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
let planning_facets = inputs.planning_facets.to_json();
let receipt = json!({
"schemaVersion": "epiphany.repo_work_imagination_action_items_receipt.v0",
"createdAt": now,
@ -6172,6 +6300,7 @@ fn write_imagination_action_items_receipt(
"stopConditions": inputs.stop_conditions,
"escalationReasons": inputs.escalation_reasons,
"rollbackHints": inputs.rollback_hints,
"planningFacets": planning_facets,
"handsCommandDerived": false
}],
"authority": {
@ -9678,6 +9807,10 @@ fn string_array_field(value: &Value, field: &str) -> Vec<String> {
.unwrap_or_default()
}
fn default_if_empty(values: Vec<String>, defaults: Vec<String>) -> Vec<String> {
if values.is_empty() { defaults } else { values }
}
fn compact_text(text: &str, limit: usize) -> String {
let compact = text.split_whitespace().collect::<Vec<_>>().join(" ");
if compact.chars().count() <= limit {
@ -9794,7 +9927,7 @@ fn print_usage() {
"usage: epiphany-work <persona-intake|accept|derive-plan|plan|run|adopt|execute|close|publish|sync|overview|export-proof|tick|queue-run|serve> ...\n\
persona-intake --workspace <repo> --item <id> --message <text> [--topic <topic>] [--store <local-verse.ccmp>] [--runtime-id <id>]\n\
accept --workspace <repo> --from <persona|bifrost|persona-or-bifrost> --item <id> [--summary <text>] [--topic <topic>] [--store <local-verse.ccmp>] [--runtime-id <id>] [--online-receipt <path>] [--public-discussion-ref <ref>] [--candidate-action-ref <ref>]\n\
derive-plan --workspace <repo> [--item <id>] [--accept-receipt <path>] [--action-family append-worklog|planning-note|checklist-note|section-note|repo-status-section|task-card|repo-manifest|repo-tool-capabilities|repo-collaboration-topic|repo-consensus-brief|repo-objective-draft|repo-adoption-request|repo-scheduling-request|repo-work-order|repo-verification-request|repo-publication-request|repo-sync-request|repo-maintainer-review-request|repo-pr-request|repo-credit-request|repo-artifact-acceptance-request|repo-metrics-request] [--target-path <path>] [--model-ref <ref>] [--model-authored] [--action-summary <text>] [--verification-ask <text>] [--stop-condition <text>] [--escalation-reason <text>]\n\
derive-plan --workspace <repo> [--item <id>] [--accept-receipt <path>] [--action-family append-worklog|planning-note|checklist-note|section-note|repo-status-section|task-card|repo-manifest|repo-tool-capabilities|repo-collaboration-topic|repo-consensus-brief|repo-objective-draft|repo-adoption-request|repo-scheduling-request|repo-work-order|repo-verification-request|repo-publication-request|repo-sync-request|repo-maintainer-review-request|repo-pr-request|repo-credit-request|repo-artifact-acceptance-request|repo-metrics-request] [--target-path <path>] [--model-ref <ref>] [--model-authored] [--action-summary <text>] [--verification-ask <text>] [--stop-condition <text>] [--escalation-reason <text>] [--assumption <text>] [--constraint <text>] [--non-goal <text>] [--open-question <text>] [--decision-point <text>] [--evidence-need <text>]\n\
plan --workspace <repo> [--item <id>] --objective <text> --plan-summary <text> --command <command> --changed-path <path> --commit-message <text> [--adoption-evidence-ref <ref>]\n\
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\

View file

@ -1368,8 +1368,8 @@ existing typed artery run as a living repo organism:
branch-local work beyond planning cargo without arbitrary shell authority
- an Idunn-owned service lifecycle path for the queue-run pulse when the
operator explicitly grants service mutation or elevated host authority
- richer model-authored planning depth beyond deterministic safe-family command
lowering
- richer adoption/interpreter depth after model-authored planning facets, without
turning typed Imagination cargo into shell authority
The MVP is functional when a human can talk to the repo Persona, the swarm can
turn that pressure into typed work, keep advancing lawful branch-local steps
@ -1388,7 +1388,7 @@ temptation wearing clean robes.
| Repo Body birth | `epiphany-repo init` exists and writes repo-local stores plus branch workbench intent. | Keep birth startup-only, review-gated, and branch-oriented. |
| Local Verse online | `epiphany-swarm online` seeds repo-local CultMesh, standing-faculty SoA, topology, liveness, Eve, and tool sight. | Keep private Verse sealed while exposing operator-safe repo status. |
| Persona/Bifrost intake | `epiphany-work accept` records pressure and candidate action refs without Hands authority. `epiphany-work persona-intake` now invokes the Persona bubble speech-audit path, records public discussion and candidate-action refs, then delegates to `accept`; wrapper mode `repo-persona-intake` exposes the operator mouth. | Deepen the intake-to-Imagination interpreter so richer model-authored action items can be proposed without granting Hands, publication, or durable-state authority at the mouth edge. |
| Imagination planning | `derive-plan` now writes a typed `epiphany.repo_work_imagination_action_items_receipt.v0` before the executable plan receipt. The action-item receipt can carry model provenance, allowed safe family, requested paths, verification asks, stop conditions, escalation reasons, and private-state seals; command text remains deterministic safe-family lowering for `append-worklog`, `planning-note`, `checklist-note`, `section-note` / `repo.markdown_managed_section`, `repo-status-section` / `repo.status_section`, `task-card` / `repo.task_card`, `repo-manifest` / `repo.body_manifest`, `repo-tool-capabilities` / `repo.tool_capabilities`, `repo-collaboration-topic` / `repo.collaboration_topic`, `repo-consensus-brief` / `repo.consensus_brief`, `repo-objective-draft` / `repo.objective_draft`, `repo-adoption-request` / `repo.adoption_request`, `repo-scheduling-request` / `repo.scheduling_request`, `repo-work-order` / `repo.work_order`, `repo-verification-request` / `repo.verification_request`, `repo-publication-request` / `repo.publication_request`, `repo-sync-request` / `repo.sync_request`, `repo-maintainer-review-request` / `repo.maintainer_review_request`, `repo-pr-request` / `repo.pr_request`, `repo-credit-request` / `repo.credit_request`, `repo-artifact-acceptance-request` / `repo.artifact_acceptance_request`, and `repo-metrics-request` / `repo.metrics_request`. `plan` remains manual quarantine scaffolding. | Deepen model-authored planning and further useful repo-owned configuration classes without turning model text into arbitrary shell authority. |
| Imagination planning | `derive-plan` now writes a typed `epiphany.repo_work_imagination_action_items_receipt.v0` before the executable plan receipt. The action-item receipt can carry model provenance, allowed safe family, requested paths, verification asks, stop conditions, escalation reasons, planning facets (assumptions, constraints, non-goals, open questions, decision points, and evidence needs), and private-state seals; command text remains deterministic safe-family lowering for `append-worklog`, `planning-note`, `checklist-note`, `section-note` / `repo.markdown_managed_section`, `repo-status-section` / `repo.status_section`, `task-card` / `repo.task_card`, `repo-manifest` / `repo.body_manifest`, `repo-tool-capabilities` / `repo.tool_capabilities`, `repo-collaboration-topic` / `repo.collaboration_topic`, `repo-consensus-brief` / `repo.consensus_brief`, `repo-objective-draft` / `repo.objective_draft`, `repo-adoption-request` / `repo.adoption_request`, `repo-scheduling-request` / `repo.scheduling_request`, `repo-work-order` / `repo.work_order`, `repo-verification-request` / `repo.verification_request`, `repo-publication-request` / `repo.publication_request`, `repo-sync-request` / `repo.sync_request`, `repo-maintainer-review-request` / `repo.maintainer_review_request`, `repo-pr-request` / `repo.pr_request`, `repo-credit-request` / `repo.credit_request`, `repo-artifact-acceptance-request` / `repo.artifact_acceptance_request`, and `repo-metrics-request` / `repo.metrics_request`. `plan` remains manual quarantine scaffolding. | Deepen adoption/interpreter behavior and further useful repo-owned configuration classes without turning model text into arbitrary shell authority. |
| Self scheduling | `tick` and `serve` prove one-step branch-local advancement, brake refusal, active-turn refusal, cooldown, and stale-turn recovery; `tick` now routes executed branch-local work through the existing Soul/Modeling/Mind `close` gate; `queue-run` selects tick-actionable rows from the typed repo-work queue and delegates to `tick`; `epiphany-swarm run` is the bounded operator mouth over that queue/tick physiology; `repo-work-service-plan` and `repo-work-service-runbook` write Idunn lifecycle receipts/artifacts for the same queue-run command without launching it. | Keep any future queue-run service launch/install behind Idunn and explicit operator authority. |
| Branch-local Hands work | `adopt` and `execute` create approved Hands gates, run planned commands, stage declared paths, commit on `epiphany/*`, and write receipts. | Keep mutation branch-contained and receipt-backed; broaden only through typed plan families, not ad hoc shell freedom. |
| Soul/Modeling/Mind closure | `close` verifies the Hands commit, writes `epiphany.repo_work_closure_review.v0`, refuses path-scope mismatches, and writes Soul, Modeling, and Mind receipts. | Deepen model-authored closure review beyond the first structured closure-review packet while preserving deterministic local closure for simple mechanical work. |
@ -1421,12 +1421,12 @@ Build the remaining MVP in this order:
candidate action refs, then delegates to `accept`; wrapper mode
`repo-persona-intake` exposes the operator surface. `derive-plan` now emits
typed Imagination action-item receipts with allowed safe families, requested
paths, verification asks, stop conditions, escalation reasons, model
provenance, and no Hands/publication/durable-state authority before it
lowers the chosen safe family into a plan receipt. Banter remains banter
unless Mind/Interpreter extracts a work candidate. The remaining cut is
richer model-authored families and adoption depth, not basic action-item
receipt shape.
paths, verification asks, stop conditions, escalation reasons, planning
facets, model provenance, and no Hands/publication/durable-state authority
before it lowers the chosen safe family into a plan receipt. Banter remains
banter unless Mind/Interpreter extracts a work candidate. The remaining cut
is richer model-authored families and adoption/interpreter depth, not basic
action-item receipt shape.
3. **Model-authored safe plan families.** Promote `derive-plan` beyond the
deterministic `append-worklog` and `planning-note` reliquaries by letting
Imagination author typed plans over allowlisted repo-local families. Shell
@ -1720,8 +1720,16 @@ Required organs before MVP:
antecedents plus model-call/token-cost/review-load/artifact/proof/credit-readback/redaction
packet requirements while granting no metrics ledger, spend, review-load,
credit ledger, publication, PR, merge, sync, Hands, or cross-body authority.
`repo-planning-facets` smoke `.epiphany-smoke\repo-planning-facets-20260620-163800`
proves model-authored planning facets on action-item receipts: assumptions,
constraints, non-goals, open questions, decision points, and evidence needs
survive into the Imagination receipt and plan derivation while Hands command
authority, durable-state authority, publication, merge, service lifecycle,
cross-body mutation, and private-state exposure remain false; the branch-local
item still closes through Soul with family assertions and path scope passing.
Remaining work is later model-authored action classes that can change more
useful repo-owned configuration without arbitrary shell authority.
useful repo-owned configuration, plus deeper adoption/interpreter behavior,
without arbitrary shell authority.
- Closure depth: `close` now writes a structured
`epiphany.repo_work_closure_review.v0` packet for Hands commits, records
model-review provenance when supplied, refuses actual-vs-declared path-scope

View file

@ -135,6 +135,7 @@ remembers doctrine; the branch remembers the blade.
- 2026-06-20: `repo-credit-request` is now live as Bifrost credit request cargo. `epiphany-work derive-plan --action-family repo-credit-request` writes `.epiphany/credit-requests/<item>.toml` with safe family `repo.credit_request`, status `awaiting-bifrost-credit-review`, closure/Soul/Mind/public-proof, maintainer-review, accepted-artifact, and authorship antecedents, author/reviewer/artifact/proof/path/ledger/redaction packet requirements, and denials for credit ledger, Bifrost publication, GitHub PR, merge, publication, upstream sync, Hands action, cross-body mutation, and private Verse rummaging. Soul closure assertions inspect schema, family, summary, request state, antecedents, receipt contract, credit packet, authority gates, and private-state seal. Focused smoke `.epiphany-smoke\repo-credit-request-family-20260620-160803` ran init -> online -> accept -> repo-credit-request derive-plan -> tick run/adopt/execute/close; Hands committed only `.epiphany/credit-requests/repo-credit-request-family.toml`, Soul passed with `pathScopeMatched=true`, `familyAssertions.status=passed`, `awaitingBifrostCreditReview=true`, `creditLedgerAuthorized=false`, `bifrostPublicationAuthorized=false`, `githubPrAuthorized=false`, `mergeAuthorized=false`, `publicationAuthorized=false`, `upstreamSyncAuthorized=false`, `handsActionAuthorized=false`, and `privateStateExposed=false`. Current remaining repo-swarm MVP organs are richer model-authored planning depth beyond deterministic safe-family lowering, more useful repo-owned configuration/action families, and Idunn-owned queue-run service lifecycle under explicit operator authority.
- 2026-06-20: `repo-artifact-acceptance-request` is now live as accepted-artifact request cargo. `epiphany-work derive-plan --action-family repo-artifact-acceptance-request` writes `.epiphany/artifact-acceptance-requests/<item>.toml` with safe family `repo.artifact_acceptance_request`, status `awaiting-artifact-acceptance-review`, closure/Soul/Mind/public-proof, maintainer-review, and Hands-commit antecedents, artifact/commit/path/review/proof/rationale/redaction packet requirements, and denials for artifact acceptance, credit ledger, GitHub PR, merge, publication, upstream sync, Hands action, cross-body mutation, and private Verse rummaging. Soul closure assertions inspect schema, family, summary, request state, antecedents, receipt contract, artifact packet, authority gates, and private-state seal. Focused smoke `.epiphany-smoke\repo-artifact-acceptance-request-family-20260620-161559` ran init -> online -> accept -> repo-artifact-acceptance-request derive-plan -> tick run/adopt/execute/close; Hands committed only `.epiphany/artifact-acceptance-requests/repo-artifact-acceptance-request-family.toml`, Soul passed with `pathScopeMatched=true`, `familyAssertions.status=passed`, `awaitingArtifactAcceptanceReview=true`, `artifactAcceptanceAuthorized=false`, `creditLedgerAuthorized=false`, `githubPrAuthorized=false`, `mergeAuthorized=false`, `publicationAuthorized=false`, `upstreamSyncAuthorized=false`, `handsActionAuthorized=false`, and `privateStateExposed=false`. Current remaining repo-swarm MVP organs are richer model-authored planning depth beyond deterministic safe-family lowering, more useful repo-owned configuration/action families, and Idunn-owned queue-run service lifecycle under explicit operator authority.
- 2026-06-20: `repo-metrics-request` is now live as Bifrost/maintainer accounting request cargo. `epiphany-work derive-plan --action-family repo-metrics-request` writes `.epiphany/metrics-requests/<item>.toml` with safe family `repo.metrics_request`, status `awaiting-metrics-review`, closure/Soul/Mind/public-proof, accepted-artifact, and credit-request antecedents, model-call/token-cost/review-load/artifact/proof/credit-readback/redaction packet requirements, and denials for metrics ledger, spend, review-load, credit ledger, GitHub PR, merge, publication, upstream sync, Hands action, cross-body mutation, and private Verse rummaging. Soul closure assertions inspect schema, family, summary, request state, antecedents, receipt contract, metrics packet, authority gates, and private-state seal. Focused smoke `.epiphany-smoke\repo-metrics-request-family-20260620-162919` ran init -> online -> accept -> repo-metrics-request derive-plan -> tick run/adopt/execute/close; Hands committed only `.epiphany/metrics-requests/repo-metrics-request-family.toml`, Soul passed with `pathScopeMatched=true`, `familyAssertions.status=passed`, `awaitingMetricsReview=true`, `metricsLedgerAuthorized=false`, `spendAuthorized=false`, `reviewLoadAuthorized=false`, `creditLedgerAuthorized=false`, `githubPrAuthorized=false`, `mergeAuthorized=false`, `publicationAuthorized=false`, `upstreamSyncAuthorized=false`, `handsActionAuthorized=false`, and `privateStateExposed=false`. Current remaining repo-swarm MVP organs are richer model-authored planning depth beyond deterministic safe-family lowering, more useful repo-owned configuration/action families, and Idunn-owned queue-run service lifecycle under explicit operator authority.
- 2026-06-20: `derive-plan` action-item receipts now carry richer model-authored planning facets without widening command authority. New CLI fields `--assumption`, `--constraint`, `--non-goal`, `--open-question`, `--decision-point`, and `--evidence-need` are recorded under `planningFacets` in `epiphany.repo_work_imagination_action_items_receipt.v0` and mirrored into the plan derivation's `actionItemReceipt`; deterministic defaults are emitted when a model does not supply them. Focused smoke `.epiphany-smoke\repo-planning-facets-20260620-163800` ran init -> online -> accept -> model-authored `derive-plan --action-family planning-note` with all facet fields -> tick run/adopt/execute/close; the action-item receipt preserved assumptions, constraints, non-goals, open questions, decision points, and evidence needs while `handsAuthorityGranted=false`, `durableStateAdmitted=false`, `handsCommandAuthority=false`, `durableStateAuthority=false`, and `privateStateExposed=false`; Soul closure passed with family assertions and path scope matched. Current remaining repo-swarm MVP organs are deeper adoption/interpreter behavior, more useful repo-owned configuration/action families, and Idunn-owned queue-run service lifecycle under explicit operator authority.
- `service-execution-runbook` makes that next cut repeatable: it generates the exact elevated wrapper command sequence without mutating the service manager, continues after per-step failure to preserve more receipts, runs `service-execution-audit` in `finally`, exits nonzero if any step failed, and seals a typed runbook lifecycle receipt. Run the generated artifact only with explicit operator authority.
- `service-execution-audit` is the non-mutating acceptance proof after the elevated rite: it reports `complete` only when typed lifecycle receipts prove the runbook witness plus expected elevated chain, and otherwise reports the missing/failed sealed checks.
- `service-tick` is the non-elevated scheduler pulse proof: it writes the latest daemon scheduler receipt through durable policy ownership and local wrapper receipts.

Binary file not shown.

File diff suppressed because one or more lines are too long