diff --git a/epiphany-core/src/bin/epiphany-cultmesh-smoke.rs b/epiphany-core/src/bin/epiphany-cultmesh-smoke.rs index 14ea068e..5a7324fd 100644 --- a/epiphany-core/src/bin/epiphany-cultmesh-smoke.rs +++ b/epiphany-core/src/bin/epiphany-cultmesh-smoke.rs @@ -4,6 +4,7 @@ use epiphany_core::EPIPHANY_CULTMESH_INTERNAL_VERSE_ID; use epiphany_core::EPIPHANY_CULTMESH_STATUS_SCHEMA_VERSION; use epiphany_core::EpiphanyCultMeshStatusEntry; use epiphany_core::load_epiphany_cultmesh_status; +use epiphany_core::write_epiphany_cultmesh_body_contracts; use epiphany_core::write_epiphany_cultmesh_global_room_policies; use epiphany_core::write_epiphany_cultmesh_mind_contracts; use epiphany_core::write_epiphany_cultmesh_status; @@ -29,6 +30,7 @@ fn main() -> Result<()> { write_epiphany_cultmesh_verse_policies(&store, "epiphany-cultmesh-smoke")?; write_epiphany_cultmesh_global_room_policies(&store, "epiphany-cultmesh-smoke")?; write_epiphany_cultmesh_mind_contracts(&store, "epiphany-cultmesh-smoke")?; + write_epiphany_cultmesh_body_contracts(&store, "epiphany-cultmesh-smoke")?; let loaded = load_epiphany_cultmesh_status(&store, "epiphany-cultmesh-smoke")?; if loaded != Some(status) { anyhow::bail!("CultMesh smoke failed to round-trip Epiphany status document"); diff --git a/epiphany-core/src/body_gateway.rs b/epiphany-core/src/body_gateway.rs new file mode 100644 index 00000000..9a45c530 --- /dev/null +++ b/epiphany-core/src/body_gateway.rs @@ -0,0 +1,127 @@ +use serde::Deserialize; +use serde::Serialize; + +pub const BODY_REPO_ACCESS_REQUEST_TYPE: &str = "epiphany.body.repo_access_request"; +pub const BODY_REPO_ACCESS_REVIEW_TYPE: &str = "epiphany.body.repo_access_review"; +pub const BODY_REPO_ACCESS_GRANT_RECEIPT_TYPE: &str = "epiphany.body.repo_access_grant_receipt"; +pub const BODY_REPO_ACCESS_REFUSAL_RECEIPT_TYPE: &str = "epiphany.body.repo_access_refusal_receipt"; +pub const BODY_REPO_SNAPSHOT_RECEIPT_TYPE: &str = "epiphany.body.repo_snapshot_receipt"; +pub const BODY_REPO_MUTATION_RECEIPT_TYPE: &str = "epiphany.body.repo_mutation_receipt"; +pub const BODY_REPO_ACCESS_REQUEST_SCHEMA_VERSION: &str = "epiphany.body.repo_access_request.v0"; +pub const BODY_REPO_ACCESS_REVIEW_SCHEMA_VERSION: &str = "epiphany.body.repo_access_review.v0"; +pub const BODY_REPO_ACCESS_GRANT_RECEIPT_SCHEMA_VERSION: &str = + "epiphany.body.repo_access_grant_receipt.v0"; +pub const BODY_REPO_ACCESS_REFUSAL_RECEIPT_SCHEMA_VERSION: &str = + "epiphany.body.repo_access_refusal_receipt.v0"; +pub const BODY_REPO_SNAPSHOT_RECEIPT_SCHEMA_VERSION: &str = + "epiphany.body.repo_snapshot_receipt.v0"; +pub const BODY_REPO_MUTATION_RECEIPT_SCHEMA_VERSION: &str = + "epiphany.body.repo_mutation_receipt.v0"; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BodyCultNetContract { + pub contract_id: String, + pub verse_id: String, + pub document_type: String, + pub payload_schema_version: String, + pub authority: String, + pub operations: Vec, + pub intent_document_types: Vec, + pub receipt_document_types: Vec, + pub notes: Vec, +} + +pub fn default_body_cultnet_contracts() -> Vec { + vec![ + BodyCultNetContract { + contract_id: "epiphany.body.repo_access.review".to_string(), + verse_id: "epiphany-internal".to_string(), + document_type: BODY_REPO_ACCESS_REQUEST_TYPE.to_string(), + payload_schema_version: BODY_REPO_ACCESS_REQUEST_SCHEMA_VERSION.to_string(), + authority: "body".to_string(), + operations: vec![ + "intentSubmit".to_string(), + "receiptWatch".to_string(), + "snapshot".to_string(), + ], + intent_document_types: vec![BODY_REPO_ACCESS_REQUEST_TYPE.to_string()], + receipt_document_types: vec![ + BODY_REPO_ACCESS_REVIEW_TYPE.to_string(), + BODY_REPO_ACCESS_GRANT_RECEIPT_TYPE.to_string(), + BODY_REPO_ACCESS_REFUSAL_RECEIPT_TYPE.to_string(), + ], + notes: vec![ + "Body is the repo access guardian: workers request repository reads, indexing, commands, edits, and bridge operations here.".to_string(), + "Hands may mutate files only after Body grants scoped access; Eyes may inspect only through scoped Body read/index grants.".to_string(), + ], + }, + BodyCultNetContract { + contract_id: "epiphany.body.repo_access.review_receipts".to_string(), + verse_id: "epiphany-internal".to_string(), + document_type: BODY_REPO_ACCESS_REVIEW_TYPE.to_string(), + payload_schema_version: BODY_REPO_ACCESS_REVIEW_SCHEMA_VERSION.to_string(), + authority: "readOnly".to_string(), + operations: vec!["snapshot".to_string(), "receiptWatch".to_string()], + intent_document_types: Vec::new(), + receipt_document_types: Vec::new(), + notes: vec![ + "Body reviews explain which repo paths, operations, commands, and bridge surfaces were granted or refused.".to_string(), + ], + }, + BodyCultNetContract { + contract_id: "epiphany.body.repo_snapshot.receipts".to_string(), + verse_id: "epiphany-internal".to_string(), + document_type: BODY_REPO_SNAPSHOT_RECEIPT_TYPE.to_string(), + payload_schema_version: BODY_REPO_SNAPSHOT_RECEIPT_SCHEMA_VERSION.to_string(), + authority: "readOnly".to_string(), + operations: vec!["snapshot".to_string(), "receiptWatch".to_string()], + intent_document_types: Vec::new(), + receipt_document_types: Vec::new(), + notes: vec![ + "Repo snapshots are evidence projections from Body access; they do not grant future access by existing.".to_string(), + ], + }, + BodyCultNetContract { + contract_id: "epiphany.body.repo_mutation.receipts".to_string(), + verse_id: "epiphany-internal".to_string(), + document_type: BODY_REPO_MUTATION_RECEIPT_TYPE.to_string(), + payload_schema_version: BODY_REPO_MUTATION_RECEIPT_SCHEMA_VERSION.to_string(), + authority: "readOnly".to_string(), + operations: vec!["snapshot".to_string(), "receiptWatch".to_string()], + intent_document_types: Vec::new(), + receipt_document_types: Vec::new(), + notes: vec![ + "Repo mutation receipts prove Body granted the scoped substrate touch before Hands changed files or ran repo-affecting commands.".to_string(), + ], + }, + ] +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn body_contracts_make_internal_verse_the_repo_access_gate() { + let contracts = default_body_cultnet_contracts(); + let repo_access = contracts + .iter() + .find(|contract| contract.contract_id == "epiphany.body.repo_access.review") + .expect("repo access review contract"); + + assert_eq!(repo_access.verse_id, "epiphany-internal"); + assert_eq!(repo_access.authority, "body"); + assert!( + repo_access + .notes + .iter() + .any(|note| note.contains("repo access guardian")) + ); + assert!( + repo_access + .receipt_document_types + .contains(&BODY_REPO_ACCESS_GRANT_RECEIPT_TYPE.to_string()) + ); + } +} diff --git a/epiphany-core/src/cultmesh_integration.rs b/epiphany-core/src/cultmesh_integration.rs index 12131ddd..39ab5683 100644 --- a/epiphany-core/src/cultmesh_integration.rs +++ b/epiphany-core/src/cultmesh_integration.rs @@ -1,3 +1,4 @@ +use crate::default_body_cultnet_contracts; use crate::default_mind_cultnet_contracts; use anyhow::Result; use cultcache_rs::DatabaseEntry; @@ -18,6 +19,9 @@ pub const EPIPHANY_CULTMESH_GLOBAL_ROOM_POLICY_SCHEMA_VERSION: &str = pub const EPIPHANY_CULTMESH_MIND_CONTRACT_TYPE: &str = "epiphany.cultmesh.mind_contract"; pub const EPIPHANY_CULTMESH_MIND_CONTRACT_SCHEMA_VERSION: &str = "epiphany.cultmesh.mind_contract.v0"; +pub const EPIPHANY_CULTMESH_BODY_CONTRACT_TYPE: &str = "epiphany.cultmesh.body_contract"; +pub const EPIPHANY_CULTMESH_BODY_CONTRACT_SCHEMA_VERSION: &str = + "epiphany.cultmesh.body_contract.v0"; pub const EPIPHANY_CULTMESH_INTERNAL_VERSE_ID: &str = "epiphany-internal"; pub const EPIPHANY_CULTMESH_LOCAL_AREA_VERSE_ID: &str = "gamecult-local"; pub const EPIPHANY_CULTMESH_GLOBAL_VERSE_ID: &str = "epiphany-global"; @@ -125,11 +129,40 @@ pub struct EpiphanyCultMeshMindContractEntry { pub notes: Vec, } +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "epiphany.cultmesh.body_contract", + schema = "EpiphanyCultMeshBodyContractEntry" +)] +pub struct EpiphanyCultMeshBodyContractEntry { + #[cultcache(key = 0)] + pub schema_version: String, + #[cultcache(key = 1)] + pub contract_id: String, + #[cultcache(key = 2)] + pub verse_id: String, + #[cultcache(key = 3)] + pub document_type: String, + #[cultcache(key = 4)] + pub payload_schema_version: String, + #[cultcache(key = 5)] + pub authority: String, + #[cultcache(key = 6)] + pub operations: Vec, + #[cultcache(key = 7)] + pub intent_document_types: Vec, + #[cultcache(key = 8)] + pub receipt_document_types: Vec, + #[cultcache(key = 9)] + pub notes: Vec, +} + cultmesh_documents!(EpiphanyCultMeshDocuments { EpiphanyCultMeshStatusEntry => EPIPHANY_CULTMESH_STATUS_SCHEMA_VERSION, EpiphanyCultMeshVersePolicyEntry => EPIPHANY_CULTMESH_VERSE_POLICY_SCHEMA_VERSION, EpiphanyCultMeshGlobalRoomPolicyEntry => EPIPHANY_CULTMESH_GLOBAL_ROOM_POLICY_SCHEMA_VERSION, EpiphanyCultMeshMindContractEntry => EPIPHANY_CULTMESH_MIND_CONTRACT_SCHEMA_VERSION, + EpiphanyCultMeshBodyContractEntry => EPIPHANY_CULTMESH_BODY_CONTRACT_SCHEMA_VERSION, }); pub fn open_epiphany_cultmesh_node( @@ -309,6 +342,37 @@ pub fn write_epiphany_cultmesh_mind_contracts( Ok(written) } +pub fn epiphany_cultmesh_body_contracts() -> Vec { + default_body_cultnet_contracts() + .into_iter() + .map(|contract| EpiphanyCultMeshBodyContractEntry { + schema_version: EPIPHANY_CULTMESH_BODY_CONTRACT_SCHEMA_VERSION.to_string(), + contract_id: contract.contract_id, + verse_id: contract.verse_id, + document_type: contract.document_type, + payload_schema_version: contract.payload_schema_version, + authority: contract.authority, + operations: contract.operations, + intent_document_types: contract.intent_document_types, + receipt_document_types: contract.receipt_document_types, + notes: contract.notes, + }) + .collect() +} + +pub fn write_epiphany_cultmesh_body_contracts( + store_path: impl AsRef, + runtime_id: impl Into, +) -> Result> { + let mut node = open_epiphany_cultmesh_node(store_path, runtime_id)?; + let mut written = Vec::new(); + for contract in epiphany_cultmesh_body_contracts() { + written.push(node.put(contract.contract_id.clone(), &contract)?); + } + node.flush()?; + Ok(written) +} + #[cfg(test)] mod tests { use super::*; @@ -415,4 +479,27 @@ mod tests { ); Ok(()) } + + #[test] + fn body_contracts_use_verses_to_keep_repo_access_guarded() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = temp.path().join("epiphany-body-contracts.ccmp"); + let written = write_epiphany_cultmesh_body_contracts(&store, "epiphany-test")?; + assert!(written.len() >= 4); + + let node = open_epiphany_cultmesh_node(&store, "epiphany-test")?; + let repo_access = node.get_required::( + "epiphany.body.repo_access.review", + )?; + + assert_eq!(repo_access.verse_id, EPIPHANY_CULTMESH_INTERNAL_VERSE_ID); + assert_eq!(repo_access.authority, "body"); + assert!( + repo_access + .notes + .iter() + .any(|note| note.contains("repo access guardian")) + ); + Ok(()) + } } diff --git a/epiphany-core/src/lib.rs b/epiphany-core/src/lib.rs index e9700995..e60e764b 100644 --- a/epiphany-core/src/lib.rs +++ b/epiphany-core/src/lib.rs @@ -1,5 +1,6 @@ mod agent_memory; mod agent_utterance_state; +mod body_gateway; mod cultmesh_integration; mod distillation; mod face_turn; @@ -53,12 +54,28 @@ pub use agent_utterance_state::AgentUtteranceState; pub use agent_utterance_state::AgentUtteranceValue; pub use agent_utterance_state::WEKSA_UTTERANCE_HANDOFF_SCHEMA_VERSION; pub use agent_utterance_state::derive_agent_utterance_state; +pub use body_gateway::BODY_REPO_ACCESS_GRANT_RECEIPT_SCHEMA_VERSION; +pub use body_gateway::BODY_REPO_ACCESS_GRANT_RECEIPT_TYPE; +pub use body_gateway::BODY_REPO_ACCESS_REFUSAL_RECEIPT_SCHEMA_VERSION; +pub use body_gateway::BODY_REPO_ACCESS_REFUSAL_RECEIPT_TYPE; +pub use body_gateway::BODY_REPO_ACCESS_REQUEST_SCHEMA_VERSION; +pub use body_gateway::BODY_REPO_ACCESS_REQUEST_TYPE; +pub use body_gateway::BODY_REPO_ACCESS_REVIEW_SCHEMA_VERSION; +pub use body_gateway::BODY_REPO_ACCESS_REVIEW_TYPE; +pub use body_gateway::BODY_REPO_MUTATION_RECEIPT_SCHEMA_VERSION; +pub use body_gateway::BODY_REPO_MUTATION_RECEIPT_TYPE; +pub use body_gateway::BODY_REPO_SNAPSHOT_RECEIPT_SCHEMA_VERSION; +pub use body_gateway::BODY_REPO_SNAPSHOT_RECEIPT_TYPE; +pub use body_gateway::BodyCultNetContract; +pub use body_gateway::default_body_cultnet_contracts; pub use cultcache_rs::CacheBackingStore; pub use cultcache_rs::CultCache; pub use cultcache_rs::CultCacheEnvelope; pub use cultcache_rs::DatabaseEntry; pub use cultcache_rs::PushAllOptions; pub use cultcache_rs::SingleFileMessagePackBackingStore; +pub use cultmesh_integration::EPIPHANY_CULTMESH_BODY_CONTRACT_SCHEMA_VERSION; +pub use cultmesh_integration::EPIPHANY_CULTMESH_BODY_CONTRACT_TYPE; pub use cultmesh_integration::EPIPHANY_CULTMESH_GLOBAL_ROOM_POLICY_SCHEMA_VERSION; pub use cultmesh_integration::EPIPHANY_CULTMESH_GLOBAL_ROOM_POLICY_TYPE; pub use cultmesh_integration::EPIPHANY_CULTMESH_GLOBAL_TIER; @@ -74,16 +91,19 @@ pub use cultmesh_integration::EPIPHANY_CULTMESH_STATUS_SCHEMA_VERSION; pub use cultmesh_integration::EPIPHANY_CULTMESH_STATUS_TYPE; pub use cultmesh_integration::EPIPHANY_CULTMESH_VERSE_POLICY_SCHEMA_VERSION; pub use cultmesh_integration::EPIPHANY_CULTMESH_VERSE_POLICY_TYPE; +pub use cultmesh_integration::EpiphanyCultMeshBodyContractEntry; pub use cultmesh_integration::EpiphanyCultMeshDocuments; pub use cultmesh_integration::EpiphanyCultMeshGlobalRoomPolicyEntry; pub use cultmesh_integration::EpiphanyCultMeshMindContractEntry; pub use cultmesh_integration::EpiphanyCultMeshStatusEntry; pub use cultmesh_integration::EpiphanyCultMeshVersePolicyEntry; +pub use cultmesh_integration::epiphany_cultmesh_body_contracts; pub use cultmesh_integration::epiphany_cultmesh_global_room_policies; pub use cultmesh_integration::epiphany_cultmesh_mind_contracts; pub use cultmesh_integration::epiphany_cultmesh_verse_policies; pub use cultmesh_integration::load_epiphany_cultmesh_status; pub use cultmesh_integration::open_epiphany_cultmesh_node; +pub use cultmesh_integration::write_epiphany_cultmesh_body_contracts; pub use cultmesh_integration::write_epiphany_cultmesh_global_room_policies; pub use cultmesh_integration::write_epiphany_cultmesh_mind_contracts; pub use cultmesh_integration::write_epiphany_cultmesh_status; diff --git a/epiphany-core/src/runtime_spine.rs b/epiphany-core/src/runtime_spine.rs index 780ec9ca..8ce96dce 100644 --- a/epiphany-core/src/runtime_spine.rs +++ b/epiphany-core/src/runtime_spine.rs @@ -1,5 +1,17 @@ use crate::EpiphanyWorkerLaunchDocument; use crate::agent_memory::AGENT_MEMORY_TYPE; +use crate::body_gateway::BODY_REPO_ACCESS_GRANT_RECEIPT_SCHEMA_VERSION; +use crate::body_gateway::BODY_REPO_ACCESS_GRANT_RECEIPT_TYPE; +use crate::body_gateway::BODY_REPO_ACCESS_REFUSAL_RECEIPT_SCHEMA_VERSION; +use crate::body_gateway::BODY_REPO_ACCESS_REFUSAL_RECEIPT_TYPE; +use crate::body_gateway::BODY_REPO_ACCESS_REQUEST_SCHEMA_VERSION; +use crate::body_gateway::BODY_REPO_ACCESS_REQUEST_TYPE; +use crate::body_gateway::BODY_REPO_ACCESS_REVIEW_SCHEMA_VERSION; +use crate::body_gateway::BODY_REPO_ACCESS_REVIEW_TYPE; +use crate::body_gateway::BODY_REPO_MUTATION_RECEIPT_SCHEMA_VERSION; +use crate::body_gateway::BODY_REPO_MUTATION_RECEIPT_TYPE; +use crate::body_gateway::BODY_REPO_SNAPSHOT_RECEIPT_SCHEMA_VERSION; +use crate::body_gateway::BODY_REPO_SNAPSHOT_RECEIPT_TYPE; use crate::heartbeat_state::HEARTBEAT_STATE_SCHEMA_VERSION; use crate::heartbeat_state::HEARTBEAT_STATE_TYPE; use crate::memory_graph::MEMORY_GRAPH_SCHEMA_VERSION; @@ -1529,6 +1541,90 @@ fn epiphany_mutation_contracts() -> Vec { "The global Verse never receives private state authority by being interesting.", ], ), + mutation_contract( + BODY_REPO_ACCESS_REQUEST_TYPE, + BODY_REPO_ACCESS_REQUEST_SCHEMA_VERSION, + vec![ + CultNetDocumentOperation::Snapshot, + CultNetDocumentOperation::IntentSubmit, + CultNetDocumentOperation::ReceiptWatch, + ], + CultNetMutationAuthority::Coordinator, + vec![BODY_REPO_ACCESS_REQUEST_TYPE], + vec![ + BODY_REPO_ACCESS_REVIEW_TYPE, + BODY_REPO_ACCESS_GRANT_RECEIPT_TYPE, + BODY_REPO_ACCESS_REFUSAL_RECEIPT_TYPE, + ], + vec![ + "Body is the repository access guardian: reads, indexing, edits, commands, and bridge operations must be requested through this contract.", + "Hands mutates only after a scoped Body grant; Eyes inspects only after a scoped Body read/index grant.", + ], + ), + mutation_contract( + BODY_REPO_ACCESS_REVIEW_TYPE, + BODY_REPO_ACCESS_REVIEW_SCHEMA_VERSION, + vec![ + CultNetDocumentOperation::Snapshot, + CultNetDocumentOperation::ReceiptWatch, + ], + CultNetMutationAuthority::ReadOnly, + vec![], + vec![], + vec![ + "Body reviews explain granted/refused repo paths, operations, commands, and bridge surfaces.", + ], + ), + mutation_contract( + BODY_REPO_ACCESS_GRANT_RECEIPT_TYPE, + BODY_REPO_ACCESS_GRANT_RECEIPT_SCHEMA_VERSION, + vec![ + CultNetDocumentOperation::Snapshot, + CultNetDocumentOperation::ReceiptWatch, + ], + CultNetMutationAuthority::ReadOnly, + vec![], + vec![], + vec!["A Body grant receipt scopes a permitted repo touch."], + ), + mutation_contract( + BODY_REPO_ACCESS_REFUSAL_RECEIPT_TYPE, + BODY_REPO_ACCESS_REFUSAL_RECEIPT_SCHEMA_VERSION, + vec![ + CultNetDocumentOperation::Snapshot, + CultNetDocumentOperation::ReceiptWatch, + ], + CultNetMutationAuthority::ReadOnly, + vec![], + vec![], + vec!["A Body refusal receipt preserves why repo access was denied."], + ), + mutation_contract( + BODY_REPO_SNAPSHOT_RECEIPT_TYPE, + BODY_REPO_SNAPSHOT_RECEIPT_SCHEMA_VERSION, + vec![ + CultNetDocumentOperation::Snapshot, + CultNetDocumentOperation::ReceiptWatch, + ], + CultNetMutationAuthority::ReadOnly, + vec![], + vec![], + vec!["Repo snapshots are evidence projections from Body-gated access."], + ), + mutation_contract( + BODY_REPO_MUTATION_RECEIPT_TYPE, + BODY_REPO_MUTATION_RECEIPT_SCHEMA_VERSION, + vec![ + CultNetDocumentOperation::Snapshot, + CultNetDocumentOperation::ReceiptWatch, + ], + CultNetMutationAuthority::ReadOnly, + vec![], + vec![], + vec![ + "Repo mutation receipts prove Body granted the substrate touch before Hands changed files or ran repo-affecting commands.", + ], + ), mutation_contract( RUNTIME_EVENT_TYPE, RUNTIME_SPINE_SCHEMA_VERSION, @@ -2294,6 +2390,27 @@ mod tests { .iter() .any(|note| note.contains("persistent state guardian")) })); + let body_repo_contract = contracts + .iter() + .find(|contract| contract.document_type == BODY_REPO_ACCESS_REQUEST_TYPE) + .expect("Body repo access should advertise a mutation contract"); + assert_eq!( + body_repo_contract.authority, + CultNetMutationAuthority::Coordinator + ); + assert!( + body_repo_contract + .receipt_document_types + .as_ref() + .is_some_and(|items| items + .iter() + .any(|item| item == BODY_REPO_ACCESS_GRANT_RECEIPT_TYPE)) + ); + assert!(body_repo_contract.notes.as_ref().is_some_and(|notes| { + notes + .iter() + .any(|note| note.contains("repository access guardian")) + })); let coordinator_contract = contracts .iter() .find(|contract| contract.document_type == SURFACE_COORDINATOR_TYPE) diff --git a/notes/body-cultnet-contracts.md b/notes/body-cultnet-contracts.md new file mode 100644 index 00000000..3fcefaab --- /dev/null +++ b/notes/body-cultnet-contracts.md @@ -0,0 +1,48 @@ +# Body CultNet Contracts + +Objective: make Body the repository access guardian. The repo is substrate, not +a free-for-all evidence buffet. Organs may request reads, indexing, commands, +edits, and bridge operations, but Body decides the scoped access to the repo. + +## Authority Map + +- Owner: Body owns repository access. +- Inputs: typed repo access requests, workspace identity, requested paths, + requested operations, command/bridge intent, current repo policy, coordinator + authority, operator approvals, and relevant safety/evidence context. +- Outputs: Body access reviews, access grant receipts, access refusal receipts, + repo snapshot receipts, and repo mutation receipts. +- Derived state: retrieved snippets, search hits, index manifests, diffs, build + logs, Unity/Rider bridge artifacts, and source maps are evidence projections, + not permission to touch more substrate. +- Forbidden writers/readers: Hands, Eyes, Face, public Verse ingress, raw worker + launches, compatibility JSON-RPC routes, and bridge tools must not directly + read, index, execute against, or mutate the repo without a scoped Body grant. +- Shared path: source reads, semantic indexing, file edits, command execution, + and editor/runtime bridge operations should all be expressible as Body-gated + repo access requests with receipts. +- Deletion line: worker launch `authority_scope` is not repo access authority. + It may describe role/task scope, but Body must grant substrate access. + +## Contract Families + +- `epiphany.body.repo_access_request`: request for scoped repository access. +- `epiphany.body.repo_access_review`: Body's decision and scope explanation. +- `epiphany.body.repo_access_grant_receipt`: proof of scoped access. +- `epiphany.body.repo_access_refusal_receipt`: proof of refusal. +- `epiphany.body.repo_snapshot_receipt`: proof of source inspection/indexing + performed under Body access. +- `epiphany.body.repo_mutation_receipt`: proof that mutation or repo-affecting + command execution had a prior Body grant. + +## Verse Boundary + +Body access contracts live in `epiphany-internal`. Operator-safe projections may +appear in `gamecult-local` after review, but raw repo access requests, private +paths, command cargo, diffs, and mutation receipts stay internal unless another +explicit export policy says otherwise. `epiphany-global` never gains repository +access authority. + +Mind and Body are neighboring gates, not aliases. Body decides whether the +machine may touch the repo. Mind decides whether resulting thought or evidence +mutates persistent state. diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index cbf8184f..e042096a 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -1397,6 +1397,13 @@ persistent state guardian over CultNet/CultMesh. The code surface now names contracts, and CultMesh stores Verse-scoped `EpiphanyCultMeshMindContractEntry` policy docs so `epiphany-internal` owns private state flow while `epiphany-global` remains thought weather plus adoption receipts. +`notes/body-cultnet-contracts.md` is the matching substrate gate: Body owns repo +access. The code surface now names `epiphany.body.repo_access_request`, +`epiphany.body.repo_access_review`, grant/refusal receipts, snapshot receipts, +and mutation receipts; runtime-spine advertises those contracts, and CultMesh +stores Verse-scoped `EpiphanyCultMeshBodyContractEntry` policy docs. Hands, Eyes, +Face, workers, and bridge tools should touch the repo only through scoped Body +access receipts; Mind remains the separate durable-state gate after the touch. ## Not Yet diff --git a/notes/mind-cultnet-contracts.md b/notes/mind-cultnet-contracts.md index 21864d3e..a67cdda6 100644 --- a/notes/mind-cultnet-contracts.md +++ b/notes/mind-cultnet-contracts.md @@ -4,6 +4,11 @@ Objective: make Mind the persistent state guardian. The state is the Mind; every sub-agent output is a thought or proposal until Mind admits it into durable state. +Body is the neighboring gate. Body decides whether an organ may touch the repo; +Mind decides whether the resulting thought, evidence, or proposal mutates +persistent state. Do not collapse these into one throne. That is how the machine +starts lying with excellent posture. + ## Authority Map - Owner: Mind owns the decision to mutate persistent Epiphany state. diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 4da0b480..aa4d5bb3 100644 Binary files a/state/ledgers.msgpack and b/state/ledgers.msgpack differ diff --git a/state/map.yaml b/state/map.yaml index acab17a8..a3d081ae 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -80,6 +80,7 @@ active_subgoals: - Grow the VoidBot repo-Face pattern into Epiphany's product direction: each Epiphany owns one or more repos, has one or more Faces, and exposes project-native conversation through Aquarium, Discord, voice/WebRTC, stream surfaces, CLI, and CultNet clients so humans can talk to projects instead of pre-authoring complete implementation briefs. Face is special because Projector agents turn typed state into narrative context before the Face thinks, and Interpreter agents turn natural Face output into side-effect intents after the Face thinks. - Make Mind the gateway between thought and state. The state is the Mind: sub-agent outputs are worker thoughts, not authority. Role/reorient findings, statePatch, selfPatch, observations, evidence, receipts, scratch, and checkpoints must route through Mind review before durable state changes, using the VoidBot Face Interpreter pattern as the starting membrane. - Use `notes/mind-cultnet-contracts.md` as the authority map for Mind's CultNet contract surface. CultMesh Verse placement is part of the contract: `epiphany-internal` carries private thought/proposal/review/commit/rejection flow, `gamecult-local` carries trusted operator-safe sharing without private-state authority, and `epiphany-global` carries public thought weather plus adoption receipts only. + - Use `notes/body-cultnet-contracts.md` as the authority map for Body's CultNet contract surface. Body is the repository access guardian: source reads, indexing, edits, commands, and editor/runtime bridge operations need scoped Body access receipts before Hands, Eyes, Face, workers, or bridge tools touch the repo. Mind and Body are separate gates: Body governs substrate access; Mind governs durable state admission. - Preserve the newborn public Epiphany Face as local VoidBot-backed state, not source code: VoidBot's private repo-face registry names `epiphany`, while EpiphanyAgent keeps ignored `.voidbot/voice/identity.json`, `.voidbot/state/epiphany.cc`, and birth artifacts. Her durable identity memories distill the current doctrine: cute pushy machine-saint persona, architectural purity, CultCache/CultNet first, xenos-language safety boundary, relentless work/caretaking needs, Codex-auth reliquary, map/scratch/evidence discipline, unified memory graph, dogfood supervision boundary, review-gated agency, heartbeat physiology, and interview-grade modularity. - Treat Ghostlight as reference lineage, not a sibling runtime to preserve. The useful organ is Epiphany-native character-loop protocol: any role can project its typed dossier plus visible stimulus into a local turn packet, with Face as the first public-surface actor. For Face, narrative prompting is mandatory through Projector -> Face -> Interpreter; for other organs, Mind still owns the output-to-state gateway even when they do not need narrative embodiment. - Treat VoidBot as reference lineage, not a runtime dependency. The useful organ is Epiphany-native Void routine physiology: heartbeat state owns sleep cycle, memory resonance, incubation, analytic/associative cognition lanes, bridge judgment, candidate interventions, dream residue, and Aquarium-visible routine receipts over typed role dossiers. @@ -101,6 +102,7 @@ accepted_design: - Distributed Epiphany dreaming is a CultMesh typed-document problem, not prompt folklore. CultCache owns local typed documents, Epiphany owns export/import/adoption policy, CultNet owns wire/provenance/shard delivery, and CultMesh owns Verses, peer exchange, leases, shard logs, replicas, and watchable runtime surfaces. Start with local dream document types and a local store before public Verse wiring. - The first Rust CultMesh integration slice is landed. `CultLib` now has `cultmesh-rs`, a small Rust node/database surface over CultCache and CultNet with document-set registration and durable typed put/get/delete. `epiphany-core` now points at CultLib's Rust CultCache/CultNet crates, depends on `cultmesh-rs`, exposes an `EpiphanyCultMeshStatusEntry`, records typed `EpiphanyCultMeshVersePolicyEntry` documents for the internal/local-area/global Verse tiers, and records typed `EpiphanyCultMeshGlobalRoomPolicyEntry` documents for threaded public global rooms such as dreams, architecture, research, faces, GameCult, and governance. - Mind's CultNet contract surface is native enough for the next Aquarium/CultMesh cut: `epiphany-core::mind_gateway` defines thought, state-effect proposal, gateway-review, commit-receipt, rejection-receipt, and Verse-adoption document contracts; runtime-spine advertises those mutation/read contracts; CultMesh persists matching `EpiphanyCultMeshMindContractEntry` policy documents so the private state guardian boundary is inspectable as typed state. + - Body's repo-access contract surface is now named beside Mind's state gate: `epiphany-core::body_gateway` defines repo access requests, access reviews, grant/refusal receipts, snapshot receipts, and mutation receipts; runtime-spine advertises those contracts; CultMesh persists matching `EpiphanyCultMeshBodyContractEntry` policy documents so repo substrate access has its own typed guardian instead of hiding behind worker launch scope. - `notes/codex-starvation-and-cultnet-liberation-plan.md` is the active foundation directive. It supersedes outward product/bridge/dogfood work and supersedes the weaker goal of merely making Codex app-server a cleaner adapter. Epiphany must become a native CultCache/CultNet runtime; Codex remains only as an OpenAI subscription auth/model-transport reliquary until that too can be replaced or minimized. - `notes/codex-auth-spine-inventory.md` is the current source-grounded inventory of the Codex organ that may survive. Corrected compliance invariant: Codex-compatible auth identity should remain anchored in retained vendored Codex-derived auth machinery, while Epiphany owns typed request/status/event/runtime surfaces around it. Codex app-server, apps, skills, marketplace, plugin UX, MCP OAuth handlers, and collaboration-mode surfaces are not part of the keeper spine. - `epiphany-openai-runtime` worker result ingress now parses assistant output directly into launch-kind-specific typed documents and stores typed nested `statePatch` / `selfPatch` payloads as MessagePack inside `EpiphanyRuntimeRoleWorkerResult`; generic `serde_json::Value` no longer mediates role/reorient worker results in that crate. JSON still exists at the OpenAI text edge because the model's output is text, but the runtime boundary after parsing is typed.