Narrow RepoModel to repository semantics

This commit is contained in:
Meta Vens 2026-08-23 15:03:12 +02:00
commit 6ea4ff3cab
14 changed files with 9 additions and 234 deletions

2
Cargo.lock generated
View file

@ -2006,7 +2006,6 @@ dependencies = [
"semver",
"serde",
"serde_json",
"sha1",
"sha2 0.10.9",
"tempfile",
"uuid",
@ -2105,7 +2104,6 @@ dependencies = [
"semver",
"serde",
"serde_json",
"sha1",
"sha2 0.10.9",
"tempfile",
"tokio",

View file

@ -47,7 +47,6 @@ rmcp = { version = "2.2.0", default-features = false, features = ["client", "tra
serde = { version = "1", features = ["derive"] }
serde_json = "1"
semver = { version = "1", features = ["serde"] }
sha1 = "0.10.6"
sha2 = "0.10"
tokio = { version = "1", features = ["io-util", "macros", "process", "rt", "rt-multi-thread", "time"] }
toml = "0.9"

View file

@ -34,7 +34,6 @@ rmp-serde = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
semver = { version = "1", features = ["serde"] }
sha1 = "0.10.6"
sha2 = "0.10"
uuid = { version = "1", features = ["serde", "v4", "v5"] }

View file

@ -92,12 +92,11 @@ fn repo_frontier_item_output_schema() -> serde_json::Value {
fn repo_model_node_output_schema() -> serde_json::Value {
serde_json::json!({
"type": "object",
"required": ["id", "domain_id", "profile", "kind", "title", "claim", "question", "tension", "action_implication"],
"required": ["id", "domain_id", "kind", "title", "claim", "question", "tension", "action_implication"],
"properties": {
"id": {"type": "string", "minLength": 1},
"domain_id": {"type": "string", "minLength": 1},
"profile": {"type": "string", "enum": ["repo_architecture", "repo_dataflow", "role_self", "short_term", "incubation", "agency_pressure", "candidate_intervention", "identity", "evidence"]},
"kind": {"type": "string", "enum": ["domain", "module", "crate", "binary", "schema", "runtime_contract", "adapter", "test_seam", "state_store", "role_memory", "short_term_thought", "incubation_thread", "agency_pressure", "candidate_intervention", "identity", "evidence", "summary", "other"]},
"kind": {"type": "string", "enum": ["domain", "module", "crate", "binary", "schema", "runtime_contract", "adapter", "test_seam", "state_store", "summary", "other"]},
"title": {"type": "string", "minLength": 1},
"claim": {"type": "string", "minLength": 1},
"question": {"type": "string"},
@ -105,7 +104,7 @@ fn repo_model_node_output_schema() -> serde_json::Value {
"action_implication": {"type": "string", "minLength": 1},
"anchors": {"type": "array", "items": memory_anchor_output_schema()},
"source_hashes": {"type": "array", "items": {"type": "string"}},
"lifecycle": {"type": "string"},
"lifecycle": {"type": "string", "enum": ["observed", "proposed", "accepted", "retired", "stale"]},
"salience": {"type": "integer", "minimum": 0},
"confidence": {"type": "integer", "minimum": 0}
},
@ -120,16 +119,15 @@ fn repo_model_node_output_schema() -> serde_json::Value {
fn repo_model_edge_output_schema() -> serde_json::Value {
serde_json::json!({
"type": "object",
"required": ["id", "source_id", "target_id", "kind", "profile", "claim"],
"required": ["id", "source_id", "target_id", "kind", "claim"],
"properties": {
"id": {"type": "string", "minLength": 1},
"source_id": {"type": "string", "minLength": 1},
"target_id": {"type": "string", "minLength": 1},
"kind": {"type": "string", "enum": ["owns", "reads", "writes", "derives", "adapts", "persists", "launches", "verifies", "supports", "contradicts", "distills", "revises", "retires", "grounds", "triggers", "spoken_as", "cools", "clusters_with", "resonates_with", "depends_on", "other"]},
"profile": {"type": "string", "enum": ["repo_architecture", "repo_dataflow", "role_self", "short_term", "incubation", "agency_pressure", "candidate_intervention", "identity", "evidence"]},
"kind": {"type": "string", "enum": ["owns", "reads", "writes", "derives", "adapts", "persists", "launches", "verifies", "supports", "contradicts", "grounds", "triggers", "depends_on", "other"]},
"claim": {"type": "string"},
"anchors": {"type": "array", "items": memory_anchor_output_schema()},
"lifecycle": {"type": "string"},
"lifecycle": {"type": "string", "enum": ["observed", "proposed", "accepted", "retired", "stale"]},
"confidence": {"type": "integer", "minimum": 0}
},
"additionalProperties": false

View file

@ -586,7 +586,6 @@ mod tests {
use epiphany_state_model::EpiphanyMemoryLifecycle;
use epiphany_state_model::EpiphanyMemoryNode;
use epiphany_state_model::EpiphanyMemoryNodeKind;
use epiphany_state_model::EpiphanyMemoryProfile;
use epiphany_state_model::RepoFrontierAdoptedPlan;
use epiphany_state_model::RepoFrontierItem;
use epiphany_state_model::RepoFrontierStatus;
@ -737,7 +736,6 @@ mod tests {
let body = observe_runtime_repository_body_basis(store)?;
let domain = EpiphanyMemoryDomain {
id: "hands-action-domain".into(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
title: "Hands action fixture".into(),
lifecycle: EpiphanyMemoryLifecycle::Accepted,
..Default::default()
@ -745,7 +743,6 @@ mod tests {
let node = EpiphanyMemoryNode {
id: "hands-action-node".into(),
domain_id: domain.id.clone(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
kind: EpiphanyMemoryNodeKind::Module,
title: "Hands action CLI".into(),
claim: "Hands consequences require exact frontier authority.".into(),

View file

@ -2,7 +2,7 @@ use anyhow::{Result, bail};
use chrono::Utc;
use epiphany_core::{
EpiphanyMemoryAnchor, EpiphanyMemoryDomain, EpiphanyMemoryLifecycle, EpiphanyMemoryNode,
EpiphanyMemoryNodeKind, EpiphanyMemoryProfile, EpiphanyRepoModelSeed,
EpiphanyMemoryNodeKind, EpiphanyRepoModelSeed,
EpiphanyRepoModelSeedDocuments, ObserveOutcome, RuntimeSpineInitOptions,
admit_repository_body_observation, bind_repository_body, bind_runtime_to_swarm,
initialize_keyed_repo_model, initialize_runtime_spine,
@ -47,7 +47,6 @@ fn main() -> Result<()> {
EpiphanyRepoModelSeedDocuments {
domains: vec![EpiphanyMemoryDomain {
id: "repository-body".to_string(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
title: "Deployed repository Body".to_string(),
description: Some(
"Cold-start substrate binding; live Modeling owns architectural expansion."
@ -58,7 +57,6 @@ fn main() -> Result<()> {
nodes: vec![EpiphanyMemoryNode {
id: "claim-deployed-repository-body".to_string(),
domain_id: "repository-body".to_string(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
kind: EpiphanyMemoryNodeKind::RuntimeContract,
title: "Runtime is bound to the deployed repository Body".to_string(),
claim: format!(

View file

@ -3597,7 +3597,7 @@ mod tests {
use super::*;
use epiphany_state_model::{
EpiphanyMemoryDomain, EpiphanyMemoryLifecycle, EpiphanyMemoryNode, EpiphanyMemoryNodeKind,
EpiphanyMemoryProfile, RepoFrontierItem, RepoFrontierStatus,
RepoFrontierItem, RepoFrontierStatus,
};
fn basis(observation_id: &str, generation: u64) -> RepositoryBodyObservationBasis {
@ -3727,7 +3727,6 @@ mod tests {
let body = crate::observe_runtime_repository_body_basis(&store)?;
let domain = EpiphanyMemoryDomain {
id: "body-domain".into(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
title: "Body".into(),
lifecycle: EpiphanyMemoryLifecycle::Accepted,
..Default::default()
@ -3735,7 +3734,6 @@ mod tests {
let node = EpiphanyMemoryNode {
id: "body-node".into(),
domain_id: domain.id.clone(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
kind: EpiphanyMemoryNodeKind::Module,
title: "Body node".into(),
claim: "The Body is typed".into(),

View file

@ -113,7 +113,6 @@ pub use epiphany_state_model::EpiphanyMemoryDomain;
pub use epiphany_state_model::EpiphanyMemoryEdge;
pub use epiphany_state_model::EpiphanyMemoryLifecycle;
pub use epiphany_state_model::EpiphanyMemoryNode;
pub use epiphany_state_model::EpiphanyMemoryProfile;
pub use epiphany_state_model::EpiphanyMemorySummary;
pub use eyes_gateway::EYES_EVIDENCE_PACKET_SCHEMA_VERSION;
pub use eyes_gateway::EYES_EVIDENCE_PACKET_TYPE;
@ -170,10 +169,6 @@ pub use memory_graph::EpiphanyMemoryNodeKind;
pub use memory_graph::RepoFrontierAdoptedPlan;
pub use memory_graph::RepoFrontierItem;
pub use memory_graph::RepoFrontierStatus;
pub use memory_graph::lifecycle_allowed_for_profile;
pub use memory_graph::memory_graph_domain_id;
pub use memory_graph::memory_graph_edge_id;
pub use memory_graph::memory_graph_node_id;
pub use memory_graph::validate_memory_graph_snapshot;
pub use mind_documents::*;
pub use packaged_release::{

View file

@ -1,87 +0,0 @@
use super::EpiphanyMemoryProfile;
use sha1::Digest;
use sha1::Sha1;
pub fn memory_graph_domain_id(
profile: EpiphanyMemoryProfile,
kind: impl AsRef<str>,
path_or_name: impl AsRef<str>,
) -> String {
let normalized = normalized_key(path_or_name.as_ref());
stable_memory_graph_id(
"memdom",
[profile_key(profile), kind.as_ref(), normalized.as_str()],
)
}
pub fn memory_graph_node_id(
domain_id: impl AsRef<str>,
kind: impl AsRef<str>,
path: impl AsRef<str>,
symbol: Option<&str>,
) -> String {
let normalized = normalized_key(path.as_ref());
stable_memory_graph_id(
"memnode",
[
domain_id.as_ref(),
kind.as_ref(),
normalized.as_str(),
symbol.unwrap_or_default(),
],
)
}
pub fn memory_graph_edge_id(
source_id: impl AsRef<str>,
target_id: impl AsRef<str>,
kind: impl AsRef<str>,
anchor_keys: impl IntoIterator<Item = impl AsRef<str>>,
) -> String {
let mut parts = vec![
source_id.as_ref().to_string(),
target_id.as_ref().to_string(),
kind.as_ref().to_string(),
];
let mut anchors = anchor_keys
.into_iter()
.map(|key| normalized_key(key.as_ref()))
.collect::<Vec<_>>();
anchors.sort();
parts.extend(anchors);
stable_memory_graph_id("memedge", parts.iter().map(String::as_str))
}
pub(crate) fn stable_memory_graph_id<'a>(
prefix: &str,
parts: impl IntoIterator<Item = &'a str>,
) -> String {
let mut hasher = Sha1::new();
for part in parts {
hasher.update(part.trim().to_lowercase().as_bytes());
hasher.update([0]);
}
format!("{prefix}-{:x}", hasher.finalize())
}
fn profile_key(profile: EpiphanyMemoryProfile) -> &'static str {
match profile {
EpiphanyMemoryProfile::RepoArchitecture => "repo_architecture",
EpiphanyMemoryProfile::RepoDataflow => "repo_dataflow",
EpiphanyMemoryProfile::RoleSelf => "role_self",
EpiphanyMemoryProfile::ShortTerm => "short_term",
EpiphanyMemoryProfile::Incubation => "incubation",
EpiphanyMemoryProfile::AgencyPressure => "agency_pressure",
EpiphanyMemoryProfile::CandidateIntervention => "candidate_intervention",
EpiphanyMemoryProfile::Identity => "identity",
EpiphanyMemoryProfile::Evidence => "evidence",
}
}
pub(crate) fn normalized_key(value: &str) -> String {
value
.trim()
.replace('\\', "/")
.trim_start_matches("./")
.to_lowercase()
}

View file

@ -1,5 +1,3 @@
pub mod ids;
pub mod profiles;
pub mod validation;
pub use epiphany_state_model::EpiphanyMemoryDomain;
@ -10,17 +8,12 @@ pub use epiphany_state_model::EpiphanyMemoryLifecycle;
pub use epiphany_state_model::EpiphanyMemoryLifecycleReceipt;
pub use epiphany_state_model::EpiphanyMemoryNode;
pub use epiphany_state_model::EpiphanyMemoryNodeKind;
pub use epiphany_state_model::EpiphanyMemoryProfile;
pub use epiphany_state_model::EpiphanyMemorySummary;
pub use epiphany_state_model::RepoFrontierAdoptedPlan;
pub use epiphany_state_model::RepoFrontierItem;
pub use epiphany_state_model::RepoFrontierStatus;
pub use ids::memory_graph_domain_id;
pub use ids::memory_graph_edge_id;
pub use ids::memory_graph_node_id;
pub use validation::EpiphanyMemoryGraphValidationError;
pub(crate) use validation::frontier_item_has_routeable_repository_scope;
pub use validation::lifecycle_allowed_for_profile;
pub(crate) use validation::repo_paths_are_canonical_and_safe;
pub use validation::validate_memory_graph_snapshot;

View file

@ -3,7 +3,6 @@ use super::EpiphanyMemoryEdge;
use super::EpiphanyMemoryGraphSnapshot;
use super::EpiphanyMemoryLifecycle;
use super::EpiphanyMemoryNode;
use super::EpiphanyMemoryProfile;
use super::EpiphanyMemorySummary;
use super::RepoFrontierStatus;
use std::collections::{HashMap, HashSet};
@ -282,12 +281,6 @@ fn validate_domain(
"domain title is required",
errors,
);
if !lifecycle_allowed_for_profile(domain.profile, domain.lifecycle) {
errors.push(EpiphanyMemoryGraphValidationError::new(
format!("{path}.lifecycle"),
"domain lifecycle is not legal for profile",
));
}
}
fn validate_node(
@ -350,12 +343,6 @@ fn validate_node(
"node requires anchors or source hash anchor:missing",
));
}
if !lifecycle_allowed_for_profile(node.profile, node.lifecycle) {
errors.push(EpiphanyMemoryGraphValidationError::new(
format!("{path}.lifecycle"),
"node lifecycle is not legal for profile",
));
}
}
fn validate_edge(
@ -389,12 +376,6 @@ fn validate_edge(
"edge claim is required",
errors,
);
if !lifecycle_allowed_for_profile(edge.profile, edge.lifecycle) {
errors.push(EpiphanyMemoryGraphValidationError::new(
format!("{path}.lifecycle"),
"edge lifecycle is not legal for profile",
));
}
}
fn validate_summary(
@ -466,36 +447,6 @@ fn validate_summary(
}
}
pub fn lifecycle_allowed_for_profile(
profile: EpiphanyMemoryProfile,
lifecycle: EpiphanyMemoryLifecycle,
) -> bool {
use EpiphanyMemoryLifecycle::*;
use EpiphanyMemoryProfile::*;
match profile {
RepoArchitecture | RepoDataflow => {
matches!(lifecycle, Observed | Proposed | Accepted | Stale | Retired)
}
RoleSelf | Identity => matches!(
lifecycle,
Proposed | Accepted | Revised | Retired | Crystallized
),
ShortTerm => matches!(
lifecycle,
Active | Clustered | Distilled | Incubated | Pruned
),
Incubation => matches!(lifecycle, Active | Deepening | Cooling | Promoted | Retired),
AgencyPressure => matches!(lifecycle, Active | Obligated | Cooling | Answered | Retired),
CandidateIntervention => {
matches!(lifecycle, Queued | Deferred | Spoken | Applied | Retired)
}
Evidence => matches!(
lifecycle,
Observed | Reviewed | Accepted | Contradicted | Superseded | Retired
),
}
}
fn collect_unique<'a>(
ids: impl IntoIterator<Item = &'a str>,
path: &str,

View file

@ -1757,7 +1757,7 @@ mod tests {
use crate::{RuntimeSpineInitOptions, initialize_runtime_spine};
use epiphany_state_model::{
EpiphanyMemoryEdgeKind, EpiphanyMemoryLifecycle, EpiphanyMemoryNodeKind,
EpiphanyMemoryProfile, RepoFrontierStatus,
RepoFrontierStatus,
};
fn bind_test_body(
@ -1800,7 +1800,6 @@ mod tests {
let body = bind_test_body(&store, "swarm-seed", "workspace-seed")?;
let domain = EpiphanyMemoryDomain {
id: "domain-seed".into(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
title: "Seed domain".into(),
lifecycle: EpiphanyMemoryLifecycle::Accepted,
..Default::default()
@ -1808,7 +1807,6 @@ mod tests {
let node = EpiphanyMemoryNode {
id: "node-seed".into(),
domain_id: domain.id.clone(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
kind: EpiphanyMemoryNodeKind::Module,
title: "Seed node".into(),
claim: "Bootstrap is keyed".into(),
@ -1963,7 +1961,6 @@ mod tests {
"domain-1",
&EpiphanyRepoModelDomainDocument::new(&EpiphanyMemoryDomain {
id: "domain-1".into(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
title: "Architecture".into(),
lifecycle: EpiphanyMemoryLifecycle::Accepted,
..Default::default()
@ -1974,7 +1971,6 @@ mod tests {
&EpiphanyRepoModelNodeDocument::new(&EpiphanyMemoryNode {
id: "node-1".into(),
domain_id: "domain-1".into(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
kind: EpiphanyMemoryNodeKind::Module,
title: "Mind".into(),
claim: "Mind state is keyed".into(),
@ -2088,7 +2084,6 @@ mod tests {
)?;
let domain = EpiphanyMemoryDomain {
id: "domain-new".into(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
title: "New domain".into(),
lifecycle: EpiphanyMemoryLifecycle::Accepted,
..Default::default()
@ -2096,7 +2091,6 @@ mod tests {
let node = |id: &str, claim: &str| EpiphanyMemoryNode {
id: id.into(),
domain_id: domain.id.clone(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
kind: EpiphanyMemoryNodeKind::Module,
title: id.into(),
claim: claim.into(),
@ -2113,7 +2107,6 @@ mod tests {
source_id: left.id.clone(),
target_id: right.id.clone(),
kind: EpiphanyMemoryEdgeKind::Writes,
profile: EpiphanyMemoryProfile::RepoArchitecture,
claim: "Left writes Right".into(),
lifecycle: EpiphanyMemoryLifecycle::Accepted,
..Default::default()
@ -2219,7 +2212,6 @@ mod tests {
let body = bind_test_body(&store, "swarm-concurrency", "workspace-concurrency")?;
let domain = EpiphanyMemoryDomain {
id: "domain-concurrency".into(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
title: "Concurrent domain".into(),
lifecycle: EpiphanyMemoryLifecycle::Accepted,
..Default::default()
@ -2246,7 +2238,6 @@ mod tests {
let node = |id: &str, claim: &str| EpiphanyMemoryNode {
id: id.into(),
domain_id: domain.id.clone(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
kind: EpiphanyMemoryNodeKind::Module,
title: id.into(),
claim: claim.into(),

View file

@ -801,7 +801,6 @@ pub fn reorient_checkpoint_from_admitted_repo_model(
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)]
pub struct EpiphanyMemoryDomain {
pub id: String,
pub profile: EpiphanyMemoryProfile,
pub title: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "string | null")]
@ -810,24 +809,6 @@ pub struct EpiphanyMemoryDomain {
pub lifecycle: EpiphanyMemoryLifecycle,
}
#[derive(
Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash, JsonSchema, TS, Default,
)]
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
pub enum EpiphanyMemoryProfile {
#[default]
RepoArchitecture,
RepoDataflow,
RoleSelf,
ShortTerm,
Incubation,
AgencyPressure,
CandidateIntervention,
Identity,
Evidence,
}
#[derive(
Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash, JsonSchema, TS, Default,
)]
@ -838,27 +819,8 @@ pub enum EpiphanyMemoryLifecycle {
Observed,
Proposed,
Accepted,
Active,
Clustered,
Distilled,
Incubated,
Pruned,
Revised,
Retired,
Crystallized,
Stale,
Deepening,
Cooling,
Promoted,
Queued,
Deferred,
Spoken,
Applied,
Obligated,
Answered,
Reviewed,
Contradicted,
Superseded,
}
#[derive(
@ -877,13 +839,6 @@ pub enum EpiphanyMemoryNodeKind {
Adapter,
TestSeam,
StateStore,
RoleMemory,
ShortTermThought,
IncubationThread,
AgencyPressure,
CandidateIntervention,
Identity,
Evidence,
Summary,
Other,
}
@ -892,7 +847,6 @@ pub enum EpiphanyMemoryNodeKind {
pub struct EpiphanyMemoryNode {
pub id: String,
pub domain_id: String,
pub profile: EpiphanyMemoryProfile,
pub kind: EpiphanyMemoryNodeKind,
pub title: String,
pub claim: String,
@ -936,15 +890,8 @@ pub enum EpiphanyMemoryEdgeKind {
Verifies,
Supports,
Contradicts,
Distills,
Revises,
Retires,
Grounds,
Triggers,
SpokenAs,
Cools,
ClustersWith,
ResonatesWith,
DependsOn,
Other,
}
@ -955,7 +902,6 @@ pub struct EpiphanyMemoryEdge {
pub source_id: String,
pub target_id: String,
pub kind: EpiphanyMemoryEdgeKind,
pub profile: EpiphanyMemoryProfile,
pub claim: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyMemoryAnchor>")]