Add typed repo work Modeling requests

This commit is contained in:
Meta Vens 2026-07-12 10:35:33 +01:00
commit 2de8df0432
9 changed files with 203 additions and 5 deletions

View file

@ -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<Value> {
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<Value> {
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<Value> {
},
"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)?;

View file

@ -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;

View file

@ -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<String>,
#[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)]

View file

@ -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<Path>) -> Result<CultCache> {
cache.register_entry_type::<MindGatewayReview>()?;
cache.register_entry_type::<MindStateCommitReceipt>()?;
cache.register_entry_type::<RepoWorkModelingFinding>()?;
cache.register_entry_type::<RepoWorkModelingRequest>()?;
cache.register_entry_type::<RepoWorkMapEntry>()?;
cache.register_entry_type::<EyesEvidencePacket>()?;
cache.register_entry_type::<SubstrateGateRepoAccessGrantReceipt>()?;
@ -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::<RepoWorkModelingRequest>(&finding.request_id)?
.ok_or_else(|| anyhow!("Modeling finding requires its typed request"))?;
let soul = cache
.get::<SoulVerdictReceipt>(&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::<RepoWorkModelingFinding>(&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<Path>,
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::<SoulVerdictReceipt>(&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::<RepoWorkModelingRequest>(&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<Path>,
request_id: &str,
) -> Result<Option<RepoWorkModelingRequest>> {
validate_non_empty(request_id, "Modeling request id")?;
let mut cache = runtime_spine_cache(store_path)?;
cache.pull_all_backing_stores()?;
cache.get::<RepoWorkModelingRequest>(request_id)
}
pub fn runtime_repo_work_modeling_finding(
store_path: impl AsRef<Path>,
receipt_id: &str,
@ -2460,6 +2529,22 @@ fn epiphany_mutation_contracts() -> Vec<CultNetDocumentMutationContract> {
"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,

View file

@ -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

View file

@ -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.

View file

@ -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

Binary file not shown.

View file

@ -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.