From 2de8df0432c746b81dfa7d709eefeb78c044be8e Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 12 Jul 2026 10:35:33 +0100 Subject: [PATCH] Add typed repo work Modeling requests --- epiphany-core/src/bin/epiphany-work.rs | 53 +++++++++++++ epiphany-core/src/lib.rs | 5 ++ epiphany-core/src/modeling_gateway.rs | 35 +++++++++ epiphany-core/src/runtime_spine.rs | 85 +++++++++++++++++++++ notes/epiphany-current-algorithmic-map.md | 10 +++ notes/epiphany-fork-implementation-plan.md | 10 ++- notes/fresh-workspace-handoff.md | 8 ++ state/ledgers.msgpack | Bin 419009 -> 419371 bytes state/map.yaml | 2 +- 9 files changed, 203 insertions(+), 5 deletions(-) diff --git a/epiphany-core/src/bin/epiphany-work.rs b/epiphany-core/src/bin/epiphany-work.rs index 6470b137..401df864 100644 --- a/epiphany-core/src/bin/epiphany-work.rs +++ b/epiphany-core/src/bin/epiphany-work.rs @@ -29,8 +29,10 @@ use epiphany_core::MindGatewayReview; use epiphany_core::PersonaMemoryCacheConfig; use epiphany_core::REPO_WORK_MAP_ENTRY_SCHEMA_VERSION; use epiphany_core::REPO_WORK_MODELING_FINDING_SCHEMA_VERSION; +use epiphany_core::REPO_WORK_MODELING_REQUEST_SCHEMA_VERSION; use epiphany_core::RepoWorkMapEntry; use epiphany_core::RepoWorkModelingFinding; +use epiphany_core::RepoWorkModelingRequest; use epiphany_core::RuntimeSpineInitOptions; use epiphany_core::SOUL_VERDICT_RECEIPT_SCHEMA_VERSION; use epiphany_core::SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_SCHEMA_VERSION; @@ -67,6 +69,7 @@ use epiphany_core::put_hands_commit_receipt; use epiphany_core::put_hands_patch_receipt; use epiphany_core::put_hands_pr_receipt; use epiphany_core::put_repo_work_modeling_finding; +use epiphany_core::put_repo_work_modeling_request; use epiphany_core::put_soul_verdict_receipt; use epiphany_core::put_substrate_gate_repo_access_grant_receipt; use epiphany_core::record_weksa_target_lowering_receipt; @@ -77,6 +80,7 @@ use epiphany_core::runtime_hands_commit_receipt; use epiphany_core::runtime_latest_hands_receipt_chain_after; use epiphany_core::runtime_repo_work_map_entry; use epiphany_core::runtime_repo_work_modeling_finding; +use epiphany_core::runtime_repo_work_modeling_request; use epiphany_core::write_epiphany_cultmesh_repo_work_map_entry; use epiphany_core::write_epiphany_cultmesh_repo_work_overview; use epiphany_core::write_epiphany_cultmesh_repo_work_public_proof; @@ -11475,6 +11479,33 @@ fn run_close(args: CloseArgs) -> Result { declared_changed_paths.join(", ") ) }); + let modeling_request_id = format!("repo-work-close-{item_slug}-modeling-request"); + let mut modeling_request = RepoWorkModelingRequest { + schema_version: REPO_WORK_MODELING_REQUEST_SCHEMA_VERSION.to_string(), + request_id: modeling_request_id.clone(), + item: item.clone(), + requester: "self".to_string(), + soul_verdict_receipt_id: soul_verdict.receipt_id.clone(), + commit_sha: commit_sha.clone(), + changed_paths: declared_changed_paths.clone(), + instruction: "Model the Soul-verified consequence and return a bounded repo-map finding; do not admit state or route the next action.".to_string(), + requested_at: Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true), + private_state_exposed: false, + contract: "Self-to-Modeling request over a passing Soul verdict; Self may route but cannot author the result.".to_string(), + }; + if let Some(existing) = + runtime_repo_work_modeling_request(&runtime_store, &modeling_request_id)? + { + modeling_request.requested_at = existing.requested_at.clone(); + if modeling_request != existing { + return Err(anyhow!( + "closure retry conflicts with immutable Modeling request" + )); + } + modeling_request = existing; + } else { + put_repo_work_modeling_request(&runtime_store, &modeling_request)?; + } let modeling_finding_receipt_id = format!("repo-work-close-{item_slug}-modeling-finding"); let mut modeling_finding = RepoWorkModelingFinding { schema_version: REPO_WORK_MODELING_FINDING_SCHEMA_VERSION.to_string(), @@ -11499,6 +11530,7 @@ fn run_close(args: CloseArgs) -> Result { emitted_at: Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true), private_state_exposed: false, contract: "Modeling interpretation of Soul-verified repo consequence; Mind must reread this typed receipt before map admission.".to_string(), + request_id: modeling_request.request_id.clone(), }; if let Some(existing) = runtime_repo_work_modeling_finding(&runtime_store, &modeling_finding_receipt_id)? @@ -11633,6 +11665,7 @@ fn run_close(args: CloseArgs) -> Result { }, "closureReview": closure_review, "modeling": { + "requestId": modeling_request.request_id, "findingReceiptId": modeling_finding.receipt_id, "summary": modeling_finding.summary, "changedPaths": execute_receipt["changedPaths"], @@ -16240,6 +16273,20 @@ mod authority_tests { contract: "test".to_string(), }, )?; + let request = RepoWorkModelingRequest { + schema_version: REPO_WORK_MODELING_REQUEST_SCHEMA_VERSION.to_string(), + request_id: "modeling-request-1".to_string(), + item: "item-1".to_string(), + requester: "self".to_string(), + soul_verdict_receipt_id: "soul-1".to_string(), + commit_sha: "abc123".to_string(), + changed_paths: vec!["README.md".to_string()], + instruction: "Model verified consequence.".to_string(), + requested_at: "2026-07-12T00:00:01Z".to_string(), + private_state_exposed: false, + contract: "test".to_string(), + }; + put_repo_work_modeling_request(&store, &request)?; let mut finding = RepoWorkModelingFinding { schema_version: REPO_WORK_MODELING_FINDING_SCHEMA_VERSION.to_string(), receipt_id: "modeling-finding-1".to_string(), @@ -16254,7 +16301,13 @@ mod authority_tests { emitted_at: "2026-07-12T00:00:01Z".to_string(), private_state_exposed: true, contract: "test".to_string(), + request_id: request.request_id.clone(), }; + let mut unsolicited_finding = finding.clone(); + unsolicited_finding.receipt_id = "unsolicited-modeling-finding".to_string(); + unsolicited_finding.request_id = "missing-modeling-request".to_string(); + unsolicited_finding.private_state_exposed = false; + assert!(put_repo_work_modeling_finding(&store, &unsolicited_finding).is_err()); assert!(put_repo_work_modeling_finding(&store, &finding).is_err()); finding.private_state_exposed = false; put_repo_work_modeling_finding(&store, &finding)?; diff --git a/epiphany-core/src/lib.rs b/epiphany-core/src/lib.rs index 957b75ae..696b7758 100644 --- a/epiphany-core/src/lib.rs +++ b/epiphany-core/src/lib.rs @@ -657,8 +657,11 @@ pub use modeling_gateway::REPO_WORK_MAP_ENTRY_SCHEMA_VERSION; pub use modeling_gateway::REPO_WORK_MAP_ENTRY_TYPE; pub use modeling_gateway::REPO_WORK_MODELING_FINDING_SCHEMA_VERSION; pub use modeling_gateway::REPO_WORK_MODELING_FINDING_TYPE; +pub use modeling_gateway::REPO_WORK_MODELING_REQUEST_SCHEMA_VERSION; +pub use modeling_gateway::REPO_WORK_MODELING_REQUEST_TYPE; pub use modeling_gateway::RepoWorkMapEntry; pub use modeling_gateway::RepoWorkModelingFinding; +pub use modeling_gateway::RepoWorkModelingRequest; pub use organ_dependencies::EPIPHANY_LAUNCH_ORGAN_CONTRACT_SCHEMA_VERSION; pub use organ_dependencies::EPIPHANY_ORGAN_DEPENDENCY_SCHEMA_VERSION; pub use organ_dependencies::EPIPHANY_STANDING_ORGANS; @@ -790,6 +793,7 @@ pub use runtime_spine::put_hands_pr_receipt; 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_runtime_reorient_worker_result; pub use runtime_spine::put_runtime_role_worker_result; pub use runtime_spine::put_soul_verdict_receipt; @@ -812,6 +816,7 @@ pub use runtime_spine::runtime_mind_state_commit_receipt; pub use runtime_spine::runtime_reorient_worker_result; 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_role_worker_result; pub use runtime_spine::runtime_schema_catalog_response; pub use runtime_spine::runtime_soul_verdict_receipt; diff --git a/epiphany-core/src/modeling_gateway.rs b/epiphany-core/src/modeling_gateway.rs index 18588d10..42699217 100644 --- a/epiphany-core/src/modeling_gateway.rs +++ b/epiphany-core/src/modeling_gateway.rs @@ -3,6 +3,9 @@ use cultcache_rs::DatabaseEntry; pub const REPO_WORK_MODELING_FINDING_TYPE: &str = "epiphany.modeling.repo_work_finding"; pub const REPO_WORK_MODELING_FINDING_SCHEMA_VERSION: &str = "epiphany.modeling.repo_work_finding.v0"; +pub const REPO_WORK_MODELING_REQUEST_TYPE: &str = "epiphany.modeling.repo_work_request"; +pub const REPO_WORK_MODELING_REQUEST_SCHEMA_VERSION: &str = + "epiphany.modeling.repo_work_request.v0"; pub const REPO_WORK_MAP_ENTRY_TYPE: &str = "epiphany.repo_work.map_entry"; pub const REPO_WORK_MAP_ENTRY_SCHEMA_VERSION: &str = "epiphany.repo_work.map_entry.v0"; @@ -38,6 +41,38 @@ pub struct RepoWorkModelingFinding { pub private_state_exposed: bool, #[cultcache(key = 12)] pub contract: String, + #[cultcache(key = 13)] + pub request_id: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "epiphany.modeling.repo_work_request", + schema = "RepoWorkModelingRequest" +)] +pub struct RepoWorkModelingRequest { + #[cultcache(key = 0)] + pub schema_version: String, + #[cultcache(key = 1)] + pub request_id: String, + #[cultcache(key = 2)] + pub item: String, + #[cultcache(key = 3)] + pub requester: String, + #[cultcache(key = 4)] + pub soul_verdict_receipt_id: String, + #[cultcache(key = 5)] + pub commit_sha: String, + #[cultcache(key = 6)] + pub changed_paths: Vec, + #[cultcache(key = 7)] + pub instruction: String, + #[cultcache(key = 8)] + pub requested_at: String, + #[cultcache(key = 9)] + pub private_state_exposed: bool, + #[cultcache(key = 10)] + pub contract: String, } #[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] diff --git a/epiphany-core/src/runtime_spine.rs b/epiphany-core/src/runtime_spine.rs index 325ab8bb..598c54b4 100644 --- a/epiphany-core/src/runtime_spine.rs +++ b/epiphany-core/src/runtime_spine.rs @@ -44,8 +44,11 @@ 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; use crate::modeling_gateway::REPO_WORK_MODELING_FINDING_TYPE; +use crate::modeling_gateway::REPO_WORK_MODELING_REQUEST_SCHEMA_VERSION; +use crate::modeling_gateway::REPO_WORK_MODELING_REQUEST_TYPE; use crate::modeling_gateway::RepoWorkMapEntry; use crate::modeling_gateway::RepoWorkModelingFinding; +use crate::modeling_gateway::RepoWorkModelingRequest; use crate::organ_dependencies::EpiphanyLaunchOrganContract; use crate::soul_gateway::SoulVerdictReceipt; use crate::soul_gateway::*; @@ -730,6 +733,7 @@ pub fn runtime_spine_cache(store_path: impl AsRef) -> Result { cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; + cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; @@ -1760,6 +1764,9 @@ pub fn put_repo_work_modeling_finding( let mut cache = runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; require_identity(&cache)?; + let request = cache + .get::(&finding.request_id)? + .ok_or_else(|| anyhow!("Modeling finding requires its typed request"))?; let soul = cache .get::(&finding.soul_verdict_receipt_id)? .ok_or_else(|| anyhow!("Modeling finding requires its Soul verdict receipt"))?; @@ -1768,6 +1775,15 @@ pub fn put_repo_work_modeling_finding( "Modeling finding requires a passing Soul verdict receipt" )); } + if request.item != finding.item + || request.soul_verdict_receipt_id != finding.soul_verdict_receipt_id + || request.commit_sha != finding.commit_sha + || request.changed_paths != finding.changed_paths + { + return Err(anyhow!( + "Modeling finding does not answer its typed request" + )); + } if let Some(existing) = cache.get::(&finding.receipt_id)? { if existing == *finding { return Ok(()); @@ -1780,6 +1796,59 @@ pub fn put_repo_work_modeling_finding( Ok(()) } +pub fn put_repo_work_modeling_request( + store_path: impl AsRef, + request: &RepoWorkModelingRequest, +) -> Result<()> { + if request.schema_version != REPO_WORK_MODELING_REQUEST_SCHEMA_VERSION { + return Err(anyhow!("unsupported Modeling request schema")); + } + validate_non_empty(&request.request_id, "Modeling request id")?; + validate_non_empty(&request.item, "Modeling request item")?; + validate_non_empty(&request.requester, "Modeling request requester")?; + validate_non_empty( + &request.soul_verdict_receipt_id, + "Modeling request Soul verdict receipt id", + )?; + validate_non_empty(&request.commit_sha, "Modeling request commit sha")?; + validate_non_empty(&request.instruction, "Modeling request instruction")?; + validate_non_empty(&request.requested_at, "Modeling request timestamp")?; + if request.requester != "self" || request.private_state_exposed { + return Err(anyhow!( + "repo-work Modeling requests must be Self-routed and private-state sealed" + )); + } + let mut cache = runtime_spine_cache(store_path)?; + cache.pull_all_backing_stores()?; + require_identity(&cache)?; + let soul = cache + .get::(&request.soul_verdict_receipt_id)? + .ok_or_else(|| anyhow!("Modeling request requires its Soul verdict receipt"))?; + if soul.verdict.trim().to_ascii_lowercase() != "passed" { + return Err(anyhow!("Modeling request requires a passing Soul verdict")); + } + if let Some(existing) = cache.get::(&request.request_id)? { + if existing == *request { + return Ok(()); + } + return Err(anyhow!( + "Modeling request id already belongs to different immutable work" + )); + } + cache.put(&request.request_id, request)?; + Ok(()) +} + +pub fn runtime_repo_work_modeling_request( + store_path: impl AsRef, + request_id: &str, +) -> Result> { + validate_non_empty(request_id, "Modeling request id")?; + let mut cache = runtime_spine_cache(store_path)?; + cache.pull_all_backing_stores()?; + cache.get::(request_id) +} + pub fn runtime_repo_work_modeling_finding( store_path: impl AsRef, receipt_id: &str, @@ -2460,6 +2529,22 @@ fn epiphany_mutation_contracts() -> Vec { "Job results are evidence records; review and acceptance are separate typed flows.", ], ), + mutation_contract( + REPO_WORK_MODELING_REQUEST_TYPE, + REPO_WORK_MODELING_REQUEST_SCHEMA_VERSION, + vec![ + CultNetDocumentOperation::Snapshot, + CultNetDocumentOperation::IntentSubmit, + CultNetDocumentOperation::ReceiptWatch, + ], + CultNetMutationAuthority::Coordinator, + vec![REPO_WORK_MODELING_REQUEST_TYPE], + vec![REPO_WORK_MODELING_FINDING_TYPE], + vec![ + "Self routes Soul-verified repo consequence to Modeling through this request.", + "The request grants no authority to author the Modeling result.", + ], + ), mutation_contract( REPO_WORK_MODELING_FINDING_TYPE, REPO_WORK_MODELING_FINDING_SCHEMA_VERSION, diff --git a/notes/epiphany-current-algorithmic-map.md b/notes/epiphany-current-algorithmic-map.md index f20dfaaa..7d2830ac 100644 --- a/notes/epiphany-current-algorithmic-map.md +++ b/notes/epiphany-current-algorithmic-map.md @@ -108,6 +108,16 @@ receipt it stops at `await-modeling`; it cannot invoke closure or manufacture Modeling/Mind evidence. `epiphany-work close` requires explicit model authorship, model reference, verdict, and finding before it can admit a map. Deterministic checks remain Soul evidence, not a substitute for Modeling. The +verified consequence now crosses an explicit +`epiphany.modeling.repo_work_request.v0` boundary authored by Self. The request +is immutable, references the passing Soul receipt, commit, and changed paths, +and grants Modeling interpretation authority without granting Self authority to +write the result. A Modeling finding is refused unless it answers that exact +request and matches its verified consequence. The current close command writes +both phase documents from explicit CLI cargo; scheduler launch and asynchronous +result collection remain the next cut. + +The accepted interpretation is persisted as `epiphany.modeling.repo_work_finding.v0`; it references the passing Soul verdict, commit, and changed paths. Mind rereads that typed receipt and the admitted repo diff --git a/notes/epiphany-fork-implementation-plan.md b/notes/epiphany-fork-implementation-plan.md index 5d2c1939..ed520350 100644 --- a/notes/epiphany-fork-implementation-plan.md +++ b/notes/epiphany-fork-implementation-plan.md @@ -204,7 +204,9 @@ Delete or demote: ## Immediate Next Action -Audit the live Hands -> Soul -> Modeling -> Mind -> Self loop against the fresh -repository exit evidence. Identify which required receipt, admission, and -negative-bypass proofs are current and which are only historical claims; close -the first missing proof rather than adding another route or summary. +Split Soul verification from repo-work closure. Let Self emit the existing +typed, immutable `epiphany.modeling.repo_work_request.v0`, route it to the +Modeling daemon, and await the matching finding before the existing Mind/map +admission runs. Prove the fresh-repository path reaches Bifrost while Self +cannot synthesize Modeling evidence. The request/finding persistence boundary +and unsolicited-finding refusal already exist; do not add a parallel route. diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 2e4332c7..82003a96 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -2039,6 +2039,14 @@ returned exit 1 for a conflicting Modeling reinterpretation, and left the admitted map byte-for-byte equivalent at the operator projection. Next restore autonomous movement by having Self launch/await a real Modeling result at `await-modeling`; Self must not regain the ability to synthesize that finding. +The first nerve is now installed: `RepoWorkModelingRequest` is a typed, +immutable Self request over the passing Soul receipt, commit, and changed paths, +and a Modeling finding cannot persist unless it answers the matching request. +The runtime CultNet contract advertises the request/finding handoff. Authority +tests prove unsolicited findings are refused; all 248 library tests pass. The +close command still creates both documents from explicit CLI cargo, so the next +cut is to split Soul verification from close and let the scheduler route this +request through the Modeling daemon before immutable closure admission. ## Immediate Re-entry Instruction diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 935955acf26990fa079bd2f90dc42286f3509fbb..33a494334b7af3767efc27d1b9aba6a49d55cf60 100644 GIT binary patch delta 395 zcmX|*ze)o^5XQY|q9B5mkRmc*n?IUF(1=AIz$#XDd%Kg|;@<6Lca|ho3Jaegwa2QzZ)%NpyltH}4)^=_=f&mrcwTRflkvvA~p!L%6XrlnRdu_?yN$i zvl%$dZD(QKBTw^yo;<`hWD3YaF1#;16B`VFOVl~}Hb|SMx)=o8WFoQ|2j_wke*=HH zYnbXx@v({MECuH6lEj4l0|EX}uz0=@PbzvMRu};$psKhMj zV9*;>7?!O@$4R0^&kUve*tCv BiG~0G delta 59 zcmZ2|M)KfE$qizRyyjNM##RQ#QO6kAlILw!W-Mf6eB3;hseLLFBM^gVW*}w(V%F_b Inb>ra07~N$aR2}S diff --git a/state/map.yaml b/state/map.yaml index 6ec005ac..82dd6ac2 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -659,4 +659,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`. Gjallar 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: Restore autonomous progress at the new `await-modeling` boundary without giving Self Modeling authority. Route the repo-work scheduler through a typed Modeling launch/finding path over the Soul receipt, then let the existing immutable closure admission consume that result; prove Self cannot synthesize the finding and the loop reaches Bifrost gate on a fresh repo. + next_action: Split Soul verification from repo-work close so the scheduler can emit the new immutable `epiphany.modeling.repo_work_request.v0`, route it through the Modeling daemon, await the matching typed finding, and then invoke existing immutable Mind/map admission. Prove a fresh repo advances from `await-modeling` to the Bifrost gate while Self remains structurally unable to author the finding.