Delete unused semantic projection subsystem

This commit is contained in:
Meta Vens 2026-08-23 14:47:31 +02:00
commit 856648decc
41 changed files with 72 additions and 32866 deletions

207
Cargo.lock generated
View file

@ -233,16 +233,6 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "assert-json-diff"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "async-broadcast"
version = "0.7.2"
@ -543,20 +533,6 @@ dependencies = [
"piper",
]
[[package]]
name = "bm25"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cbd8ffdfb7b4c2ff038726178a780a94f90525ed0ad264c0afaa75dd8c18a64"
dependencies = [
"cached",
"deunicode",
"fxhash",
"rust-stemmers",
"stop-words",
"unicode-segmentation",
]
[[package]]
name = "borsh"
version = "1.8.0"
@ -616,39 +592,6 @@ version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]]
name = "cached"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "801927ee168e17809ab8901d9f01f700cd7d8d6a6527997fee44e4b0327a253c"
dependencies = [
"ahash",
"cached_proc_macro",
"cached_proc_macro_types",
"hashbrown 0.15.5",
"once_cell",
"thiserror 2.0.20",
"web-time",
]
[[package]]
name = "cached_proc_macro"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9225bdcf4e4a9a4c08bf16607908eb2fbf746828d5e0b5e019726dbf6571f201"
dependencies = [
"darling 0.20.11",
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "cached_proc_macro_types"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
[[package]]
name = "cbc"
version = "0.1.2"
@ -1636,38 +1579,14 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "darling"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
dependencies = [
"darling_core 0.20.11",
"darling_macro 0.20.11",
]
[[package]]
name = "darling"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
dependencies = [
"darling_core 0.23.0",
"darling_macro 0.23.0",
]
[[package]]
name = "darling_core"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim 0.11.1",
"syn 2.0.119",
"darling_core",
"darling_macro",
]
[[package]]
@ -1683,24 +1602,13 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "darling_macro"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core 0.20.11",
"quote",
"syn 2.0.119",
]
[[package]]
name = "darling_macro"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
dependencies = [
"darling_core 0.23.0",
"darling_core",
"quote",
"syn 2.0.119",
]
@ -1740,24 +1648,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "deadpool"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b"
dependencies = [
"deadpool-runtime",
"lazy_static",
"num_cpus",
"tokio",
]
[[package]]
name = "deadpool-runtime"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b"
[[package]]
name = "debugserver-types"
version = "0.5.0"
@ -1835,12 +1725,6 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "deunicode"
version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04"
[[package]]
name = "diff"
version = "0.1.13"
@ -2102,7 +1986,6 @@ name = "epiphany-core"
version = "0.1.0"
dependencies = [
"anyhow",
"bm25",
"chrono",
"ctrlc",
"cultcache-rs",
@ -2114,14 +1997,11 @@ dependencies = [
"epiphany-release-construction",
"epiphany-state-model",
"epiphany-tool-adapter",
"fs2",
"ghostlight-persona-projection",
"ignore",
"libc",
"postgres",
"pretty_assertions",
"rand_core 0.6.4",
"reqwest 0.12.28",
"rmp-serde",
"semver",
"serde",
@ -2129,12 +2009,8 @@ dependencies = [
"sha1",
"sha2 0.10.9",
"tempfile",
"tokio",
"tracing",
"uuid",
"windows-sys 0.59.0",
"wiremock",
"zeroize",
]
[[package]]
@ -2205,7 +2081,6 @@ name = "epiphany-release-bundle"
version = "0.1.0"
dependencies = [
"anyhow",
"bm25",
"chrono",
"ctrlc",
"cultcache-rs",
@ -2450,12 +2325,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foldhash"
version = "0.2.0"
@ -2764,17 +2633,6 @@ dependencies = [
"allocator-api2",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash 0.1.5",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
@ -2783,7 +2641,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash 0.2.0",
"foldhash",
]
[[package]]
@ -2990,7 +2848,6 @@ dependencies = [
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"smallvec",
@ -4056,16 +3913,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "objc2"
version = "0.6.4"
@ -5642,7 +5489,7 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e88ad84b8b6237a934534a62b379a5be6388915663c0cc598ceb9b3292bbbfe"
dependencies = [
"darling 0.23.0",
"darling",
"proc-macro2",
"quote",
"serde_json",
@ -5679,16 +5526,6 @@ dependencies = [
"serde_bytes",
]
[[package]]
name = "rust-stemmers"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54"
dependencies = [
"serde",
"serde_derive",
]
[[package]]
name = "rustc-hash"
version = "2.1.3"
@ -6212,7 +6049,7 @@ version = "3.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660"
dependencies = [
"darling 0.23.0",
"darling",
"proc-macro2",
"quote",
"syn 2.0.119",
@ -6504,15 +6341,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stop-words"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645a3d441ccf4bf47f2e4b7681461986681a6eeea9937d4c3bc9febd61d17c71"
dependencies = [
"serde_json",
]
[[package]]
name = "streaming-iterator"
version = "0.1.9"
@ -8027,29 +7855,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "wiremock"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031"
dependencies = [
"assert-json-diff",
"base64",
"deadpool",
"futures",
"http",
"http-body-util",
"hyper",
"hyper-util",
"log",
"once_cell",
"regex",
"serde",
"serde_json",
"tokio",
"url",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"

View file

@ -24,7 +24,6 @@ license-file = "LICENSE"
[dependencies]
anyhow = "1"
bm25 = "2.3.2"
chrono = "0.4.44"
ctrlc = { version = "3.5.1", features = ["termination"] }
cultcache-rs = { path = "vendor/cultcache-rs" }
@ -80,10 +79,6 @@ tungstenite = { git = "https://github.com/openai-oss-forks/tungstenite-rs", rev
[patch."ssh://git@github.com/openai-oss-forks/tungstenite-rs.git"]
tungstenite = { git = "https://github.com/openai-oss-forks/tungstenite-rs", rev = "9200079d3b54a1ff51072e24d81fd354f085156f" }
[[bin]]
name = "epiphany-daemon-supervisor"
path = "epiphany-core/src/bin/epiphany-daemon-supervisor.rs"
[[bin]]
name = "epiphany-atlas-publisher"
path = "epiphany-core/src/bin/epiphany-atlas-publisher.rs"
@ -104,18 +99,6 @@ path = "epiphany-core/src/bin/epiphany-release.rs"
name = "epiphany-state"
path = "epiphany-core/src/bin/epiphany-state.rs"
[[bin]]
name = "epiphany-memory-semantic-projector"
path = "epiphany-core/src/bin/epiphany-memory-semantic-projector.rs"
[[bin]]
name = "epiphany-workspace-coverage-projector"
path = "epiphany-core/src/bin/epiphany-workspace-coverage-projector.rs"
[[bin]]
name = "epiphany-memory-semantic"
path = "epiphany-core/src/bin/epiphany-memory-semantic.rs"
[[bin]]
name = "epiphany-repository-body"
path = "epiphany-core/src/bin/epiphany-repository-body.rs"

View file

@ -16,7 +16,6 @@ path = "src/bin/epiphany-prepare-compaction.rs"
[dependencies]
anyhow = "1"
bm25 = "2.3.2"
chrono = "0.4.44"
ctrlc = { version = "3.5.1", features = ["termination"] }
cultcache-rs = { path = "../vendor/cultcache-rs" }
@ -29,26 +28,19 @@ epiphany-tool-adapter = { path = "../epiphany-tool-adapter" }
epiphany-state-model = { path = "../epiphany-state-model" }
ghostlight-persona-projection = { git = "https://github.com/GameCult/Ghostlight.git", rev = "22281891cbf9258db80c15a9fd465e6fa251e7dd", package = "ghostlight-persona-projection" }
ed25519-dalek = "2"
fs2 = "0.4"
ignore = "0.4.23"
rand_core = { version = "0.6", features = ["getrandom"] }
zeroize = "1"
postgres = "0.19"
reqwest = { version = "0.12", features = ["blocking", "json"] }
rmp-serde = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
semver = { version = "1", features = ["serde"] }
sha1 = "0.10.6"
sha2 = "0.10"
tracing = "0.1.44"
uuid = { version = "1", features = ["serde", "v4", "v5"] }
[dev-dependencies]
pretty_assertions = "1.4.1"
tempfile = "3"
tokio = { version = "1", features = ["rt", "rt-multi-thread"] }
wiremock = "0.6"
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_Security", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Diagnostics_ToolHelp", "Win32_System_Threading"] }

File diff suppressed because it is too large Load diff

View file

@ -1,301 +0,0 @@
use anyhow::{Context, Result, anyhow};
use epiphany_core::{
EPIPHANY_CULTMESH_DAEMON_HEARTBEAT_EVENT_SCHEMA_VERSION,
EpiphanyCultMeshDaemonHeartbeatEventEntry, EpiphanyCultMeshDaemonServiceLifecycleReceiptEntry,
MemorySemanticIndexConfig, MemorySemanticProjectionInput, MemorySemanticProjectorPulseStatus,
SemanticProjectorServiceBody, authenticate_epiphany_cultmesh_semantic_projector_launch,
authorize_memory_semantic_physical_retirements, execute_memory_semantic_physical_retirement,
load_epiphany_cultmesh_daemon_service_lifecycle_receipt,
publish_epiphany_cultmesh_semantic_projection_health,
retain_memory_semantic_projection_lifecycles, write_epiphany_cultmesh_daemon_heartbeat_event,
};
use serde_json::json;
use sha2::{Digest, Sha256};
use std::env;
use std::fs;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use std::time::Instant;
use uuid::Uuid;
const DAEMON_ID: &str = "epiphany-memory-semantic-projector";
fn main() -> Result<()> {
let args = Args::parse()?;
if args.requires_managed_launch {
if args.startup_lifecycle_receipt_id.trim().is_empty() {
return Err(anyhow!(
"managed projector requires its startup launch receipt id"
));
}
let receipt = authenticate_managed_launch(&args)?;
if receipt.process_id != Some(std::process::id()) {
return Err(anyhow!(
"managed projector process disagrees with its launch receipt"
));
}
let executable = env::current_exe().context("failed to resolve projector executable")?;
let executable_sha256 = format!(
"sha256-{:x}",
Sha256::digest(fs::read(&executable).with_context(|| {
format!(
"failed to fingerprint projector executable {}",
executable.display()
)
})?)
);
if receipt.executable_sha256 != executable_sha256 {
return Err(anyhow!(
"managed projector executable disagrees with its launch receipt"
));
}
}
let mut semantic_config = MemorySemanticIndexConfig::from_env();
semantic_config.qdrant_url = args.qdrant_url.clone();
semantic_config.ollama_base_url = args.ollama_base_url.clone();
semantic_config.ollama_model = args.ollama_model.clone();
let projector =
SemanticProjectorServiceBody::new(&args.runtime_store, semantic_config.clone())?;
let provider_incarnation = projector.provider_incarnation().to_string();
let mut sequence = 0_u64;
loop {
sequence = sequence.saturating_add(1);
let pulse = projector.pulse();
let outcome = pulse.outcome;
let inputs = pulse.inputs;
let source_faults = pulse.source_fault_count;
let mut retention_faults = Vec::new();
let mut retention_mutation_count = 0_u32;
for input in &inputs {
let store = source_store(&args, input)?;
match authorize_memory_semantic_physical_retirements(
store,
input,
args.retained_generations,
&chrono::Utc::now().to_rfc3339(),
) {
Ok(retirements) => {
for retirement in retirements {
if let Err(error) = execute_memory_semantic_physical_retirement(
store,
input,
&retirement.retirement_id,
&semantic_config,
&chrono::Utc::now().to_rfc3339(),
) {
retention_faults.push(format!(
"{} physical: {error:#}",
input.obligation().partition
));
}
}
}
Err(error) => retention_faults.push(format!(
"{} physical authorization: {error:#}",
input.obligation().partition
)),
}
match retain_memory_semantic_projection_lifecycles(
store,
input,
args.retained_generations,
&chrono::Utc::now().to_rfc3339(),
) {
Ok(Some(_)) => retention_mutation_count += 1,
Ok(None) => {}
Err(error) => {
retention_faults.push(format!("{}: {error:#}", input.obligation().partition))
}
}
}
let retention_fault_count = retention_faults.len() as u32;
let mut health_publication_faults = Vec::new();
for input in &inputs {
let store = source_store(&args, input)?;
if let Err(error) = publish_epiphany_cultmesh_semantic_projection_health(
&args.local_verse_store,
args.runtime_id.clone(),
store,
input,
&provider_incarnation,
) {
health_publication_faults
.push(format!("{}: {error:#}", input.obligation().partition));
}
}
let health_faults = health_publication_faults.len() as u32;
let degraded = source_faults > 0
|| retention_fault_count > 0
|| health_faults > 0
|| matches!(
outcome.status,
MemorySemanticProjectorPulseStatus::Contended
| MemorySemanticProjectorPulseStatus::Refused
);
let heartbeat = write_epiphany_cultmesh_daemon_heartbeat_event(
&args.local_verse_store,
args.runtime_id.clone(),
EpiphanyCultMeshDaemonHeartbeatEventEntry {
schema_version: EPIPHANY_CULTMESH_DAEMON_HEARTBEAT_EVENT_SCHEMA_VERSION.to_string(),
heartbeat_id: Uuid::new_v4().to_string(),
daemon_id: DAEMON_ID.to_string(),
cluster_id: "local".to_string(),
provider_incarnation: provider_incarnation.clone(),
sequence,
status: if degraded { "degraded" } else { "ready" }.to_string(),
heartbeat_at: chrono::Utc::now().to_rfc3339(),
private_state_exposed: false,
startup_lifecycle_receipt_id: args.startup_lifecycle_receipt_id.clone(),
},
)?;
println!(
"{}",
serde_json::to_string(&json!({
"schemaVersion": "epiphany.memory_semantic_projector_pulse.v0",
"pulseSequence": sequence,
"providerIncarnation": provider_incarnation,
"heartbeatId": heartbeat.heartbeat_id,
"providerStatus": heartbeat.status,
"pulseStatus": pulse_status(outcome.status),
"inspectedSourceCount": outcome.inspections.len(),
"sourceFaultCount": source_faults,
"retentionMutationCount": retention_mutation_count,
"retentionFaultCount": retention_fault_count,
"retentionFaults": retention_faults,
"healthPublicationFaultCount": health_faults,
"healthPublicationFaults": health_publication_faults,
"selectedScopeId": outcome.selected_scope_id,
"privateStateExposed": false,
"authoritative": false
}))?
);
if args.max_iterations != 0 && sequence >= args.max_iterations {
break;
}
thread::sleep(Duration::from_secs(args.interval_seconds));
}
Ok(())
}
fn authenticate_managed_launch(
args: &Args,
) -> Result<EpiphanyCultMeshDaemonServiceLifecycleReceiptEntry> {
let deadline = Instant::now() + Duration::from_secs(5);
loop {
if load_epiphany_cultmesh_daemon_service_lifecycle_receipt(
&args.local_verse_store,
args.runtime_id.clone(),
&args.startup_lifecycle_receipt_id,
)?
.is_some()
{
return authenticate_epiphany_cultmesh_semantic_projector_launch(
&args.local_verse_store,
args.runtime_id.clone(),
&args.startup_lifecycle_receipt_id,
);
}
if Instant::now() >= deadline {
return Err(anyhow!(
"managed projector startup launch receipt was not persisted"
));
}
thread::sleep(Duration::from_millis(20));
}
}
fn source_store<'a>(args: &'a Args, input: &MemorySemanticProjectionInput) -> Result<&'a PathBuf> {
match input.obligation().partition.as_str() {
"modeling" => Ok(&args.runtime_store),
other => Err(anyhow!(
"unsupported semantic projection partition {other:?}"
)),
}
}
fn pulse_status(status: MemorySemanticProjectorPulseStatus) -> &'static str {
match status {
MemorySemanticProjectorPulseStatus::Idle => "idle",
MemorySemanticProjectorPulseStatus::Executed => "executed",
MemorySemanticProjectorPulseStatus::Contended => "contended",
MemorySemanticProjectorPulseStatus::Refused => "refused",
MemorySemanticProjectorPulseStatus::Busy => "busy",
}
}
struct Args {
runtime_store: PathBuf,
local_verse_store: PathBuf,
runtime_id: String,
interval_seconds: u64,
max_iterations: u64,
retained_generations: usize,
startup_lifecycle_receipt_id: String,
qdrant_url: String,
ollama_base_url: String,
ollama_model: String,
requires_managed_launch: bool,
}
impl Args {
fn parse() -> Result<Self> {
let mut values = env::args().skip(1);
let command = values.next().unwrap_or_else(|| "pulse".to_string());
if !matches!(command.as_str(), "pulse" | "once" | "serve" | "daemon") {
return Err(anyhow!("unknown command {command:?}; use pulse or serve"));
}
let mut runtime_store = None;
let mut local_verse_store = None;
let mut runtime_id = "epiphany-local".to_string();
let mut interval_seconds = 60_u64;
let mut max_iterations = if matches!(command.as_str(), "pulse" | "once") {
1
} else {
0
};
let mut retained_generations = 256_usize;
let mut qdrant_url = None;
let mut ollama_base_url = None;
let mut ollama_model = "qwen3-embedding:0.6b".to_string();
while let Some(flag) = values.next() {
let mut value = || {
values
.next()
.with_context(|| format!("missing value for {flag}"))
};
match flag.as_str() {
"--runtime-store" => runtime_store = Some(PathBuf::from(value()?)),
"--local-verse-store" => local_verse_store = Some(PathBuf::from(value()?)),
"--runtime-id" => runtime_id = value()?,
"--interval-seconds" => interval_seconds = value()?.parse()?,
"--max-iterations" => max_iterations = value()?.parse()?,
"--retained-generations" => retained_generations = value()?.parse()?,
"--qdrant-url" => qdrant_url = Some(value()?),
"--ollama-base-url" => ollama_base_url = Some(value()?),
"--ollama-model" => ollama_model = value()?,
_ => return Err(anyhow!("unexpected argument {flag:?}")),
}
}
if interval_seconds == 0 {
return Err(anyhow!("--interval-seconds must be positive"));
}
if retained_generations == 0 {
return Err(anyhow!("--retained-generations must be positive"));
}
Ok(Self {
runtime_store: runtime_store.context("missing --runtime-store")?,
local_verse_store: local_verse_store.context("missing --local-verse-store")?,
runtime_id,
interval_seconds,
max_iterations,
retained_generations,
startup_lifecycle_receipt_id: env::var("EPIPHANY_STARTUP_LIFECYCLE_RECEIPT_ID")
.unwrap_or_default(),
qdrant_url: qdrant_url.context("missing --qdrant-url")?,
ollama_base_url: ollama_base_url.context("missing --ollama-base-url")?,
ollama_model,
requires_managed_launch: matches!(command.as_str(), "serve" | "daemon"),
})
}
}

View file

@ -1,258 +0,0 @@
use anyhow::{Context, Result, anyhow};
use epiphany_core::{
EpiphanyMemoryContextQuery, EpiphanyMemoryGraphSnapshot, MemorySemanticIndexConfig,
MemorySemanticProjectionInput, WORKSPACE_COVERAGE_MAXIMUM_FILE_BYTES,
WorkspaceCoveragePolicy, WorkspaceProjectionIdentity,
load_memory_semantic_projection_readiness, observe_repository_readiness,
publish_epiphany_cultmesh_semantic_projection_health,
runtime_modeling_semantic_projection_input, semantic_memory_context,
};
use std::env;
use std::path::PathBuf;
fn main() -> Result<()> {
let mut args = env::args().skip(1);
let command = args.next().ok_or_else(|| usage_error("missing command"))?;
let options = Options::parse(args)?;
let config = MemorySemanticIndexConfig::from_env();
match command.as_str() {
"health" => {
let (input, source_store) = options.load_projection_input()?;
let verse_store = options
.local_verse_store
.as_ref()
.ok_or_else(|| usage_error("health requires --local-verse-store <path>"))?;
let health = publish_epiphany_cultmesh_semantic_projection_health(
verse_store,
options.runtime_id.clone(),
source_store,
&input,
options.provider_incarnation()?,
)?;
print_semantic_health(&health)?;
}
"context" => {
let (snapshot, swarm_id) = options.load_source()?;
let text = options
.text
.clone()
.ok_or_else(|| usage_error("context requires --text <query>"))?;
let projection_input = options.load_projection_input().ok().map(|(input, _)| input);
let readiness = match (&projection_input, options.source_store()) {
(Some(input), Some(store)) => {
load_memory_semantic_projection_readiness(store, input)?
}
_ => None,
};
let packet = semantic_memory_context(
&snapshot,
&swarm_id,
&EpiphanyMemoryContextQuery {
id: options
.query_id
.clone()
.unwrap_or_else(|| "memory-semantic-context".to_string()),
profile: options.profile,
text: Some(text),
budget: options.budget,
..Default::default()
},
readiness.as_ref(),
&config,
);
print_json(&packet)?;
}
"repository-readiness" => {
let runtime_store = options.runtime_store.as_ref().ok_or_else(|| {
usage_error("repository-readiness requires --runtime-store <path>")
})?;
let workspace_identity = WorkspaceProjectionIdentity {
policy: WorkspaceCoveragePolicy::bounded_regular_files_v0(
WORKSPACE_COVERAGE_MAXIMUM_FILE_BYTES,
)?,
qdrant_url: config.qdrant_url.clone(),
qdrant_api_key: config.qdrant_api_key.clone(),
qdrant_timeout_ms: config.qdrant_timeout_ms,
embedding_provider_id: options.workspace_embedding_provider_id.clone().ok_or_else(
|| {
usage_error(
"repository-readiness requires --workspace-embedding-provider-id <id>",
)
},
)?,
embedding_model: options.workspace_embedding_model.clone().ok_or_else(|| {
usage_error("repository-readiness requires --workspace-embedding-model <model>")
})?,
vector_dimensions: options.workspace_vector_dimensions.ok_or_else(|| {
usage_error("repository-readiness requires --workspace-vector-dimensions <n>")
})?,
};
print_json(&observe_repository_readiness(
runtime_store,
&config,
&workspace_identity,
)?)?;
}
_ => return Err(usage_error(&format!("unknown command {command:?}"))),
}
Ok(())
}
struct Options {
runtime_store: Option<PathBuf>,
local_verse_store: Option<PathBuf>,
runtime_id: String,
provider_incarnation: Option<String>,
swarm_id: Option<String>,
text: Option<String>,
query_id: Option<String>,
budget: Option<u32>,
profile: Option<epiphany_core::EpiphanyMemoryProfile>,
workspace_embedding_provider_id: Option<String>,
workspace_embedding_model: Option<String>,
workspace_vector_dimensions: Option<u32>,
}
impl Options {
fn parse(args: impl Iterator<Item = String>) -> Result<Self> {
let mut options = Self {
runtime_store: None,
local_verse_store: None,
runtime_id: "epiphany-memory-semantic".to_string(),
provider_incarnation: None,
swarm_id: None,
text: None,
query_id: None,
budget: None,
profile: None,
workspace_embedding_provider_id: None,
workspace_embedding_model: None,
workspace_vector_dimensions: None,
};
let mut args = args.peekable();
while let Some(flag) = args.next() {
let mut value = || {
args.next()
.with_context(|| format!("missing value for {flag}"))
};
match flag.as_str() {
"--runtime-store" => options.runtime_store = Some(PathBuf::from(value()?)),
"--local-verse-store" => options.local_verse_store = Some(PathBuf::from(value()?)),
"--runtime-id" => options.runtime_id = value()?,
"--provider-incarnation" => options.provider_incarnation = Some(value()?),
"--swarm-id" => options.swarm_id = Some(value()?),
"--text" => options.text = Some(value()?),
"--query-id" => options.query_id = Some(value()?),
"--budget" => options.budget = Some(value()?.parse().context("invalid budget")?),
"--profile" => options.profile = Some(parse_profile(&value()?)?),
"--workspace-embedding-provider-id" => {
options.workspace_embedding_provider_id = Some(value()?)
}
"--workspace-embedding-model" => options.workspace_embedding_model = Some(value()?),
"--workspace-vector-dimensions" => {
options.workspace_vector_dimensions = Some(
value()?
.parse()
.context("invalid workspace vector dimensions")?,
)
}
_ => return Err(usage_error(&format!("unexpected argument {flag:?}"))),
}
}
if options
.swarm_id
.as_ref()
.is_some_and(|id| id.trim().is_empty())
{
return Err(usage_error("--swarm-id must not be empty"));
}
if options.runtime_store.is_none() {
return Err(usage_error("provide --runtime-store"));
}
Ok(options)
}
fn load_source(&self) -> Result<(EpiphanyMemoryGraphSnapshot, String)> {
let (input, _) = self.load_projection_input()?;
Ok((
input.snapshot().clone(),
input.obligation().swarm_id.clone(),
))
}
fn load_projection_input(&self) -> Result<(MemorySemanticProjectionInput, &PathBuf)> {
let store = self
.runtime_store
.as_ref()
.ok_or_else(|| anyhow!("semantic projection requires the runtime store"))?;
let input = runtime_modeling_semantic_projection_input(store)?;
if self
.swarm_id
.as_ref()
.is_some_and(|claimed| claimed != &input.obligation().swarm_id)
{
return Err(anyhow!(
"--swarm-id does not match canonical source identity"
));
}
Ok((input, store))
}
fn source_store(&self) -> Option<&PathBuf> {
self.runtime_store.as_ref()
}
fn provider_incarnation(&self) -> Result<&str> {
self.provider_incarnation
.as_deref()
.filter(|value| !value.trim().is_empty())
.ok_or_else(|| {
usage_error("CultMesh health publication requires --provider-incarnation <id>")
})
}
}
fn parse_profile(value: &str) -> Result<epiphany_core::EpiphanyMemoryProfile> {
serde_json::from_str(&format!("\"{value}\""))
.with_context(|| format!("unknown memory profile {value:?}"))
}
fn usage_error(message: &str) -> anyhow::Error {
anyhow!(
"{message}\nusage: epiphany-memory-semantic <context|health|repository-readiness> --runtime-store <path> [--swarm-id <id>] [--local-verse-store <path> --runtime-id <id> --provider-incarnation <id>] [--text <query>] [--query-id <id>] [--budget <n>] [--profile <profile>] [--workspace-embedding-provider-id <id> --workspace-embedding-model <model> --workspace-vector-dimensions <n>]"
)
}
fn print_json(value: &impl serde::Serialize) -> Result<()> {
println!("{}", serde_json::to_string_pretty(value)?);
Ok(())
}
fn print_semantic_health(
health: &epiphany_core::EpiphanyCultMeshSemanticProjectionHealthEntry,
) -> Result<()> {
print_json(&serde_json::json!({
"schemaVersion": health.schema_version,
"verseId": health.verse_id,
"verseTier": health.verse_tier,
"swarmId": health.swarm_id,
"partition": health.partition,
"obligationId": health.obligation_id,
"sourceGeneration": health.source_generation,
"canonicalModelHash": health.canonical_model_hash,
"canonicalContentSetHash": health.canonical_content_set_hash,
"status": health.status,
"receiptId": health.receipt_id,
"indexedDocumentCount": health.indexed_document_count,
"vectorDimensions": health.vector_dimensions,
"observedAt": health.observed_at,
"observedSourceAt": health.observed_source_at,
"providerId": health.provider_id,
"providerIncarnation": health.provider_incarnation,
"authoritative": health.authoritative,
"nonAuthoritative": !health.authoritative,
"authority": "sight-only",
"queryEligibleDisplayOnly": health.query_eligible_display_only,
"privateStateExposed": health.private_state_exposed,
}))
}

View file

@ -5,7 +5,7 @@ use epiphany_core::{
EpiphanyMemoryNodeKind, EpiphanyMemoryProfile, EpiphanyRepoModelSeed,
EpiphanyRepoModelSeedDocuments, ObserveOutcome, RuntimeSpineInitOptions,
admit_repository_body_observation, bind_repository_body, bind_runtime_to_swarm,
bind_runtime_workspace_coverage_store, initialize_keyed_repo_model, initialize_runtime_spine,
initialize_keyed_repo_model, initialize_runtime_spine,
load_current_runtime_repository_body_basis, load_repository_body_status,
observe_repository_body,
};
@ -21,8 +21,6 @@ fn main() -> Result<()> {
let repo = PathBuf::from(required(&args, "--repo")?);
let store = PathBuf::from(required(&args, "--store")?);
let runtime_store = PathBuf::from(required(&args, "--runtime-store")?);
let workspace_coverage_store =
PathBuf::from(required(&args, "--workspace-coverage-store")?);
let workspace_id = required(&args, "--workspace-id")?;
let runtime_id = required(&args, "--runtime-id")?;
let swarm_id = required(&args, "--swarm-id")?;
@ -37,7 +35,6 @@ fn main() -> Result<()> {
)?;
bind_runtime_to_swarm(&runtime_store, swarm_id, &at)?;
let binding = bind_repository_body(&repo, &store, &runtime_store, workspace_id)?;
bind_runtime_workspace_coverage_store(&runtime_store, &workspace_coverage_store, &at)?;
observe_repository_body(&repo, &store, &runtime_store)?;
let body_basis = load_current_runtime_repository_body_basis(&runtime_store)?;
admit_repository_body_observation(&runtime_store, &body_basis)?;
@ -173,7 +170,7 @@ fn required<'a>(args: &'a [String], name: &str) -> Result<&'a str> {
}
fn usage<T>() -> Result<T> {
bail!(
"usage: epiphany-repository-body bootstrap --repo PATH --store PATH --runtime-store PATH --workspace-coverage-store PATH --workspace-id ID --runtime-id ID --swarm-id ID | bind --repo PATH --store PATH --runtime-store PATH --workspace-id ID | observe --repo PATH --store PATH --runtime-store PATH | status --store PATH | smoke"
"usage: epiphany-repository-body bootstrap --repo PATH --store PATH --runtime-store PATH --workspace-id ID --runtime-id ID --swarm-id ID | bind --repo PATH --store PATH --runtime-store PATH --workspace-id ID | observe --repo PATH --store PATH --runtime-store PATH | status --store PATH | smoke"
)
}
fn smoke() -> Result<()> {
@ -183,7 +180,6 @@ fn smoke() -> Result<()> {
uuid::Uuid::new_v4()
));
let runtime_store = store.with_extension("runtime.cc");
let workspace_coverage_store = store.with_extension("workspace-coverage.cc");
initialize_runtime_spine(
&runtime_store,
RuntimeSpineInitOptions {
@ -203,11 +199,6 @@ fn smoke() -> Result<()> {
&runtime_store,
"epiphany-repository-body-smoke",
)?;
bind_runtime_workspace_coverage_store(
&runtime_store,
&workspace_coverage_store,
"2026-07-15T00:00:02Z",
)?;
let outcome = observe_repository_body(&root, &store, &runtime_store)?;
let basis = load_current_runtime_repository_body_basis(&runtime_store)?;
admit_repository_body_observation(&runtime_store, &basis)?;
@ -215,8 +206,6 @@ fn smoke() -> Result<()> {
.ok_or_else(|| anyhow::anyhow!("smoke observation missing after commit"))?;
std::fs::remove_file(&store)?;
std::fs::remove_file(&runtime_store)?;
std::fs::remove_file(&workspace_coverage_store)?;
let _ = std::fs::remove_file(workspace_coverage_store.with_extension("cc.lock"));
let generation = match outcome {
ObserveOutcome::Created(value) | ObserveOutcome::Unchanged(value) => value.generation,
};

View file

@ -1,370 +0,0 @@
use anyhow::{Context, Result, anyhow};
use ed25519_dalek::SigningKey;
use epiphany_core::{
WORKSPACE_COVERAGE_PROVIDER_HEARTBEAT_SCHEMA_VERSION,
WorkspaceCoverageManagedProcessLaunchEntry, WorkspaceCoverageProjectorConfig,
WorkspaceCoverageProjectorPulseStatus, WorkspaceCoverageProjectorServiceBody,
WorkspaceCoverageProviderHeartbeatEntry,
authenticate_workspace_coverage_managed_process_launch, capture_process_instance,
load_workspace_coverage_managed_process_launch,
load_workspace_coverage_managed_process_launch_with_digest, native_boot_identity,
open_default_host_identity, read_workspace_coverage_process_bootstrap,
sign_workspace_coverage_heartbeat, write_workspace_coverage_provider_heartbeat,
};
use serde_json::json;
use std::env;
use std::io;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use uuid::Uuid;
use zeroize::Zeroize;
fn main() -> Result<()> {
let args = Args::parse()?;
let mut bootstrap = read_workspace_coverage_process_bootstrap(io::stdin().lock())?;
if bootstrap.launch_id.to_string() != args.managed_process_launch_id {
return Err(anyhow!(
"bootstrap launch id disagrees with launch environment"
));
}
let provider_key = SigningKey::from_bytes(&bootstrap.provider_signing_seed);
bootstrap.provider_signing_seed.zeroize();
let (launch, launch_digest) = authenticate_managed_launch(&args, &provider_key)?;
let mut config = WorkspaceCoverageProjectorConfig::from_env();
config.qdrant_url = args.qdrant_url.clone();
config.ollama_base_url = args.ollama_base_url.clone();
config.ollama_model = args.ollama_model.clone();
let mut projector = WorkspaceCoverageProjectorServiceBody::new(
&args.runtime_store,
&args.runtime_id,
config,
&launch.provider_incarnation_id,
&launch.launch_id,
)?;
let trusted_host = open_default_host_identity()?;
projector.install_execution_authority(
&args.local_verse_store,
&args.runtime_id,
trusted_host.entry().clone(),
provider_key.clone(),
)?;
let first_heartbeat = publish_heartbeat(&args, &launch, &launch_digest, &provider_key, 1)?;
let latest_heartbeat = Arc::new(Mutex::new(HeartbeatProjection {
heartbeat_id: first_heartbeat.heartbeat_id,
provider_status: first_heartbeat.status,
}));
spawn_heartbeat_nerve(
&args,
&launch,
&launch_digest,
&provider_key,
Arc::clone(&latest_heartbeat),
)?;
let mut pulse_sequence = 0_u64;
loop {
pulse_sequence = pulse_sequence
.checked_add(1)
.ok_or_else(|| anyhow!("workspace coverage pulse sequence exhausted"))?;
let pulse = projector.pulse();
let heartbeat = latest_heartbeat
.lock()
.map_err(|_| anyhow!("workspace coverage heartbeat projection lock poisoned"))?
.clone();
println!(
"{}",
serde_json::to_string(&json!({
"schemaVersion": "epiphany.workspace_coverage_projector_pulse.v0",
"pulseSequence": pulse_sequence,
"providerIncarnation": projector.provider_incarnation(),
"heartbeatId": heartbeat.heartbeat_id,
"providerStatus": heartbeat.provider_status,
"pulseStatus": pulse_status(pulse.status),
"bodyObservationId": pulse.body_observation_id,
"bodyGeneration": pulse.body_generation,
"planId": pulse.plan_id,
"receiptId": pulse.receipt_id,
"fault": pulse.fault,
"privateStateExposed": false,
"authoritative": false
}))?
);
thread::sleep(Duration::from_secs(args.interval_seconds));
}
}
#[derive(Clone)]
struct HeartbeatProjection {
heartbeat_id: String,
provider_status: String,
}
fn spawn_heartbeat_nerve(
args: &Args,
launch: &WorkspaceCoverageManagedProcessLaunchEntry,
launch_digest: &str,
provider_key: &SigningKey,
latest: Arc<Mutex<HeartbeatProjection>>,
) -> Result<()> {
let local_verse_store = args.local_verse_store.clone();
let runtime_id = args.runtime_id.clone();
let interval = Duration::from_secs(args.heartbeat_interval_seconds);
let launch = launch.clone();
let launch_digest = launch_digest.to_string();
let provider_key = provider_key.clone();
thread::Builder::new()
.name("workspace-coverage-heartbeat".into())
.spawn(move || {
let mut sequence = 1_u64;
loop {
thread::sleep(interval);
sequence = match sequence.checked_add(1) {
Some(sequence) => sequence,
None => {
fatal_heartbeat_nerve("workspace coverage heartbeat sequence exhausted");
}
};
let heartbeat = match publish_heartbeat_to(
&local_verse_store,
&runtime_id,
&launch,
&launch_digest,
&provider_key,
sequence,
) {
Ok(heartbeat) => heartbeat,
Err(error) => {
fatal_heartbeat_nerve(&format!(
"workspace coverage heartbeat publication failed: {error:#}"
));
}
};
match latest.lock() {
Ok(mut projection) => {
projection.heartbeat_id = heartbeat.heartbeat_id;
projection.provider_status = heartbeat.status;
}
Err(_) => {
fatal_heartbeat_nerve(
"workspace coverage heartbeat projection lock poisoned",
);
}
}
}
})
.context("failed to start workspace coverage heartbeat nerve")?;
Ok(())
}
fn fatal_heartbeat_nerve(message: &str) -> ! {
eprintln!("{message}");
// This exact process owns both projection and its authenticated liveness
// publisher. Continuing after the nerve dies would leave an unfenced
// actuator relying on an external repair loop to notice the corpse.
std::process::exit(1)
}
fn publish_heartbeat(
args: &Args,
launch: &WorkspaceCoverageManagedProcessLaunchEntry,
launch_digest: &str,
provider_key: &SigningKey,
sequence: u64,
) -> Result<WorkspaceCoverageProviderHeartbeatEntry> {
publish_heartbeat_to(
&args.local_verse_store,
&args.runtime_id,
launch,
launch_digest,
provider_key,
sequence,
)
}
fn publish_heartbeat_to(
local_verse_store: &PathBuf,
runtime_id: &str,
launch: &WorkspaceCoverageManagedProcessLaunchEntry,
launch_digest: &str,
provider_key: &SigningKey,
sequence: u64,
) -> Result<WorkspaceCoverageProviderHeartbeatEntry> {
let mut heartbeat = WorkspaceCoverageProviderHeartbeatEntry {
schema_version: WORKSPACE_COVERAGE_PROVIDER_HEARTBEAT_SCHEMA_VERSION.to_string(),
heartbeat_id: Uuid::new_v4().to_string(),
launch_id: launch.launch_id.clone(),
launch_envelope_digest: launch_digest.to_string(),
service_id: launch.service_id.clone(),
provider_daemon_id: launch.provider_daemon_id.clone(),
runtime_id: launch.runtime_id.clone(),
host_identity_id: launch.host_identity_id.clone(),
host_identity_record_digest: launch.host_identity_record_digest.clone(),
boot_identity: launch.boot_identity.clone(),
process_id: launch.process_id,
process_creation_token: launch.process_creation_token,
process_executable_path: launch.process_executable_path.clone(),
provider_incarnation_id: launch.provider_incarnation_id.clone(),
provider_public_key: launch.provider_public_key.clone(),
sequence,
// Heartbeat proves only that this exact provider process remains able
// to publish. Projection faults and progress belong to separate state.
status: "ready".to_string(),
observed_at_utc: chrono::Utc::now().to_rfc3339(),
provider_signature: Vec::new(),
signature_algorithm: "ed25519".to_string(),
};
sign_workspace_coverage_heartbeat(&mut heartbeat, provider_key)?;
write_workspace_coverage_provider_heartbeat(
local_verse_store,
runtime_id.to_string(),
heartbeat,
)
}
fn authenticate_managed_launch(
args: &Args,
provider_key: &SigningKey,
) -> Result<(WorkspaceCoverageManagedProcessLaunchEntry, String)> {
let host = open_default_host_identity()?;
let deadline = Instant::now() + Duration::from_secs(5);
loop {
if load_workspace_coverage_managed_process_launch(
&args.local_verse_store,
args.runtime_id.clone(),
&args.managed_process_launch_id,
)?
.is_some()
{
let launch = authenticate_workspace_coverage_managed_process_launch(
&args.local_verse_store,
args.runtime_id.clone(),
&args.managed_process_launch_id,
host.entry(),
)?;
let own = capture_process_instance(std::process::id())?;
let boot = native_boot_identity()
.ok_or_else(|| anyhow!("native boot identity is not provable"))?;
if launch.boot_identity != boot
|| launch.process_id != own.process_id
|| launch.process_creation_token != own.creation_token
|| PathBuf::from(&launch.process_executable_path) != own.executable_path
|| launch.provider_public_key != provider_key.verifying_key().to_bytes()
{
return Err(anyhow!(
"managed launch disagrees with this exact provider process"
));
}
let (_, digest) = load_workspace_coverage_managed_process_launch_with_digest(
&args.local_verse_store,
args.runtime_id.clone(),
&args.managed_process_launch_id,
)?
.ok_or_else(|| anyhow!("managed launch disappeared"))?;
return Ok((launch, digest));
}
if Instant::now() >= deadline {
return Err(anyhow!(
"managed workspace coverage projector launch was not persisted"
));
}
thread::sleep(Duration::from_millis(20));
}
}
fn pulse_status(status: WorkspaceCoverageProjectorPulseStatus) -> &'static str {
match status {
WorkspaceCoverageProjectorPulseStatus::Idle => "idle",
WorkspaceCoverageProjectorPulseStatus::Executed => "executed",
WorkspaceCoverageProjectorPulseStatus::Contended => "contended",
WorkspaceCoverageProjectorPulseStatus::Refused => "refused",
}
}
struct Args {
runtime_store: PathBuf,
local_verse_store: PathBuf,
runtime_id: String,
interval_seconds: u64,
heartbeat_interval_seconds: u64,
managed_process_launch_id: String,
qdrant_url: String,
ollama_base_url: String,
ollama_model: String,
}
impl Args {
fn parse() -> Result<Self> {
let mut values = env::args().skip(1);
let command = values.next().context("missing command; use serve")?;
if command != "serve" {
return Err(anyhow!("unknown command {command:?}; use serve"));
}
let mut runtime_store = None;
let mut local_verse_store = None;
let mut runtime_id = None;
let mut interval_seconds = None;
let mut heartbeat_interval_seconds = None;
let mut qdrant_url = None;
let mut ollama_base_url = None;
let mut ollama_model = None;
while let Some(flag) = values.next() {
let mut value = || {
values
.next()
.with_context(|| format!("missing value for {flag}"))
};
match flag.as_str() {
"--runtime-store" => runtime_store = Some(PathBuf::from(value()?)),
"--local-verse-store" => local_verse_store = Some(PathBuf::from(value()?)),
"--runtime-id" => runtime_id = Some(value()?),
"--interval-seconds" => interval_seconds = Some(value()?.parse::<u64>()?),
"--heartbeat-interval-seconds" => {
heartbeat_interval_seconds = Some(value()?.parse::<u64>()?)
}
"--qdrant-url" => qdrant_url = Some(value()?),
"--ollama-base-url" => ollama_base_url = Some(value()?),
"--ollama-model" => ollama_model = Some(value()?),
_ => return Err(anyhow!("unexpected argument {flag:?}")),
}
}
let interval_seconds = interval_seconds.context("missing --interval-seconds")?;
if interval_seconds == 0 {
return Err(anyhow!("--interval-seconds must be positive"));
}
let heartbeat_interval_seconds =
heartbeat_interval_seconds.context("missing --heartbeat-interval-seconds")?;
if heartbeat_interval_seconds == 0 {
return Err(anyhow!("--heartbeat-interval-seconds must be positive"));
}
let managed_process_launch_id =
env::var("EPIPHANY_WORKSPACE_COVERAGE_LAUNCH_ID").unwrap_or_default();
if managed_process_launch_id.trim().is_empty() {
return Err(anyhow!(
"managed workspace coverage projector requires its managed process launch id"
));
}
Ok(Self {
runtime_store: runtime_store.context("missing --runtime-store")?,
local_verse_store: local_verse_store.context("missing --local-verse-store")?,
runtime_id: runtime_id.context("missing --runtime-id")?,
interval_seconds,
heartbeat_interval_seconds,
managed_process_launch_id,
qdrant_url: required_nonempty(qdrant_url, "--qdrant-url")?,
ollama_base_url: required_nonempty(ollama_base_url, "--ollama-base-url")?,
ollama_model: required_nonempty(ollama_model, "--ollama-model")?,
})
}
}
fn required_nonempty(value: Option<String>, flag: &str) -> Result<String> {
let value = value.with_context(|| format!("missing {flag}"))?;
if value.trim().is_empty() {
return Err(anyhow!("{flag} must not be empty"));
}
Ok(value)
}

File diff suppressed because it is too large Load diff

View file

@ -28,27 +28,15 @@ mod reorientation_work;
mod repo_model_documents;
mod repo_model_gateway;
mod repository_body_observer;
mod repository_readiness;
mod resident_readiness;
mod resident_self;
mod runtime_spine;
mod runtime_store_backend;
mod runtime_worker_attempt;
mod semantic_backend;
mod semantic_projector_service;
mod soul_gateway;
mod state_ledger;
mod substrate_gate;
mod surfaces;
mod workspace_coverage_process_bootstrap;
mod workspace_coverage_process_documents;
mod workspace_coverage_projection_batch_checkpoint;
mod workspace_coverage_projection_progress;
mod workspace_coverage_projector;
mod workspace_coverage_projector_service;
mod workspace_coverage_store_binding;
#[allow(dead_code)]
mod workspace_retrieval_coverage;
pub use admitted_model_direction_consideration::{
AdmittedModelDirectionConsiderationRequest, AdmittedModelDirectionConsiderationResult,
@ -110,57 +98,19 @@ pub use cultcache_rs::RedbMessagePackBackingStore;
pub use cultcache_rs::SingleFileMessagePackBackingStore;
pub use cultmesh_integration::EPIPHANY_CANONICAL_SWARM_BRAKE_ID;
pub use cultmesh_integration::EPIPHANY_CANONICAL_SWARM_BRAKE_OWNER;
pub use cultmesh_integration::EPIPHANY_CULTMESH_DAEMON_HEARTBEAT_EVENT_SCHEMA_VERSION;
pub use cultmesh_integration::EPIPHANY_CULTMESH_DAEMON_HEARTBEAT_EVENT_TYPE;
pub use cultmesh_integration::EPIPHANY_CULTMESH_DAEMON_SERVICE_LIFECYCLE_RECEIPT_SCHEMA_VERSION;
pub use cultmesh_integration::EPIPHANY_CULTMESH_DAEMON_SERVICE_LIFECYCLE_RECEIPT_TYPE;
pub use cultmesh_integration::EPIPHANY_CULTMESH_LOCAL_AREA_TIER;
pub use cultmesh_integration::EPIPHANY_CULTMESH_LOCAL_AREA_VERSE_ID;
pub use cultmesh_integration::EPIPHANY_CULTMESH_MANAGED_SERVICE_POLICY_SCHEMA_VERSION;
pub use cultmesh_integration::EPIPHANY_CULTMESH_MANAGED_SERVICE_POLICY_TYPE;
pub use cultmesh_integration::EPIPHANY_CULTMESH_SEMANTIC_PROJECTION_HEALTH_SCHEMA_VERSION;
pub use cultmesh_integration::EPIPHANY_CULTMESH_SEMANTIC_PROJECTION_HEALTH_TYPE;
pub use cultmesh_integration::EPIPHANY_CULTMESH_SWARM_BRAKE_KEY;
pub use cultmesh_integration::EPIPHANY_CULTMESH_SWARM_BRAKE_SCHEMA_VERSION;
pub use cultmesh_integration::EPIPHANY_CULTMESH_SWARM_BRAKE_TYPE;
pub use cultmesh_integration::EPIPHANY_WORKSPACE_COVERAGE_PROJECTOR_DAEMON_ID;
pub use cultmesh_integration::EPIPHANY_WORKSPACE_COVERAGE_PROJECTOR_SERVICE_ID;
pub use cultmesh_integration::EpiphanyCultMeshDaemonHeartbeatEventEntry;
pub use cultmesh_integration::EpiphanyCultMeshDaemonServiceLifecycleReceiptEntry;
pub use cultmesh_integration::EpiphanyCultMeshDocuments;
pub use cultmesh_integration::EpiphanyCultMeshManagedServicePolicyEntry;
pub use cultmesh_integration::EpiphanyCultMeshSemanticProjectionHealthEntry;
pub use cultmesh_integration::EpiphanyCultMeshSwarmBrakeEntry;
pub use cultmesh_integration::authenticate_epiphany_cultmesh_semantic_projector_launch;
pub use cultmesh_integration::canonical_epiphany_swarm_brake_protected_surfaces;
pub use cultmesh_integration::default_epiphany_cultmesh_swarm_brake;
pub use cultmesh_integration::engage_epiphany_cultmesh_swarm_brake;
pub use cultmesh_integration::idunn_recover_memory_semantic_projection_from_cultmesh;
pub use cultmesh_integration::load_current_epiphany_cultmesh_daemon_service_lifecycle_receipt_for_service;
pub use cultmesh_integration::load_epiphany_cultmesh_daemon_heartbeat_event;
pub use cultmesh_integration::load_epiphany_cultmesh_daemon_service_lifecycle_receipt;
pub use cultmesh_integration::load_epiphany_cultmesh_managed_service_policies;
pub use cultmesh_integration::load_epiphany_cultmesh_managed_service_policy;
pub use cultmesh_integration::load_epiphany_cultmesh_managed_service_policy_with_digest;
pub use cultmesh_integration::load_epiphany_cultmesh_semantic_projection_health;
pub use cultmesh_integration::load_epiphany_cultmesh_swarm_brake;
pub use cultmesh_integration::load_latest_epiphany_cultmesh_daemon_heartbeat;
pub use cultmesh_integration::open_epiphany_cultmesh_node;
pub use cultmesh_integration::publish_epiphany_cultmesh_semantic_projection_health;
pub use cultmesh_integration::release_epiphany_cultmesh_swarm_brake;
pub use cultmesh_integration::write_epiphany_cultmesh_daemon_heartbeat_event;
pub use cultmesh_integration::write_epiphany_cultmesh_daemon_service_lifecycle_receipt;
pub use cultmesh_integration::write_epiphany_cultmesh_semantic_projector_service_policy;
pub use cultmesh_integration::write_epiphany_cultmesh_swarm_brake;
pub use cultmesh_integration::write_epiphany_cultmesh_workspace_coverage_projector_service_policy;
pub use cultmesh_integration::{
EPIPHANY_CULTMESH_SWARM_BRAKE_KEY, EPIPHANY_CULTMESH_SWARM_BRAKE_SCHEMA_VERSION,
EPIPHANY_CULTMESH_SWARM_BRAKE_TYPE, EpiphanyCultMeshDocuments,
EpiphanyCultMeshSwarmBrakeEntry, canonical_epiphany_swarm_brake_protected_surfaces,
default_epiphany_cultmesh_swarm_brake, engage_epiphany_cultmesh_swarm_brake,
load_epiphany_cultmesh_swarm_brake, open_epiphany_cultmesh_node,
release_epiphany_cultmesh_swarm_brake, write_epiphany_cultmesh_swarm_brake,
};
pub use current_work::*;
pub use epiphany_state_model::EpiphanyMemoryAnchor;
pub use epiphany_state_model::EpiphanyMemoryContextPacket;
pub use epiphany_state_model::EpiphanyMemoryContextQuery;
pub use epiphany_state_model::EpiphanyMemoryDomain;
pub use epiphany_state_model::EpiphanyMemoryEdge;
pub use epiphany_state_model::EpiphanyMemoryFreshness;
pub use epiphany_state_model::EpiphanyMemoryFreshnessStatus;
pub use epiphany_state_model::EpiphanyMemoryLifecycle;
pub use epiphany_state_model::EpiphanyMemoryNode;
pub use epiphany_state_model::EpiphanyMemoryProfile;
@ -213,77 +163,18 @@ pub use imagination_consideration::{
validate_current_request as validate_current_imagination_consideration_request,
};
pub use memory_graph::EpiphanyMemoryEdgeKind;
pub use memory_graph::EpiphanyMemoryEmbeddingManifest;
pub use memory_graph::EpiphanyMemoryGraphSnapshot;
pub use memory_graph::EpiphanyMemoryGraphValidationError;
pub use memory_graph::EpiphanyMemoryLifecycleReceipt;
pub use memory_graph::EpiphanyMemoryNodeKind;
pub use memory_graph::EpiphanyMemoryPatchCandidate;
pub use memory_graph::MEMORY_GRAPH_PROJECTION_SCHEMA_VERSION;
pub use memory_graph::MEMORY_SEMANTIC_INDEX_RECEIPT_SCHEMA_VERSION;
pub use memory_graph::MEMORY_SEMANTIC_PROJECTION_ATTEMPT_SCHEMA_VERSION;
pub use memory_graph::MEMORY_SEMANTIC_PROJECTION_CLAIM_SCHEMA_VERSION;
pub use memory_graph::MEMORY_SEMANTIC_PROJECTION_OBLIGATION_SCHEMA_VERSION;
pub use memory_graph::MEMORY_SEMANTIC_PROJECTION_RETENTION_HEAD_SCHEMA_VERSION;
pub use memory_graph::MEMORY_SEMANTIC_PROJECTOR_EXECUTOR_GRANT_SCHEMA_VERSION;
pub use memory_graph::MEMORY_SEMANTIC_PROJECTOR_RECOVERY_AUTHORIZATION_SCHEMA_VERSION;
pub use memory_graph::MemorySemanticIndexConfig;
pub use memory_graph::MemorySemanticIndexReceipt;
pub use memory_graph::MemorySemanticPhysicalRetirementObligation;
pub use memory_graph::MemorySemanticPhysicalRetirementReceipt;
pub use memory_graph::MemorySemanticProjectionAttempt;
pub use memory_graph::MemorySemanticProjectionClaim;
pub use memory_graph::MemorySemanticProjectionHealth;
pub use memory_graph::MemorySemanticProjectionHealthStatus;
pub use memory_graph::MemorySemanticProjectionInput;
pub use memory_graph::MemorySemanticProjectionObligation;
pub use memory_graph::MemorySemanticProjectionObservation;
pub use memory_graph::MemorySemanticProjectionReadiness;
pub use memory_graph::MemorySemanticProjectionRetentionHead;
pub use memory_graph::MemorySemanticProjectionSourceHead;
pub use memory_graph::MemorySemanticProjectorAcquisition;
pub use memory_graph::MemorySemanticProjectorExecutorGrant;
pub use memory_graph::MemorySemanticProjectorPulseClassification;
pub use memory_graph::MemorySemanticProjectorPulseInspection;
pub use memory_graph::MemorySemanticProjectorPulseOutcome;
pub use memory_graph::MemorySemanticProjectorPulseStatus;
pub use memory_graph::MemorySemanticProjectorRecoveryAuthorization;
pub use memory_graph::RepoFrontierAdoptedPlan;
pub use memory_graph::RepoFrontierItem;
pub use memory_graph::RepoFrontierStatus;
pub use memory_graph::SEMANTIC_PROJECTION_SCHEMA_VERSION;
pub use memory_graph::SemanticCanonicalLocator;
pub use memory_graph::SemanticDocumentKind;
pub use memory_graph::SemanticLifecycle;
pub use memory_graph::SemanticProjectionCandidate;
pub use memory_graph::SemanticProjectionDocument;
pub use memory_graph::SemanticVisibility;
pub use memory_graph::authorize_memory_semantic_physical_retirements;
pub use memory_graph::bind_memory_semantic_index_receipt;
pub use memory_graph::compose_memory_graph_snapshots;
pub use memory_graph::derive_memory_graph_freshness;
pub use memory_graph::derive_memory_semantic_projection_health;
pub use memory_graph::derive_memory_semantic_projection_obligation;
pub use memory_graph::derive_semantic_projection;
pub use memory_graph::execute_memory_semantic_physical_retirement;
pub use memory_graph::lifecycle_allowed_for_profile;
pub use memory_graph::load_memory_semantic_projection_readiness;
pub use memory_graph::load_memory_semantic_projection_success;
pub use memory_graph::memory_graph_domain_id;
pub use memory_graph::memory_graph_edge_id;
pub use memory_graph::memory_graph_model_hash;
pub use memory_graph::memory_graph_node_id;
pub use memory_graph::memory_semantic_projection_query_eligible;
pub use memory_graph::observe_memory_semantic_projection;
pub use memory_graph::plan_memory_graph_context_cut;
pub use memory_graph::plan_memory_graph_context_cut_with_ranked_ids;
pub use memory_graph::resolve_semantic_candidate;
pub use memory_graph::retain_memory_semantic_projection_lifecycles;
pub use memory_graph::semantic_memory_context;
pub use memory_graph::semantic_point_id;
pub use memory_graph::validate_memory_graph_snapshot;
pub use memory_graph::validate_memory_semantic_projection_attempt;
pub use memory_graph::validate_memory_semantic_projection_obligation;
pub use mind_documents::*;
pub use packaged_release::{
EPIPHANY_PACKAGED_RELEASE_HEAD_SCHEMA_VERSION, EPIPHANY_PACKAGED_RELEASE_SCHEMA_VERSION,
@ -339,8 +230,6 @@ pub use persona_turn::parse_and_validate_persona_interpreter_effect_set;
pub use persona_turn::persona_interpreter_effect_set_json_schema;
pub use persona_turn::persona_projected_surface_is_clean;
pub use persona_turn::put_persona_terminal_decision;
pub use persona_turn::render_persona_semantic_memory_recall;
pub use persona_turn::semantic_memory_recall_from_heartbeat_action;
pub use process_observation::EpiphanyProcessObservation;
pub use process_observation::ProcessInstanceIdentity;
pub use process_observation::ProcessInstanceObservation;
@ -357,7 +246,6 @@ pub use reorientation_work::*;
pub use repo_model_documents::*;
pub use repo_model_gateway::*;
pub use repository_body_observer::*;
pub use repository_readiness::*;
pub use resident_readiness::*;
pub use resident_self::*;
pub use runtime_spine::ARCHIVED_RUNTIME_SESSION_SCHEMA_VERSION;
@ -493,7 +381,6 @@ pub use runtime_spine::runtime_hello_frame;
pub use runtime_spine::runtime_job_snapshot;
pub use runtime_spine::runtime_latest_hands_receipt_chain_after;
pub use runtime_spine::runtime_latest_repo_frontier_relinquishment;
pub use runtime_spine::runtime_modeling_semantic_projection_input;
pub use runtime_spine::runtime_registered_document_types;
pub use runtime_spine::runtime_reorient_worker_result;
pub use runtime_spine::runtime_repo_frontier_plan_decision;
@ -528,8 +415,6 @@ pub use runtime_spine::validate_hands_action_authority;
pub use runtime_spine::write_runtime_hello_frame;
pub use runtime_spine::write_runtime_schema_catalog_json;
pub use runtime_worker_attempt::{RuntimeTypedRequestRef, WorkerProcessStatus};
pub use semantic_projector_service::SemanticProjectorServiceBody;
pub use semantic_projector_service::SemanticProjectorServicePulse;
pub use soul_gateway::*;
pub use state_ledger::EpiphanyBranchRecord;
pub use state_ledger::EpiphanyLedgerEvidenceRecord;
@ -586,67 +471,4 @@ pub use surfaces::derive_role_board;
pub use surfaces::interpret_runtime_reorient_worker_result;
pub use surfaces::interpret_runtime_role_worker_result;
pub use surfaces::recommend_coordinator_action;
pub use workspace_coverage_process_bootstrap::{
WorkspaceCoverageProcessBootstrap, read_workspace_coverage_process_bootstrap,
write_workspace_coverage_process_bootstrap,
};
pub use workspace_coverage_process_documents::{
WORKSPACE_COVERAGE_ADVANCEMENT_SIGHT_SCHEMA_VERSION, WORKSPACE_COVERAGE_ADVANCEMENT_SIGHT_TYPE,
WORKSPACE_COVERAGE_CLAIM_SIGHT_SCHEMA_VERSION, WORKSPACE_COVERAGE_CLAIM_SIGHT_TYPE,
WORKSPACE_COVERAGE_PROCESS_LAUNCH_LATEST_KEY, WORKSPACE_COVERAGE_PROCESS_LAUNCH_SCHEMA_VERSION,
WORKSPACE_COVERAGE_PROCESS_LAUNCH_TYPE, WORKSPACE_COVERAGE_PROCESS_TERMINATION_SCHEMA_VERSION,
WORKSPACE_COVERAGE_PROCESS_TERMINATION_TYPE, WORKSPACE_COVERAGE_PROVIDER_HEARTBEAT_LATEST_KEY,
WORKSPACE_COVERAGE_PROVIDER_HEARTBEAT_SCHEMA_VERSION,
WORKSPACE_COVERAGE_PROVIDER_HEARTBEAT_TYPE,
WORKSPACE_COVERAGE_RECOVERY_DIRECTIVE_SCHEMA_VERSION,
WORKSPACE_COVERAGE_RECOVERY_DIRECTIVE_TYPE, WORKSPACE_COVERAGE_TERMINAL_SIGHT_SCHEMA_VERSION,
WORKSPACE_COVERAGE_TERMINAL_SIGHT_TYPE, WorkspaceCoverageAdvancementSightEntry,
WorkspaceCoverageClaimSightEntry, WorkspaceCoverageManagedProcessLaunchEntry,
WorkspaceCoverageProcessLifecycleObservation,
WorkspaceCoverageProcessTerminationObservationEntry, WorkspaceCoverageProviderHeartbeatEntry,
WorkspaceCoverageRecoveryDirectiveEntry, WorkspaceCoverageTerminalSightEntry,
authenticate_current_workspace_coverage_advancement_sight,
authenticate_current_workspace_coverage_claim_sight,
authenticate_current_workspace_coverage_terminal_sight,
authenticate_historical_workspace_coverage_managed_process_launch,
authenticate_recovery_workspace_coverage_claim_sight,
authenticate_workspace_coverage_managed_process_launch,
authenticate_workspace_coverage_process_termination_observation,
authenticate_workspace_coverage_provider_heartbeat,
authenticate_workspace_coverage_replacement_lineage,
authenticate_workspace_coverage_termination_with_envelope_digest,
load_latest_workspace_coverage_managed_process_launch,
load_latest_workspace_coverage_provider_heartbeat,
load_workspace_coverage_managed_process_launch,
load_workspace_coverage_managed_process_launch_with_digest,
load_workspace_coverage_process_termination_observation,
load_workspace_coverage_provider_heartbeat,
observe_historical_workspace_coverage_managed_process,
observe_workspace_coverage_managed_process, process_identity_from_workspace_coverage_launch,
sign_workspace_coverage_heartbeat, sign_workspace_coverage_launch,
sign_workspace_coverage_termination, workspace_coverage_heartbeat_statement,
workspace_coverage_host_identity_record_digest, workspace_coverage_launch_statement,
workspace_coverage_termination_statement, write_workspace_coverage_managed_process_launch,
write_workspace_coverage_process_termination_observation,
write_workspace_coverage_provider_heartbeat, write_workspace_coverage_recovery_directive,
};
pub use workspace_coverage_projection_progress::{
WORKSPACE_COVERAGE_PROJECTION_PROGRESS_SCHEMA_VERSION,
WORKSPACE_COVERAGE_PROJECTION_PROGRESS_TYPE, WorkspaceCoverageAdvancingAuthority,
WorkspaceCoverageProjectionProgressEntry, authenticate_current_workspace_coverage_advancement,
authenticate_workspace_coverage_projection_progress,
load_latest_workspace_coverage_projection_progress,
load_workspace_coverage_projection_progress,
};
pub use workspace_coverage_projector::{
WORKSPACE_COVERAGE_MAXIMUM_FILE_BYTES, WorkspaceCoverageRecoveryOutcome,
WorkspaceCoverageTerminalAuthority, authenticate_current_workspace_coverage_terminal_authority,
authenticate_workspace_coverage_recovery_receipt,
};
pub use workspace_coverage_projector_service::WorkspaceCoverageProjectorConfig;
pub use workspace_coverage_projector_service::WorkspaceCoverageProjectorPulseStatus;
pub use workspace_coverage_projector_service::WorkspaceCoverageProjectorServiceBody;
pub use workspace_coverage_projector_service::WorkspaceCoverageProjectorServicePulse;
pub use workspace_coverage_store_binding::*;
pub use workspace_retrieval_coverage::*;
mod admitted_model_direction_consideration;

View file

@ -1,93 +0,0 @@
use super::EpiphanyMemoryFreshness;
use super::EpiphanyMemoryFreshnessStatus;
use super::EpiphanyMemoryGraphSnapshot;
use super::EpiphanyMemoryGraphValidationError;
use super::validate_memory_graph_snapshot;
use crate::memory_graph::MEMORY_GRAPH_PROJECTION_SCHEMA_VERSION;
pub fn compose_memory_graph_snapshots(
graph_id: impl Into<String>,
snapshots: impl IntoIterator<Item = EpiphanyMemoryGraphSnapshot>,
) -> Result<EpiphanyMemoryGraphSnapshot, Vec<EpiphanyMemoryGraphValidationError>> {
let mut composed = EpiphanyMemoryGraphSnapshot {
schema_version: Some(MEMORY_GRAPH_PROJECTION_SCHEMA_VERSION.to_string()),
graph_id: graph_id.into(),
..Default::default()
};
let mut freshness_notes = Vec::new();
for snapshot in snapshots {
composed.domains.extend(snapshot.domains);
composed.nodes.extend(snapshot.nodes);
composed.edges.extend(snapshot.edges);
composed.summaries.extend(snapshot.summaries);
composed
.lifecycle_receipts
.extend(snapshot.lifecycle_receipts);
if let Some(freshness) = snapshot.freshness {
merge_freshness(&mut composed, freshness, &mut freshness_notes);
}
}
if let Some(freshness) = composed.freshness.as_mut() {
if freshness.note.is_none() && !freshness_notes.is_empty() {
freshness.note = Some(freshness_notes.join(" "));
}
}
let errors = validate_memory_graph_snapshot(&composed);
if errors.is_empty() {
Ok(composed)
} else {
Err(errors)
}
}
fn merge_freshness(
composed: &mut EpiphanyMemoryGraphSnapshot,
freshness: EpiphanyMemoryFreshness,
notes: &mut Vec<String>,
) {
let aggregate = composed
.freshness
.get_or_insert_with(|| EpiphanyMemoryFreshness {
status: EpiphanyMemoryFreshnessStatus::Ready,
note: None,
..Default::default()
});
if freshness_rank(freshness.status) > freshness_rank(aggregate.status) {
aggregate.status = freshness.status;
}
merge_unique(&mut aggregate.stale_node_ids, freshness.stale_node_ids);
merge_unique(&mut aggregate.stale_edge_ids, freshness.stale_edge_ids);
merge_unique(
&mut aggregate.stale_summary_ids,
freshness.stale_summary_ids,
);
merge_unique(
&mut aggregate.dirty_source_hashes,
freshness.dirty_source_hashes,
);
if let Some(note) = freshness.note {
if !note.trim().is_empty() {
notes.push(note);
}
}
}
fn freshness_rank(status: EpiphanyMemoryFreshnessStatus) -> u8 {
match status {
EpiphanyMemoryFreshnessStatus::Stale => 2,
EpiphanyMemoryFreshnessStatus::Ready => 1,
_ => 0,
}
}
fn merge_unique(target: &mut Vec<String>, values: impl IntoIterator<Item = String>) {
for value in values {
if !target.iter().any(|existing| existing == &value) {
target.push(value);
}
}
}

View file

@ -1,693 +0,0 @@
use super::EpiphanyMemoryAnchor;
use super::EpiphanyMemoryContextPacket;
use super::EpiphanyMemoryContextQuery;
use super::EpiphanyMemoryEdge;
use super::EpiphanyMemoryFreshnessStatus;
use super::EpiphanyMemoryGraphSnapshot;
use super::EpiphanyMemoryNode;
use super::EpiphanyMemoryProfile;
use super::EpiphanyMemorySummary;
use super::RepoFrontierItem;
use super::RepoFrontierStatus;
use super::derive_memory_graph_freshness;
use super::ids::normalized_key;
use super::ids::stable_memory_graph_id;
use super::memory_graph_model_hash;
use bm25::Document;
use bm25::Language;
use bm25::SearchEngineBuilder;
use std::collections::HashMap;
use std::collections::HashSet;
pub fn plan_memory_graph_context_cut(
snapshot: &EpiphanyMemoryGraphSnapshot,
query: &EpiphanyMemoryContextQuery,
) -> EpiphanyMemoryContextPacket {
plan_memory_graph_context_cut_constrained(snapshot, query, &[], false)
}
pub fn plan_memory_graph_context_cut_with_ranked_ids(
snapshot: &EpiphanyMemoryGraphSnapshot,
query: &EpiphanyMemoryContextQuery,
ranked_document_ids: &[String],
) -> EpiphanyMemoryContextPacket {
plan_memory_graph_context_cut_constrained(snapshot, query, ranked_document_ids, false)
}
pub(crate) fn plan_modeling_context_cut(
snapshot: &EpiphanyMemoryGraphSnapshot,
query: &EpiphanyMemoryContextQuery,
ranked_document_ids: &[String],
) -> EpiphanyMemoryContextPacket {
plan_memory_graph_context_cut_constrained(snapshot, query, ranked_document_ids, true)
}
fn plan_memory_graph_context_cut_constrained(
snapshot: &EpiphanyMemoryGraphSnapshot,
query: &EpiphanyMemoryContextQuery,
ranked_document_ids: &[String],
modeling_only: bool,
) -> EpiphanyMemoryContextPacket {
let budget = query.budget.unwrap_or(12).clamp(1, 64) as usize;
let dirty_source_hashes = snapshot
.freshness
.as_ref()
.map(|freshness| freshness.dirty_source_hashes.as_slice())
.unwrap_or(&[]);
let freshness = derive_memory_graph_freshness(snapshot, dirty_source_hashes);
let stale_nodes: HashSet<&str> = freshness
.stale_node_ids
.iter()
.map(String::as_str)
.collect();
let stale_edges: HashSet<&str> = freshness
.stale_edge_ids
.iter()
.map(String::as_str)
.collect();
let stale_summaries: HashSet<&str> = freshness
.stale_summary_ids
.iter()
.map(String::as_str)
.collect();
let domains = query
.domain_ids
.iter()
.map(String::as_str)
.collect::<HashSet<_>>();
let requested_nodes = query
.node_ids
.iter()
.map(String::as_str)
.collect::<HashSet<_>>();
let requested_edges = query
.edge_ids
.iter()
.map(String::as_str)
.collect::<HashSet<_>>();
let terms = query_terms(query);
let node_by_id = snapshot
.nodes
.iter()
.map(|node| (node.id.as_str(), node))
.collect::<HashMap<_, _>>();
let edge_by_id = snapshot
.edges
.iter()
.map(|edge| (edge.id.as_str(), edge))
.collect::<HashMap<_, _>>();
let mut nodes = Vec::new();
let mut edges = Vec::new();
let mut summaries: Vec<EpiphanyMemorySummary> = Vec::new();
let mut warnings = Vec::new();
// Modeling's unresolved frontier is explicit routing authority. Preserve its
// stored order and exact claim targets before asking semantic retrieval to
// infer what matters from the launch prose.
let (frontier, rejected_frontier_ids) = dependency_ordered_frontier(
snapshot,
query,
modeling_only,
budget,
&node_by_id,
&stale_nodes,
&domains,
);
for id in rejected_frontier_ids {
warnings.push(format!(
"frontier {id} was omitted because its target claims are missing, stale, or retired"
));
}
for node_id in frontier
.iter()
.flat_map(|item| item.target_claim_ids.iter())
{
if let Some(node) = node_by_id.get(node_id.as_str()) {
push_unique_node(&mut nodes, (*node).clone(), budget);
}
}
// Semantic projection may rank canonical document identities, never
// payload-authored claim text. Explicit frontier remains first; ranked
// canonical objects then seed the cut before BM25 fills remaining space.
for document_id in ranked_document_ids {
if let Some(node) = node_by_id.get(document_id.as_str()) {
if !stale_nodes.contains(node.id.as_str())
&& node_matches(node, query.profile, modeling_only, &domains, &[])
{
push_unique_node(&mut nodes, (*node).clone(), budget);
}
continue;
}
if let Some(edge) = edge_by_id.get(document_id.as_str()) {
let endpoints_live = [edge.source_id.as_str(), edge.target_id.as_str()]
.into_iter()
.all(|id| !stale_nodes.contains(id));
if !stale_edges.contains(edge.id.as_str())
&& endpoints_live
&& edge_matches(edge, query.profile, modeling_only, &domains, &node_by_id)
{
push_unique_edge(&mut edges, (*edge).clone(), budget);
if let Some(node) = node_by_id.get(edge.source_id.as_str()) {
if !stale_nodes.contains(node.id.as_str())
&& node_matches(node, query.profile, modeling_only, &domains, &[])
{
push_unique_node(&mut nodes, (*node).clone(), budget);
}
}
if let Some(node) = node_by_id.get(edge.target_id.as_str()) {
if !stale_nodes.contains(node.id.as_str())
&& node_matches(node, query.profile, modeling_only, &domains, &[])
{
push_unique_node(&mut nodes, (*node).clone(), budget);
}
}
}
continue;
}
if let Some(summary) = snapshot
.summaries
.iter()
.find(|summary| summary.id == *document_id)
&& summary_matches(
summary,
query.profile,
modeling_only,
&domains,
&[],
&node_by_id,
)
&& summary_is_usable(summary, &stale_summaries)
&& summaries.len() < budget
&& !summaries.iter().any(|existing| existing.id == summary.id)
{
summaries.push(summary.clone());
}
}
for summary in ranked_summaries(
snapshot,
query,
modeling_only,
&domains,
&terms,
&node_by_id,
) {
if summaries.len() >= budget {
break;
}
if !summary_matches(
summary,
query.profile,
modeling_only,
&domains,
&terms,
&node_by_id,
) {
continue;
}
if summary_is_usable(summary, &stale_summaries) {
if !summaries.iter().any(|existing| existing.id == summary.id) {
summaries.push(summary.clone());
}
continue;
}
warnings.push(format!(
"summary {} is stale or low-confidence; descended into children",
summary.id
));
include_summary_children(
summary,
&node_by_id,
&edge_by_id,
&stale_nodes,
&stale_edges,
query.profile,
modeling_only,
&domains,
budget,
&mut nodes,
&mut edges,
);
}
for node_id in &query.node_ids {
if let Some(node) = node_by_id.get(node_id.as_str()) {
if !stale_nodes.contains(node.id.as_str())
&& node_matches(node, query.profile, modeling_only, &domains, &[])
{
push_unique_node(&mut nodes, (*node).clone(), budget);
}
}
}
for edge_id in &query.edge_ids {
if let Some(edge) = edge_by_id.get(edge_id.as_str()) {
if !stale_edges.contains(edge.id.as_str())
&& edge_matches(edge, query.profile, modeling_only, &domains, &node_by_id)
{
push_unique_edge(&mut edges, (*edge).clone(), budget);
for node_id in [&edge.source_id, &edge.target_id] {
if let Some(node) = node_by_id.get(node_id.as_str())
&& !stale_nodes.contains(node.id.as_str())
&& node_matches(node, query.profile, modeling_only, &domains, &[])
{
push_unique_node(&mut nodes, (*node).clone(), budget);
}
}
}
}
}
if summaries.is_empty() && nodes.is_empty() {
for node in ranked_nodes(
snapshot,
query,
modeling_only,
&domains,
&terms,
&stale_nodes,
) {
if nodes.len() >= budget {
break;
}
if node_matches(node, query.profile, modeling_only, &domains, &terms) {
push_unique_node(&mut nodes, node.clone(), budget);
}
}
}
let anchors = unique_anchors(
nodes
.iter()
.flat_map(|node| node.anchors.iter())
.chain(edges.iter().flat_map(|edge| edge.anchors.iter())),
);
EpiphanyMemoryContextPacket {
id: stable_memory_graph_id("memctx", [query.id.as_str(), snapshot.graph_id.as_str()]),
query_id: query.id.clone(),
repo_model_revision: snapshot.model_revision,
repo_model_hash: memory_graph_model_hash(snapshot).unwrap_or_default(),
nodes,
edges,
summaries,
frontier,
anchors,
warnings,
missing_node_ids: query
.node_ids
.iter()
.filter(|id| !requested_nodes.is_empty() && !node_by_id.contains_key(id.as_str()))
.cloned()
.collect(),
missing_edge_ids: query
.edge_ids
.iter()
.filter(|id| !requested_edges.is_empty() && !edge_by_id.contains_key(id.as_str()))
.cloned()
.collect(),
}
}
fn dependency_ordered_frontier(
snapshot: &EpiphanyMemoryGraphSnapshot,
query: &EpiphanyMemoryContextQuery,
modeling_only: bool,
budget: usize,
node_by_id: &HashMap<&str, &EpiphanyMemoryNode>,
stale_nodes: &HashSet<&str>,
domains: &HashSet<&str>,
) -> (Vec<RepoFrontierItem>, Vec<String>) {
let unresolved = snapshot
.frontier
.iter()
.filter(|item| {
matches!(
item.status,
RepoFrontierStatus::Active
| RepoFrontierStatus::Proposed
| RepoFrontierStatus::Blocked
)
})
.collect::<Vec<_>>();
let unresolved_ids = unresolved
.iter()
.map(|item| item.id.as_str())
.collect::<HashSet<_>>();
let mut eligible_ids = unresolved
.iter()
.filter(|item| {
!item.target_claim_ids.is_empty()
&& item.target_claim_ids.iter().all(|id| {
node_by_id.get(id.as_str()).is_some_and(|node| {
!matches!(
node.lifecycle,
super::EpiphanyMemoryLifecycle::Retired
| super::EpiphanyMemoryLifecycle::Stale
) && !stale_nodes.contains(id.as_str())
&& node_matches(node, query.profile, modeling_only, domains, &[])
})
})
})
.map(|item| item.id.as_str())
.collect::<HashSet<_>>();
loop {
let before = eligible_ids.len();
let current_eligible = eligible_ids.clone();
eligible_ids.retain(|id| {
unresolved
.iter()
.find(|item| item.id == *id)
.is_some_and(|item| {
item.dependency_item_ids.iter().all(|dependency| {
!unresolved_ids.contains(dependency.as_str())
|| current_eligible.contains(dependency.as_str())
})
})
});
if eligible_ids.len() == before {
break;
}
}
let rejected = unresolved
.iter()
.filter(|item| !eligible_ids.contains(item.id.as_str()))
.map(|item| item.id.clone())
.collect();
let eligible = unresolved
.into_iter()
.filter(|item| eligible_ids.contains(item.id.as_str()))
.map(|item| (item.id.as_str(), item))
.collect::<HashMap<_, _>>();
let mut ordered = Vec::new();
let mut complete = HashSet::new();
let mut visiting = HashSet::new();
for item in &snapshot.frontier {
append_frontier_prerequisites(
&item.id,
&eligible,
&mut visiting,
&mut complete,
&mut ordered,
);
}
(
ordered.into_iter().take(budget).cloned().collect(),
rejected,
)
}
fn append_frontier_prerequisites<'a>(
id: &'a str,
eligible: &HashMap<&'a str, &'a RepoFrontierItem>,
visiting: &mut HashSet<&'a str>,
complete: &mut HashSet<&'a str>,
ordered: &mut Vec<&'a RepoFrontierItem>,
) {
if complete.contains(id) || !visiting.insert(id) {
return;
}
let Some(item) = eligible.get(id).copied() else {
visiting.remove(id);
return;
};
for dependency in &item.dependency_item_ids {
append_frontier_prerequisites(dependency, eligible, visiting, complete, ordered);
}
visiting.remove(id);
if complete.insert(id) {
ordered.push(item);
}
}
fn ranked_nodes<'a>(
snapshot: &'a EpiphanyMemoryGraphSnapshot,
query: &EpiphanyMemoryContextQuery,
modeling_only: bool,
domain_ids: &HashSet<&str>,
terms: &[String],
stale_nodes: &HashSet<&str>,
) -> Vec<&'a EpiphanyMemoryNode> {
let eligible = snapshot
.nodes
.iter()
.filter(|node| {
node_matches(node, query.profile, modeling_only, domain_ids, &[])
&& !stale_nodes.contains(node.id.as_str())
})
.collect::<Vec<_>>();
rank_typed_documents(eligible, query.text.as_deref(), terms, |node| {
[
node.title.as_str(),
node.claim.as_str(),
node.question.as_str(),
node.tension.as_str(),
node.action_implication.as_str(),
]
.join(" ")
})
}
fn ranked_summaries<'a>(
snapshot: &'a EpiphanyMemoryGraphSnapshot,
query: &EpiphanyMemoryContextQuery,
modeling_only: bool,
domain_ids: &HashSet<&str>,
terms: &[String],
node_by_id: &HashMap<&str, &EpiphanyMemoryNode>,
) -> Vec<&'a EpiphanyMemorySummary> {
let eligible = snapshot
.summaries
.iter()
.filter(|summary| {
summary_matches(
summary,
query.profile,
modeling_only,
domain_ids,
&[],
node_by_id,
)
})
.collect::<Vec<_>>();
rank_typed_documents(eligible, query.text.as_deref(), terms, |summary| {
[
summary.target.as_str(),
summary.claim.as_str(),
summary.question.as_str(),
summary.tension.as_str(),
summary.action_implication.as_str(),
]
.join(" ")
})
}
fn rank_typed_documents<'a, T>(
eligible: Vec<&'a T>,
query: Option<&str>,
terms: &[String],
search_text: impl Fn(&T) -> String,
) -> Vec<&'a T> {
if eligible.is_empty() || terms.is_empty() {
return eligible;
}
let documents = eligible
.iter()
.enumerate()
.map(|(index, value)| Document::new(index, search_text(value)))
.collect::<Vec<_>>();
let engine = SearchEngineBuilder::<usize>::with_documents(Language::English, documents).build();
engine
.search(query.unwrap_or_default(), eligible.len())
.into_iter()
.filter_map(|result| eligible.get(result.document.id).copied())
.collect()
}
fn query_terms(query: &EpiphanyMemoryContextQuery) -> Vec<String> {
query
.text
.as_deref()
.unwrap_or_default()
.split(|ch: char| !ch.is_alphanumeric() && ch != '_')
.filter(|term| term.len() > 2)
.map(normalized_key)
.collect()
}
fn summary_matches(
summary: &EpiphanyMemorySummary,
profile: Option<EpiphanyMemoryProfile>,
modeling_only: bool,
domain_ids: &HashSet<&str>,
terms: &[String],
node_by_id: &HashMap<&str, &EpiphanyMemoryNode>,
) -> bool {
if !domain_ids.is_empty() && !domain_ids.contains(summary.domain_id.as_str()) {
return false;
}
let covers_partition = summary
.covers_node_ids
.iter()
.filter_map(|id| node_by_id.get(id.as_str()))
.any(|node| modeling_scope_matches(node.profile, modeling_only));
if modeling_only && !covers_partition {
return false;
}
if let Some(profile) = profile {
let covers_profile = summary
.covers_node_ids
.iter()
.filter_map(|id| node_by_id.get(id.as_str()))
.any(|node| node.profile == profile);
if !covers_profile {
return false;
}
}
terms.is_empty()
|| text_matches_terms(
[
&summary.target,
&summary.claim,
&summary.question,
&summary.tension,
],
terms,
)
}
fn node_matches(
node: &EpiphanyMemoryNode,
profile: Option<EpiphanyMemoryProfile>,
modeling_only: bool,
domain_ids: &HashSet<&str>,
terms: &[String],
) -> bool {
if profile.is_some_and(|profile| node.profile != profile) {
return false;
}
if !modeling_scope_matches(node.profile, modeling_only) {
return false;
}
if !domain_ids.is_empty() && !domain_ids.contains(node.domain_id.as_str()) {
return false;
}
terms.is_empty()
|| text_matches_terms(
[
&node.title,
&node.claim,
&node.question,
&node.tension,
&node.action_implication,
],
terms,
)
}
fn edge_matches(
edge: &EpiphanyMemoryEdge,
profile: Option<EpiphanyMemoryProfile>,
modeling_only: bool,
domain_ids: &HashSet<&str>,
node_by_id: &HashMap<&str, &EpiphanyMemoryNode>,
) -> bool {
if profile.is_some_and(|profile| edge.profile != profile)
|| !modeling_scope_matches(edge.profile, modeling_only)
{
return false;
}
[&edge.source_id, &edge.target_id].into_iter().all(|id| {
node_by_id.get(id.as_str()).is_some_and(|node| {
modeling_scope_matches(node.profile, modeling_only)
&& (domain_ids.is_empty() || domain_ids.contains(node.domain_id.as_str()))
})
})
}
fn modeling_scope_matches(profile: EpiphanyMemoryProfile, modeling_only: bool) -> bool {
!modeling_only
|| matches!(
profile,
EpiphanyMemoryProfile::RepoArchitecture | EpiphanyMemoryProfile::RepoDataflow
)
}
fn text_matches_terms<'a>(values: impl IntoIterator<Item = &'a String>, terms: &[String]) -> bool {
let joined = values
.into_iter()
.map(|value| value.to_lowercase())
.collect::<Vec<_>>()
.join(" ");
terms.iter().any(|term| joined.contains(term))
}
fn summary_is_usable(summary: &EpiphanyMemorySummary, stale_summaries: &HashSet<&str>) -> bool {
summary.freshness == EpiphanyMemoryFreshnessStatus::Ready
&& summary.confidence >= 70
&& !stale_summaries.contains(summary.id.as_str())
&& summary.known_omissions.is_empty()
}
fn include_summary_children(
summary: &EpiphanyMemorySummary,
node_by_id: &HashMap<&str, &EpiphanyMemoryNode>,
edge_by_id: &HashMap<&str, &EpiphanyMemoryEdge>,
stale_nodes: &HashSet<&str>,
stale_edges: &HashSet<&str>,
profile: Option<EpiphanyMemoryProfile>,
modeling_only: bool,
domain_ids: &HashSet<&str>,
budget: usize,
nodes: &mut Vec<EpiphanyMemoryNode>,
edges: &mut Vec<EpiphanyMemoryEdge>,
) {
for node_id in &summary.covers_node_ids {
if let Some(node) = node_by_id.get(node_id.as_str()) {
if stale_nodes.contains(node.id.as_str())
|| !node_matches(node, profile, modeling_only, domain_ids, &[])
{
continue;
}
push_unique_node(nodes, (*node).clone(), budget);
}
}
for edge_id in &summary.covers_edge_ids {
if let Some(edge) = edge_by_id.get(edge_id.as_str()) {
if stale_edges.contains(edge.id.as_str())
|| !edge_matches(edge, profile, modeling_only, domain_ids, node_by_id)
{
continue;
}
push_unique_edge(edges, (*edge).clone(), budget);
}
}
}
fn unique_anchors<'a>(
anchors: impl IntoIterator<Item = &'a EpiphanyMemoryAnchor>,
) -> Vec<EpiphanyMemoryAnchor> {
let mut out = Vec::new();
let mut seen = HashSet::new();
for anchor in anchors {
if seen.insert(anchor.id.as_str()) {
out.push(anchor.clone());
}
}
out
}
fn push_unique_node(target: &mut Vec<EpiphanyMemoryNode>, node: EpiphanyMemoryNode, budget: usize) {
if target.len() < budget && !target.iter().any(|existing| existing.id == node.id) {
target.push(node);
}
}
fn push_unique_edge(target: &mut Vec<EpiphanyMemoryEdge>, edge: EpiphanyMemoryEdge, budget: usize) {
if target.len() < budget && !target.iter().any(|existing| existing.id == edge.id) {
target.push(edge);
}
}

View file

@ -1,84 +0,0 @@
use super::EpiphanyMemoryFreshness;
use super::EpiphanyMemoryFreshnessStatus;
use super::EpiphanyMemoryGraphSnapshot;
use super::EpiphanyMemoryLifecycle;
use super::push_unique;
use super::unique_strings;
use std::collections::HashSet;
pub fn derive_memory_graph_freshness(
snapshot: &EpiphanyMemoryGraphSnapshot,
dirty_source_hashes: &[String],
) -> EpiphanyMemoryFreshness {
let dirty: HashSet<&str> = dirty_source_hashes.iter().map(String::as_str).collect();
let mut stale_node_ids = Vec::new();
let mut stale_edge_ids = Vec::new();
let mut stale_summary_ids = Vec::new();
for node in &snapshot.nodes {
if node.lifecycle == EpiphanyMemoryLifecycle::Stale
|| node
.source_hashes
.iter()
.any(|hash| dirty.contains(hash.as_str()))
{
push_unique(&mut stale_node_ids, node.id.clone());
}
}
for edge in &snapshot.edges {
if edge.lifecycle == EpiphanyMemoryLifecycle::Stale
|| edge
.anchors
.iter()
.filter_map(|anchor| anchor.source_hash.as_deref())
.any(|hash| dirty.contains(hash))
{
push_unique(&mut stale_edge_ids, edge.id.clone());
}
}
for summary in &snapshot.summaries {
if summary.freshness == EpiphanyMemoryFreshnessStatus::Stale
|| summary
.source_hashes
.iter()
.any(|hash| dirty.contains(hash.as_str()))
|| summary
.covers_node_ids
.iter()
.any(|id| stale_node_ids.iter().any(|stale| stale == id))
|| summary
.covers_edge_ids
.iter()
.any(|id| stale_edge_ids.iter().any(|stale| stale == id))
{
push_unique(&mut stale_summary_ids, summary.id.clone());
}
}
let status = if stale_node_ids.is_empty()
&& stale_edge_ids.is_empty()
&& stale_summary_ids.is_empty()
&& dirty_source_hashes.is_empty()
{
EpiphanyMemoryFreshnessStatus::Ready
} else {
EpiphanyMemoryFreshnessStatus::Stale
};
EpiphanyMemoryFreshness {
status,
stale_node_ids,
stale_edge_ids,
stale_summary_ids,
dirty_source_hashes: unique_strings(dirty_source_hashes.iter().cloned()),
note: Some(match status {
EpiphanyMemoryFreshnessStatus::Ready => "Memory graph is ready.".to_string(),
EpiphanyMemoryFreshnessStatus::Stale => {
"Memory graph has stale source-backed records.".to_string()
}
_ => "Memory graph freshness is not derived.".to_string(),
}),
}
}

View file

@ -1,79 +1,26 @@
pub mod compose;
pub mod context_cut;
pub mod freshness;
pub mod ids;
pub mod profiles;
pub mod semantic_index;
pub mod semantic_projection;
pub mod semantic_projector;
pub mod semantic_projector_pulse;
pub mod validation;
pub use epiphany_state_model::EpiphanyMemoryAnchor;
pub use epiphany_state_model::EpiphanyMemoryContextPacket;
pub use epiphany_state_model::EpiphanyMemoryContextQuery;
pub use epiphany_state_model::EpiphanyMemoryDomain;
pub use epiphany_state_model::EpiphanyMemoryEdge;
pub use epiphany_state_model::EpiphanyMemoryEdgeKind;
pub use epiphany_state_model::EpiphanyMemoryEmbeddingManifest;
pub use epiphany_state_model::EpiphanyMemoryFreshness;
pub use epiphany_state_model::EpiphanyMemoryFreshnessStatus;
pub use epiphany_state_model::EpiphanyMemoryGraphSnapshot;
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::EpiphanyMemoryPatchCandidate;
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 compose::compose_memory_graph_snapshots;
pub use context_cut::plan_memory_graph_context_cut;
pub use context_cut::plan_memory_graph_context_cut_with_ranked_ids;
pub(crate) use context_cut::plan_modeling_context_cut;
pub use freshness::derive_memory_graph_freshness;
pub use ids::memory_graph_domain_id;
pub use ids::memory_graph_edge_id;
pub use ids::memory_graph_node_id;
pub use semantic_index::*;
pub use semantic_projection::*;
pub use semantic_projector::*;
pub use semantic_projector_pulse::*;
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;
pub(crate) fn push_unique(target: &mut Vec<String>, value: String) {
if !target.iter().any(|existing| existing == &value) {
target.push(value);
}
}
pub(crate) fn unique_strings(values: impl IntoIterator<Item = String>) -> Vec<String> {
let mut out = Vec::new();
for value in values {
push_unique(&mut out, value);
}
out
}
pub const MEMORY_GRAPH_PROJECTION_SCHEMA_VERSION: &str = "epiphany.memory_graph.projection.v1";
/// Digest helper for transient memory-context and semantic projection DTOs.
/// This digest is derived notification state; keyed Mind documents own the
/// RepoModel and no aggregate revision can be committed through this module.
pub fn memory_graph_model_hash(snapshot: &EpiphanyMemoryGraphSnapshot) -> anyhow::Result<String> {
use sha2::{Digest, Sha256};
let mut canonical = snapshot.clone();
canonical.model_hash.clear();
Ok(format!(
"{:x}",
Sha256::digest(rmp_serde::to_vec_named(&canonical)?)
))
}

File diff suppressed because it is too large Load diff

View file

@ -1,686 +0,0 @@
use super::{
EpiphanyMemoryDomain, EpiphanyMemoryFreshnessStatus, EpiphanyMemoryGraphSnapshot,
EpiphanyMemoryLifecycle, EpiphanyMemoryProfile, RepoFrontierStatus, memory_graph_model_hash,
validate_memory_graph_snapshot,
};
use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::{HashMap, HashSet};
use uuid::Uuid;
pub const SEMANTIC_PROJECTION_SCHEMA_VERSION: &str = "gamecult.epiphany.semantic_projection.v0";
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SemanticDocumentKind {
Node,
Edge,
Summary,
Frontier,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SemanticVisibility {
PrivateVerse,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "owner", content = "value", rename_all = "snake_case")]
pub enum SemanticLifecycle {
Memory(EpiphanyMemoryLifecycle),
Frontier(RepoFrontierStatus),
Derived,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SemanticCanonicalLocator {
pub locator: String,
pub canonical_type: String,
pub canonical_key: String,
pub canonical_document_id: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SemanticProjectionDocument {
pub point_id: String,
pub swarm_id: String,
pub kind: SemanticDocumentKind,
pub canonical: SemanticCanonicalLocator,
pub graph_id: String,
pub model_revision: u64,
pub model_hash: String,
pub domain_id: String,
pub profile: EpiphanyMemoryProfile,
pub lifecycle: SemanticLifecycle,
pub visibility: SemanticVisibility,
pub source_refs: Vec<String>,
pub source_hashes: Vec<String>,
pub canonical_content_hash: String,
pub canonical_schema_version: String,
pub projection_schema_version: String,
pub projection_text: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SemanticProjectionCandidate {
pub point_id: String,
pub canonical: SemanticCanonicalLocator,
pub score: f32,
pub indexed_model_revision: u64,
pub indexed_model_hash: String,
pub indexed_canonical_content_hash: String,
}
pub fn semantic_point_id(
swarm_id: &str,
canonical_type: &str,
canonical_key: &str,
canonical_document_id: &str,
) -> String {
Uuid::new_v5(
&Uuid::NAMESPACE_URL,
format!("{swarm_id}|modeling|{canonical_type}|{canonical_key}|{canonical_document_id}")
.as_bytes(),
)
.to_string()
}
pub fn derive_semantic_projection(
swarm_id: &str,
snapshot: &EpiphanyMemoryGraphSnapshot,
) -> Result<Vec<SemanticProjectionDocument>> {
if swarm_id.trim().is_empty() {
return Err(anyhow!("semantic projection requires swarm_id"));
}
let validation = validate_memory_graph_snapshot(snapshot);
if !validation.is_empty() {
return Err(anyhow!("memory graph is invalid: {validation:?}"));
}
let computed_hash = memory_graph_model_hash(snapshot)?;
if !snapshot.model_hash.is_empty() && snapshot.model_hash != computed_hash {
return Err(anyhow!(
"memory graph model_hash does not match canonical content"
));
}
let mut canonical_snapshot = snapshot.clone();
canonical_snapshot.model_hash = computed_hash;
let snapshot = &canonical_snapshot;
let domains = snapshot
.domains
.iter()
.map(|domain| (domain.id.as_str(), domain))
.collect::<HashMap<_, _>>();
let nodes = snapshot
.nodes
.iter()
.map(|node| (node.id.as_str(), node))
.collect::<HashMap<_, _>>();
let stale_nodes: HashSet<&str> = snapshot
.freshness
.as_ref()
.map(|freshness| {
freshness
.stale_node_ids
.iter()
.map(String::as_str)
.collect()
})
.unwrap_or_default();
let stale_edges: HashSet<&str> = snapshot
.freshness
.as_ref()
.map(|freshness| {
freshness
.stale_edge_ids
.iter()
.map(String::as_str)
.collect()
})
.unwrap_or_default();
let stale_summaries: HashSet<&str> = snapshot
.freshness
.as_ref()
.map(|freshness| {
freshness
.stale_summary_ids
.iter()
.map(String::as_str)
.collect()
})
.unwrap_or_default();
let mut out = Vec::new();
for node in &snapshot.nodes {
if !is_modeling_profile(node.profile)
|| excluded_lifecycle(node.lifecycle)
|| stale_nodes.contains(node.id.as_str())
{
continue;
}
let domain = required_domain(&domains, &node.domain_id)?;
out.push(document(
swarm_id,
snapshot,
SemanticDocumentKind::Node,
&node.id,
domain,
node.profile,
SemanticLifecycle::Memory(node.lifecycle),
node.anchors
.iter()
.map(|anchor| anchor.target.clone())
.collect(),
node.source_hashes.clone(),
node,
join_text([
&node.title,
&node.claim,
&node.question,
&node.tension,
&node.action_implication,
]),
)?);
}
for edge in &snapshot.edges {
if !is_modeling_profile(edge.profile)
|| excluded_lifecycle(edge.lifecycle)
|| stale_edges.contains(edge.id.as_str())
{
continue;
}
let source = nodes
.get(edge.source_id.as_str())
.ok_or_else(|| anyhow!("validated edge lost source"))?;
let target = nodes
.get(edge.target_id.as_str())
.ok_or_else(|| anyhow!("validated edge lost target"))?;
if excluded_lifecycle(source.lifecycle)
|| excluded_lifecycle(target.lifecycle)
|| !is_modeling_profile(source.profile)
|| !is_modeling_profile(target.profile)
|| stale_nodes.contains(source.id.as_str())
|| stale_nodes.contains(target.id.as_str())
{
continue;
}
let domain = required_domain(&domains, &source.domain_id)?;
let source_hashes = edge
.anchors
.iter()
.filter_map(|anchor| anchor.source_hash.clone())
.collect();
out.push(document(
swarm_id,
snapshot,
SemanticDocumentKind::Edge,
&edge.id,
domain,
edge.profile,
SemanticLifecycle::Memory(edge.lifecycle),
edge.anchors
.iter()
.map(|anchor| anchor.target.clone())
.collect(),
source_hashes,
edge,
join_text([&edge.claim, &edge.source_id, &edge.target_id]),
)?);
}
for summary in &snapshot.summaries {
let domain = required_domain(&domains, &summary.domain_id)?;
if !is_modeling_profile(domain.profile)
|| summary.freshness != EpiphanyMemoryFreshnessStatus::Ready
|| summary.confidence < 70
|| !summary.known_omissions.is_empty()
|| stale_summaries.contains(summary.id.as_str())
{
continue;
}
out.push(document(
swarm_id,
snapshot,
SemanticDocumentKind::Summary,
&summary.id,
domain,
domain.profile,
SemanticLifecycle::Derived,
summary
.covers_node_ids
.iter()
.chain(&summary.covers_edge_ids)
.cloned()
.collect(),
summary.source_hashes.clone(),
summary,
join_text([
&summary.target,
&summary.claim,
&summary.question,
&summary.tension,
&summary.action_implication,
]),
)?);
}
for item in &snapshot.frontier {
if !matches!(
item.status,
RepoFrontierStatus::Proposed | RepoFrontierStatus::Active | RepoFrontierStatus::Blocked
) {
continue;
}
let domain = item
.target_claim_ids
.first()
.and_then(|id| nodes.get(id.as_str()))
.and_then(|node| domains.get(node.domain_id.as_str()))
.copied()
.ok_or_else(|| anyhow!("validated unresolved frontier lost its target domain"))?;
out.push(document(
swarm_id,
snapshot,
SemanticDocumentKind::Frontier,
&item.id,
domain,
EpiphanyMemoryProfile::RepoArchitecture,
SemanticLifecycle::Frontier(item.status),
item.repository_scope
.iter()
.chain(&item.evidence_refs)
.cloned()
.collect(),
Vec::new(),
item,
join_text([
&item.migration_body,
&item.question,
&item.gap,
&item.recommended_next_organ,
]),
)?);
}
out.sort_by(|left, right| left.point_id.cmp(&right.point_id));
Ok(out)
}
pub fn resolve_semantic_candidate<'a>(
candidate: &SemanticProjectionCandidate,
current_documents: &'a [SemanticProjectionDocument],
) -> Result<&'a SemanticProjectionDocument> {
let document = current_documents
.iter()
.find(|document| document.point_id == candidate.point_id)
.ok_or_else(|| anyhow!("semantic candidate is missing or no longer live"))?;
if document.canonical != candidate.canonical
|| document.model_revision != candidate.indexed_model_revision
|| document.model_hash != candidate.indexed_model_hash
|| document.canonical_content_hash != candidate.indexed_canonical_content_hash
{
return Err(anyhow!(
"semantic candidate no longer matches canonical state"
));
}
Ok(document)
}
fn document<T: Serialize>(
swarm_id: &str,
snapshot: &EpiphanyMemoryGraphSnapshot,
kind: SemanticDocumentKind,
document_id: &str,
domain: &EpiphanyMemoryDomain,
profile: EpiphanyMemoryProfile,
lifecycle: SemanticLifecycle,
mut source_refs: Vec<String>,
mut source_hashes: Vec<String>,
canonical_value: &T,
projection_text: String,
) -> Result<SemanticProjectionDocument> {
source_refs.sort();
source_refs.dedup();
source_hashes.sort();
source_hashes.dedup();
let canonical_type = match kind {
SemanticDocumentKind::Node => "epiphany.mind.repo_model.node.v1",
SemanticDocumentKind::Edge => "epiphany.mind.repo_model.edge.v1",
SemanticDocumentKind::Summary => "epiphany.mind.repo_model.summary.v1",
SemanticDocumentKind::Frontier => "epiphany.mind.repo_model.frontier.v1",
};
let canonical = SemanticCanonicalLocator {
locator: format!("cultcache://{swarm_id}/{canonical_type}/{document_id}"),
canonical_type: canonical_type.to_string(),
canonical_key: document_id.to_string(),
canonical_document_id: document_id.to_string(),
};
Ok(SemanticProjectionDocument {
point_id: semantic_point_id(swarm_id, canonical_type, document_id, document_id),
swarm_id: swarm_id.to_string(),
kind,
canonical,
graph_id: snapshot.graph_id.clone(),
model_revision: snapshot.model_revision,
model_hash: snapshot.model_hash.clone(),
domain_id: domain.id.clone(),
profile,
lifecycle,
visibility: SemanticVisibility::PrivateVerse,
source_refs,
source_hashes,
canonical_content_hash: format!(
"{:x}",
Sha256::digest(rmp_serde::to_vec_named(canonical_value)?)
),
canonical_schema_version: snapshot
.schema_version
.clone()
.unwrap_or_else(|| "unknown".to_string()),
projection_schema_version: SEMANTIC_PROJECTION_SCHEMA_VERSION.to_string(),
projection_text,
})
}
fn is_modeling_profile(profile: EpiphanyMemoryProfile) -> bool {
matches!(
profile,
EpiphanyMemoryProfile::RepoArchitecture | EpiphanyMemoryProfile::RepoDataflow
)
}
fn excluded_lifecycle(lifecycle: EpiphanyMemoryLifecycle) -> bool {
matches!(
lifecycle,
EpiphanyMemoryLifecycle::Retired | EpiphanyMemoryLifecycle::Stale
)
}
fn required_domain<'a>(
domains: &HashMap<&str, &'a EpiphanyMemoryDomain>,
id: &str,
) -> Result<&'a EpiphanyMemoryDomain> {
domains
.get(id)
.copied()
.ok_or_else(|| anyhow!("validated document lost domain"))
}
fn join_text<const N: usize>(parts: [&str; N]) -> String {
parts
.into_iter()
.filter(|part| !part.trim().is_empty())
.collect::<Vec<_>>()
.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::memory_graph::{
EpiphanyMemoryEdge, EpiphanyMemoryNode, EpiphanyMemorySummary, RepoFrontierItem,
};
use epiphany_state_model::{EpiphanyMemoryEdgeKind, EpiphanyMemoryNodeKind};
fn snapshot() -> EpiphanyMemoryGraphSnapshot {
let mut snapshot = EpiphanyMemoryGraphSnapshot {
schema_version: Some("v0".into()),
graph_id: "graph".into(),
model_revision: 7,
domains: vec![
EpiphanyMemoryDomain {
id: "repo".into(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
title: "Repo".into(),
lifecycle: EpiphanyMemoryLifecycle::Accepted,
..Default::default()
},
EpiphanyMemoryDomain {
id: "mind".into(),
profile: EpiphanyMemoryProfile::Evidence,
title: "Mind".into(),
lifecycle: EpiphanyMemoryLifecycle::Accepted,
..Default::default()
},
],
nodes: vec![
EpiphanyMemoryNode {
id: "repo-node".into(),
domain_id: "repo".into(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
kind: EpiphanyMemoryNodeKind::Module,
title: "Repo node".into(),
claim: "owns map".into(),
tension: "map can drift".into(),
action_implication: "keep typed".into(),
source_hashes: vec!["anchor:missing".into()],
lifecycle: EpiphanyMemoryLifecycle::Accepted,
confidence: 90,
..Default::default()
},
EpiphanyMemoryNode {
id: "mind-node".into(),
domain_id: "mind".into(),
profile: EpiphanyMemoryProfile::Evidence,
kind: EpiphanyMemoryNodeKind::Evidence,
title: "Lesson".into(),
claim: "remember it".into(),
tension: "memory can drift".into(),
action_implication: "rehydrate".into(),
source_hashes: vec!["anchor:missing".into()],
lifecycle: EpiphanyMemoryLifecycle::Accepted,
confidence: 90,
..Default::default()
},
EpiphanyMemoryNode {
id: "retired".into(),
domain_id: "repo".into(),
profile: EpiphanyMemoryProfile::RepoArchitecture,
kind: EpiphanyMemoryNodeKind::Module,
title: "Old".into(),
claim: "old".into(),
tension: "obsolete".into(),
action_implication: "none".into(),
source_hashes: vec!["anchor:missing".into()],
lifecycle: EpiphanyMemoryLifecycle::Retired,
confidence: 90,
..Default::default()
},
],
edges: vec![EpiphanyMemoryEdge {
id: "edge".into(),
source_id: "repo-node".into(),
target_id: "mind-node".into(),
kind: EpiphanyMemoryEdgeKind::Grounds,
profile: EpiphanyMemoryProfile::RepoArchitecture,
claim: "grounds".into(),
lifecycle: EpiphanyMemoryLifecycle::Accepted,
confidence: 80,
..Default::default()
}],
summaries: vec![EpiphanyMemorySummary {
id: "summary".into(),
domain_id: "mind".into(),
covers_node_ids: vec!["mind-node".into()],
target: "lesson".into(),
claim: "durable".into(),
tension: "retrieval can drift".into(),
action_implication: "retrieve".into(),
freshness: EpiphanyMemoryFreshnessStatus::Ready,
confidence: 80,
..Default::default()
}],
frontier: vec![RepoFrontierItem {
id: "frontier".into(),
migration_body: "finish projection".into(),
gap: "not wired".into(),
target_claim_ids: vec!["repo-node".into()],
repository_scope: vec!["epiphany-core".into()],
recommended_next_organ: "Hands".into(),
status: RepoFrontierStatus::Active,
..Default::default()
}],
..Default::default()
};
snapshot.model_hash = memory_graph_model_hash(&snapshot).unwrap();
snapshot
}
#[test]
fn derives_only_live_modeling_documents() {
let documents = derive_semantic_projection("swarm-a", &snapshot()).unwrap();
assert_eq!(documents.len(), 2);
assert!(
documents
.iter()
.any(|document| document.kind == SemanticDocumentKind::Frontier)
);
assert!(
!documents
.iter()
.any(|document| document.canonical.canonical_document_id == "retired")
);
let first = derive_semantic_projection("swarm-a", &snapshot()).unwrap();
assert_eq!(documents, first);
let other_swarm = derive_semantic_projection("swarm-b", &snapshot()).unwrap();
assert!(documents.iter().zip(&other_swarm).all(|(left, right)| {
left.swarm_id != right.swarm_id && left.point_id != right.point_id
}));
}
#[test]
fn candidate_resolution_revalidates_canonical_identity_and_hashes() {
let documents = derive_semantic_projection("swarm-a", &snapshot()).unwrap();
let document = documents
.iter()
.find(|document| document.canonical.canonical_document_id == "repo-node")
.unwrap();
let candidate = SemanticProjectionCandidate {
point_id: document.point_id.clone(),
canonical: document.canonical.clone(),
score: 0.9,
indexed_model_revision: document.model_revision,
indexed_model_hash: document.model_hash.clone(),
indexed_canonical_content_hash: document.canonical_content_hash.clone(),
};
assert_eq!(
resolve_semantic_candidate(&candidate, &documents)
.unwrap()
.point_id,
document.point_id
);
let mut stale = candidate.clone();
stale.indexed_canonical_content_hash = "stale".into();
assert!(resolve_semantic_candidate(&stale, &documents).is_err());
let mut stale_revision = candidate.clone();
stale_revision.indexed_model_revision -= 1;
assert!(resolve_semantic_candidate(&stale_revision, &documents).is_err());
let mut stale_model = candidate.clone();
stale_model.indexed_model_hash = "old-model".into();
assert!(resolve_semantic_candidate(&stale_model, &documents).is_err());
let mut wrong_locator = candidate.clone();
wrong_locator.canonical.canonical_document_id = "other".into();
assert!(resolve_semantic_candidate(&wrong_locator, &documents).is_err());
assert!(resolve_semantic_candidate(&candidate, &[]).is_err());
}
#[test]
fn ranked_edge_cannot_reintroduce_stale_endpoint_and_summary_is_not_duplicated() {
let mut snapshot = snapshot();
snapshot.nodes[0].lifecycle = EpiphanyMemoryLifecycle::Stale;
snapshot.summaries[0].freshness = EpiphanyMemoryFreshnessStatus::Ready;
snapshot.freshness = Some(crate::memory_graph::derive_memory_graph_freshness(
&snapshot,
&[],
));
snapshot.model_hash.clear();
snapshot.model_hash = memory_graph_model_hash(&snapshot).unwrap();
let packet = crate::memory_graph::plan_memory_graph_context_cut_with_ranked_ids(
&snapshot,
&crate::memory_graph::EpiphanyMemoryContextQuery {
id: "ranked-hostile".to_string(),
text: Some("durable lesson".to_string()),
..Default::default()
},
&["edge".to_string(), "summary".to_string()],
);
assert!(!packet.nodes.iter().any(|node| node.id == "repo-node"));
assert_eq!(
packet
.summaries
.iter()
.filter(|summary| summary.id == "summary")
.count(),
1
);
}
#[test]
fn stale_summary_descent_never_emits_stale_children() {
let mut snapshot = snapshot();
snapshot.nodes[0].lifecycle = EpiphanyMemoryLifecycle::Stale;
snapshot.summaries[0].covers_node_ids = vec!["repo-node".to_string()];
snapshot.summaries[0].freshness = EpiphanyMemoryFreshnessStatus::Stale;
snapshot.freshness = Some(crate::memory_graph::derive_memory_graph_freshness(
&snapshot,
&[],
));
let packet = crate::memory_graph::plan_memory_graph_context_cut(
&snapshot,
&crate::memory_graph::EpiphanyMemoryContextQuery {
id: "stale-summary".to_string(),
text: Some("durable".to_string()),
..Default::default()
},
);
assert!(!packet.nodes.iter().any(|node| node.id == "repo-node"));
assert!(packet.summaries.is_empty());
}
#[test]
fn modeling_cut_cannot_cross_domain_through_ranked_ids() {
let mut snapshot = snapshot();
snapshot.frontier.push(RepoFrontierItem {
id: "mind-frontier".into(),
migration_body: "tempt cross-partition routing".into(),
gap: "must remain private to Mind".into(),
target_claim_ids: vec!["mind-node".into()],
recommended_next_organ: "Self".into(),
status: RepoFrontierStatus::Active,
..Default::default()
});
let modeling = crate::memory_graph::plan_modeling_context_cut(
&snapshot,
&crate::memory_graph::EpiphanyMemoryContextQuery {
id: "hostile-modeling".into(),
domain_ids: vec!["repo".into()],
node_ids: vec!["mind-node".into()],
edge_ids: vec!["edge".into()],
text: Some("remember durable lesson".into()),
budget: Some(64),
..Default::default()
},
&["mind-node".into(), "edge".into(), "summary".into()],
);
assert!(modeling.nodes.iter().all(|node| {
node.domain_id == "repo"
&& matches!(
node.profile,
EpiphanyMemoryProfile::RepoArchitecture | EpiphanyMemoryProfile::RepoDataflow
)
}));
assert!(modeling.edges.is_empty());
assert!(modeling.summaries.is_empty());
assert_eq!(
modeling
.frontier
.iter()
.map(|item| item.id.as_str())
.collect::<Vec<_>>(),
vec!["frontier"]
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,430 +0,0 @@
use super::MemorySemanticProjectionInput;
use anyhow::Result;
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MemorySemanticProjectorPulseClassification {
Pending,
Failed,
Repair,
Ready,
Running,
RunningOwned { claim_id: String },
Stale,
}
impl MemorySemanticProjectorPulseClassification {
fn automatic_purpose(&self) -> Option<&'static str> {
match self {
Self::Pending | Self::Failed => Some("execute"),
Self::Repair => Some("repair"),
Self::Ready | Self::Running | Self::RunningOwned { .. } | Self::Stale => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MemorySemanticProjectorPulseInspection {
pub scope_id: String,
pub classification: Option<MemorySemanticProjectorPulseClassification>,
pub error: Option<String>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MemorySemanticProjectorPulseStatus {
Idle,
Executed,
Contended,
Refused,
Busy,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MemorySemanticProjectorPulseOutcome {
pub status: MemorySemanticProjectorPulseStatus,
pub selected_scope_id: Option<String>,
pub inspections: Vec<MemorySemanticProjectorPulseInspection>,
pub error: Option<String>,
}
pub(crate) trait MemorySemanticProjectorPulsePort {
fn classify(
&self,
input: &MemorySemanticProjectionInput,
) -> Result<MemorySemanticProjectorPulseClassification>;
/// `Ok(None)` means another pulse won the exact acquisition CAS.
fn acquire(
&self,
input: &MemorySemanticProjectionInput,
purpose: &str,
) -> Result<Option<String>>;
fn execute(&self, input: &MemorySemanticProjectionInput, claim_id: &str) -> Result<()>;
}
pub(crate) struct MemorySemanticProjectorPulser<P> {
pub(crate) port: P,
active: AtomicBool,
}
impl<P> MemorySemanticProjectorPulser<P>
where
P: MemorySemanticProjectorPulsePort,
{
pub(crate) fn new(port: P) -> Self {
Self {
port,
active: AtomicBool::new(false),
}
}
pub(crate) fn pulse(
&self,
input: &MemorySemanticProjectionInput,
) -> MemorySemanticProjectorPulseOutcome {
if self
.active
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
return outcome(MemorySemanticProjectorPulseStatus::Busy, vec![], None, None);
}
let _guard = PulseGuard(&self.active);
let scope_id = scope_key(input);
let classification = match self.port.classify(input) {
Ok(classification) => classification,
Err(error) => {
return outcome(
MemorySemanticProjectorPulseStatus::Refused,
vec![MemorySemanticProjectorPulseInspection {
scope_id,
classification: None,
error: Some(format!("{error:#}")),
}],
None,
Some(format!("{error:#}")),
);
}
};
let inspections = vec![MemorySemanticProjectorPulseInspection {
scope_id: scope_id.clone(),
classification: Some(classification.clone()),
error: None,
}];
if let MemorySemanticProjectorPulseClassification::RunningOwned { claim_id } =
classification
{
if let Err(error) = self.port.execute(input, &claim_id) {
return outcome(
MemorySemanticProjectorPulseStatus::Refused,
inspections,
Some(scope_id),
Some(format!("{error:#}")),
);
}
return outcome(
MemorySemanticProjectorPulseStatus::Executed,
inspections,
Some(scope_id),
None,
);
}
let Some(purpose) = classification.automatic_purpose() else {
return outcome(
MemorySemanticProjectorPulseStatus::Idle,
inspections,
None,
None,
);
};
let claim_id = match self.port.acquire(input, purpose) {
Ok(Some(claim_id)) => claim_id,
Ok(None) => {
return outcome(
MemorySemanticProjectorPulseStatus::Contended,
inspections,
Some(scope_id),
None,
);
}
Err(error) => {
return outcome(
MemorySemanticProjectorPulseStatus::Refused,
inspections,
Some(scope_id),
Some(format!("{error:#}")),
);
}
};
if let Err(error) = self.port.execute(input, &claim_id) {
return outcome(
MemorySemanticProjectorPulseStatus::Refused,
inspections,
Some(scope_id),
Some(format!("{error:#}")),
);
}
outcome(
MemorySemanticProjectorPulseStatus::Executed,
inspections,
Some(scope_id),
None,
)
}
}
struct PulseGuard<'a>(&'a AtomicBool);
impl Drop for PulseGuard<'_> {
fn drop(&mut self) {
self.0.store(false, Ordering::Release);
}
}
fn scope_key(input: &MemorySemanticProjectionInput) -> String {
format!(
"{}:{}",
input.obligation().swarm_id,
input.obligation().partition
)
}
fn outcome(
status: MemorySemanticProjectorPulseStatus,
inspections: Vec<MemorySemanticProjectorPulseInspection>,
selected_scope_id: Option<String>,
error: Option<String>,
) -> MemorySemanticProjectorPulseOutcome {
MemorySemanticProjectorPulseOutcome {
status,
selected_scope_id,
inspections,
error,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::memory_graph::semantic_projector::MemorySemanticProjectionAuthoritySnapshot;
use crate::memory_graph::semantic_projector::{
classify_memory_semantic_projection_for_pulse, semantic_projector_cache,
};
use crate::{
MEMORY_SEMANTIC_PROJECTION_OBLIGATION_SCHEMA_VERSION, MemorySemanticProjectionObligation,
MemorySemanticProjectionSourceHead, SEMANTIC_PROJECTION_SCHEMA_VERSION,
};
use cultcache_rs::CultCache;
use std::sync::{Arc, Barrier, Mutex};
use tempfile::tempdir;
fn input(partition: &str) -> MemorySemanticProjectionInput {
let obligation = MemorySemanticProjectionObligation {
schema_version: MEMORY_SEMANTIC_PROJECTION_OBLIGATION_SCHEMA_VERSION.to_string(),
obligation_id: format!("obligation-{partition}"),
swarm_id: "swarm-a".to_string(),
partition: partition.to_string(),
canonical_source_id: format!("source-{partition}"),
source_commit_id: "commit-1".to_string(),
graph_id: format!("graph-{partition}"),
source_generation: 1,
source_model_hash: "model-hash".to_string(),
canonical_content_set_hash: "content-hash".to_string(),
projection_schema_version: SEMANTIC_PROJECTION_SCHEMA_VERSION.to_string(),
created_at: "2026-07-15T10:00:00Z".to_string(),
};
MemorySemanticProjectionInput {
snapshot: crate::EpiphanyMemoryGraphSnapshot {
schema_version: Some("v0".to_string()),
graph_id: obligation.graph_id.clone(),
model_revision: 1,
..Default::default()
},
authority: MemorySemanticProjectionAuthoritySnapshot {
head: MemorySemanticProjectionSourceHead {
swarm_id: obligation.swarm_id.clone(),
partition: obligation.partition.clone(),
canonical_source_id: obligation.canonical_source_id.clone(),
source_commit_id: obligation.source_commit_id.clone(),
graph_id: obligation.graph_id.clone(),
source_generation: 1,
source_model_hash: obligation.source_model_hash.clone(),
canonical_content_set_hash: obligation.canonical_content_set_hash.clone(),
},
envelopes: vec![],
},
obligation,
}
}
struct MockPort {
classifications: Mutex<
Vec<std::result::Result<MemorySemanticProjectorPulseClassification, &'static str>>,
>,
acquire_result: Mutex<Option<Option<String>>>,
acquisitions: Mutex<Vec<(String, String)>>,
executions: Mutex<Vec<String>>,
execute_barriers: Option<(Arc<Barrier>, Arc<Barrier>)>,
}
impl MockPort {
fn new(classifications: Vec<MemorySemanticProjectorPulseClassification>) -> Self {
Self {
classifications: Mutex::new(classifications.into_iter().map(Ok).collect()),
acquire_result: Mutex::new(Some(Some("claim-1".to_string()))),
acquisitions: Mutex::new(vec![]),
executions: Mutex::new(vec![]),
execute_barriers: None,
}
}
}
impl MemorySemanticProjectorPulsePort for MockPort {
fn classify(
&self,
_input: &MemorySemanticProjectionInput,
) -> Result<MemorySemanticProjectorPulseClassification> {
self.classifications
.lock()
.unwrap()
.remove(0)
.map_err(anyhow::Error::msg)
}
fn acquire(
&self,
input: &MemorySemanticProjectionInput,
purpose: &str,
) -> Result<Option<String>> {
self.acquisitions
.lock()
.unwrap()
.push((scope_key(input), purpose.to_string()));
Ok(self.acquire_result.lock().unwrap().take().unwrap_or(None))
}
fn execute(&self, _input: &MemorySemanticProjectionInput, claim_id: &str) -> Result<()> {
self.executions.lock().unwrap().push(claim_id.to_string());
if let Some((entered, release)) = &self.execute_barriers {
entered.wait();
release.wait();
}
Ok(())
}
}
#[test]
fn decision_table_never_runs_ready_running_or_stale() {
for classification in [
MemorySemanticProjectorPulseClassification::Ready,
MemorySemanticProjectorPulseClassification::Running,
MemorySemanticProjectorPulseClassification::Stale,
] {
let pulser = MemorySemanticProjectorPulser::new(MockPort::new(vec![classification]));
let outcome = pulser.pulse(&input("modeling"));
assert_eq!(outcome.status, MemorySemanticProjectorPulseStatus::Idle);
assert!(pulser.port.acquisitions.lock().unwrap().is_empty());
assert!(pulser.port.executions.lock().unwrap().is_empty());
}
}
#[test]
fn repair_classification_acquires_the_typed_repair_path() {
let pulser = MemorySemanticProjectorPulser::new(MockPort::new(vec![
MemorySemanticProjectorPulseClassification::Repair,
]));
let outcome = pulser.pulse(&input("modeling"));
assert_eq!(outcome.status, MemorySemanticProjectorPulseStatus::Executed);
assert_eq!(
pulser.port.acquisitions.lock().unwrap().as_slice(),
&[("swarm-a:modeling".to_string(), "repair".to_string())]
);
}
#[test]
fn native_classification_refuses_an_advanced_sealed_input_as_stale() -> Result<()> {
let temp = tempdir()?;
let store = temp.path().join("classification.msgpack");
let current = input("modeling");
let mut cache: CultCache = semantic_projector_cache(&store)?;
cache.put(&current.obligation().obligation_id, current.obligation())?;
assert_eq!(
classify_memory_semantic_projection_for_pulse(&store, &current)?,
MemorySemanticProjectorPulseClassification::Pending
);
let mut stale = current.clone();
stale.obligation.source_generation += 1;
assert_eq!(
classify_memory_semantic_projection_for_pulse(&store, &stale)?,
MemorySemanticProjectorPulseClassification::Stale
);
Ok(())
}
#[test]
fn pending_and_failed_execute_one_action() {
for classification in [
MemorySemanticProjectorPulseClassification::Pending,
MemorySemanticProjectorPulseClassification::Failed,
] {
let pulser = MemorySemanticProjectorPulser::new(MockPort::new(vec![classification]));
let outcome = pulser.pulse(&input("modeling"));
assert_eq!(outcome.status, MemorySemanticProjectorPulseStatus::Executed);
assert_eq!(pulser.port.acquisitions.lock().unwrap().len(), 1);
assert_eq!(pulser.port.executions.lock().unwrap().len(), 1);
}
}
#[test]
fn contention_consumes_the_one_action_slot_without_false_execution() {
let port = MockPort::new(vec![MemorySemanticProjectorPulseClassification::Pending]);
*port.acquire_result.lock().unwrap() = Some(None);
let pulser = MemorySemanticProjectorPulser::new(port);
let outcome = pulser.pulse(&input("modeling"));
assert_eq!(
outcome.status,
MemorySemanticProjectorPulseStatus::Contended
);
assert_eq!(outcome.inspections.len(), 1);
assert!(pulser.port.executions.lock().unwrap().is_empty());
}
#[test]
fn owned_running_claim_resumes_without_a_second_acquisition() {
let pulser = MemorySemanticProjectorPulser::new(MockPort::new(vec![
MemorySemanticProjectorPulseClassification::RunningOwned {
claim_id: "recovered-claim".to_string(),
},
]));
let outcome = pulser.pulse(&input("modeling"));
assert_eq!(outcome.status, MemorySemanticProjectorPulseStatus::Executed);
assert!(pulser.port.acquisitions.lock().unwrap().is_empty());
assert_eq!(
pulser.port.executions.lock().unwrap().as_slice(),
["recovered-claim"]
);
}
#[test]
fn overlapping_pulse_is_busy_and_cannot_execute() {
let entered = Arc::new(Barrier::new(2));
let release = Arc::new(Barrier::new(2));
let mut port = MockPort::new(vec![MemorySemanticProjectorPulseClassification::Pending]);
port.execute_barriers = Some((entered.clone(), release.clone()));
let pulser = Arc::new(MemorySemanticProjectorPulser::new(port));
let worker = {
let pulser = pulser.clone();
std::thread::spawn(move || pulser.pulse(&input("modeling")))
};
entered.wait();
let busy = pulser.pulse(&input("modeling"));
assert_eq!(busy.status, MemorySemanticProjectorPulseStatus::Busy);
release.wait();
assert_eq!(
worker.join().unwrap().status,
MemorySemanticProjectorPulseStatus::Executed
);
assert_eq!(pulser.port.executions.lock().unwrap().len(), 1);
}
}

View file

@ -203,10 +203,8 @@ pub struct EpiphanyMindPersonaPassInputDocument {
#[cultcache(key = 3)]
pub allowed_channel_ids: Vec<String>,
#[cultcache(key = 4)]
pub dynamic_semantic_memory_recall: String,
#[cultcache(key = 5)]
pub observed_sources: Vec<EpiphanyMindDocumentVersion>,
#[cultcache(key = 6)]
#[cultcache(key = 5)]
pub admitted_at: String,
}

View file

@ -1422,7 +1422,6 @@ mod tests {
status: "terminal".into(),
reserved_at: reserved_at.into(),
mentions: vec![],
semantic_memory_recall: serde_json::Value::Null,
terminal_receipt: Some(terminal.clone()),
private_state_exposed: false,
},
@ -1730,7 +1729,6 @@ mod tests {
},
transcript: Vec::new(),
allowed_channel_ids: vec!["aquarium".into()],
dynamic_semantic_memory_recall: String::new(),
observed_sources: vec![provenance.clone()],
admitted_at: "2026-08-18T02:00:01Z".into(),
};
@ -1780,7 +1778,6 @@ mod tests {
status: "terminal".into(),
reserved_at: "2026-08-09T23:59:59Z".into(),
mentions: vec![],
semantic_memory_recall: serde_json::Value::Null,
terminal_receipt: Some(crate::PersonaTurnTerminalReceipt {
schema_version: crate::PERSONA_TURN_TERMINAL_RECEIPT_SCHEMA_VERSION.into(),
receipt_id: "turn-failed:terminal".into(),

View file

@ -4,7 +4,6 @@ use cultcache_rs::{
SingleFileMessagePackBackingStore,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use sha2::Digest;
use std::collections::{BTreeMap, HashSet};
use std::path::Path;
@ -57,8 +56,6 @@ pub struct PersonaTurnRequest {
pub reserved_at: String,
pub mentions: Vec<PersonaSocialMention>,
#[serde(default)]
pub semantic_memory_recall: Value,
#[serde(default)]
pub terminal_receipt: Option<PersonaTurnTerminalReceipt>,
pub private_state_exposed: bool,
}
@ -434,7 +431,6 @@ pub fn pulse_persona_social(
.iter()
.map(|document| document.mention.clone())
.collect(),
semantic_memory_recall: serde_json::Value::Null,
terminal_receipt: None,
private_state_exposed: false,
};
@ -715,7 +711,6 @@ pub fn persona_turn_requests(store_path: impl AsRef<Path>) -> Result<Vec<Persona
request.status = "terminal".into();
request.terminal_receipt = Some(terminal.clone());
request.mentions.clear();
request.semantic_memory_recall = serde_json::Value::Null;
}
request
})

View file

@ -3,10 +3,8 @@ use crate::PersonaSocialMention;
use anyhow::Result;
use anyhow::anyhow;
use cultcache_rs::DatabaseEntry;
use epiphany_state_model::EpiphanyMemoryContextPacket;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value;
use std::path::Path;
pub const PERSONA_PROJECTOR_PROMPT_SCHEMA_VERSION: &str =
@ -238,8 +236,6 @@ pub struct PersonaProjectorInput {
#[serde(default)]
pub memories: Vec<EpiphanyMindPersonaMemoryDocument>,
#[serde(default)]
pub semantic_memory_recall: String,
#[serde(default)]
pub pending_mentions: Vec<PersonaSocialMention>,
#[serde(default)]
pub repo_activity: Vec<PersonaRepoActivity>,
@ -261,10 +257,6 @@ pub struct PersonaInterpreterInput {
pub persona_prompt: String,
pub persona_output: String,
#[serde(default)]
pub semantic_memory_recall: String,
#[serde(default)]
pub dynamic_semantic_memory_recall: String,
#[serde(default)]
pub pending_mentions: Vec<PersonaSocialMention>,
#[serde(default)]
pub allowed_channel_ids: Vec<String>,
@ -280,11 +272,10 @@ pub fn build_persona_projector_prompt_with_transcript(
) -> String {
let memory = render_memory_packet(&input.memories);
let typed_context = format!(
"prompt schema: {schema}\nPersona identity:\n{identity}\n\nTyped memory packet:\n{memory}\n\nSemantic memory recall:\n{semantic_recall}\n\nPending addressed pressure:\n{mentions}\n\nRecent home-repo activity:\n{activity}\n\nLive social affordances:\n{affordances}",
"prompt schema: {schema}\nPersona identity:\n{identity}\n\nTyped memory packet:\n{memory}\n\nPending addressed pressure:\n{mentions}\n\nRecent home-repo activity:\n{activity}\n\nLive social affordances:\n{affordances}",
schema = PERSONA_PROJECTOR_PROMPT_SCHEMA_VERSION,
identity = render_identity(&input.identity),
memory = memory,
semantic_recall = render_semantic_memory_recall_text(&input.semantic_memory_recall),
mentions = render_pending_mentions(&input.pending_mentions),
activity = render_repo_activity(&input.repo_activity),
affordances = render_social_affordances(&input.social_affordances),
@ -336,24 +327,10 @@ Allowed channel ids:
Pending addressed pressure:
{mentions}
Initial semantic memory recall from the Persona turn:
{semantic_recall}
Dynamic self-memory recall from the Persona output:
{dynamic_semantic_recall}
"#,
schema = PERSONA_INTERPRETER_PROMPT_SCHEMA_VERSION,
channels = render_allowed_channels(&input.allowed_channel_ids),
mentions = render_pending_mentions(&input.pending_mentions),
semantic_recall = render_semantic_memory_recall_text(&input.semantic_memory_recall),
dynamic_semantic_recall = render_semantic_memory_recall_text(
input
.dynamic_semantic_memory_recall
.trim()
.is_empty()
.then_some(input.semantic_memory_recall.as_str())
.unwrap_or(input.dynamic_semantic_memory_recall.as_str())
),
);
let typed_context = format!(
"Allowed channel ids:\n{}\n\nPending addressed-pressure records:\n{}",
@ -481,75 +458,6 @@ pub fn parse_and_validate_persona_interpreter_effect_set(
Ok(parsed)
}
pub fn render_persona_semantic_memory_recall(packet: &EpiphanyMemoryContextPacket) -> String {
let mut lines = vec![
format!(
"Derived semantic recall packet `{}` from query `{}`.",
packet.id, packet.query_id
),
"These hits come from Epiphany's typed memory graph. They are hints, not durable authority; reviewed memory/state remains the owner.".to_string(),
];
if packet.summaries.is_empty() && packet.nodes.is_empty() {
lines.push("- no matching memory graph entries in this packet".to_string());
}
for (index, summary) in packet.summaries.iter().take(4).enumerate() {
lines.push(format!(
"- summary {}. {}: {}; next: {}",
index + 1,
fallback(&summary.target, "unknown target"),
compact_semantic_recall_line(&summary.claim, 420),
compact_semantic_recall_line(&summary.action_implication, 260)
));
}
for (index, node) in packet.nodes.iter().take(8).enumerate() {
lines.push(format!(
"- hit {}. {}: {}; next: {}",
index + 1,
fallback(&node.title, "untitled memory"),
compact_semantic_recall_line(&node.claim, 420),
compact_semantic_recall_line(&node.action_implication, 260)
));
}
if !packet.warnings.is_empty() {
lines.push("Warnings:".to_string());
for warning in packet.warnings.iter().take(4) {
lines.push(format!("- {}", compact_semantic_recall_line(warning, 260)));
}
}
lines.join("\n")
}
pub fn semantic_memory_recall_from_heartbeat_action(action: &Value) -> String {
let Some(recall) = action
.get("persona_memory_recall")
.or_else(|| action.get("personaMemoryRecall"))
else {
return "- semantic memory recall unavailable in heartbeat action; do not pretend it ran"
.to_string();
};
if recall.get("privateStateExposed").and_then(Value::as_bool) != Some(false) {
return "- semantic memory recall refused: heartbeat action did not carry a private-state seal"
.to_string();
}
recall
.get("renderedRecall")
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToString::to_string)
.unwrap_or_else(|| {
"- semantic memory recall unavailable in heartbeat action; do not pretend it ran"
.to_string()
})
}
pub fn persona_projected_surface_is_clean(surface: &str) -> bool {
if !ghostlight_persona_projection::narrative_stream_is_clean(surface) {
return false;
@ -673,24 +581,6 @@ fn render_allowed_channels(channel_ids: &[String]) -> String {
.join("\n")
}
fn render_semantic_memory_recall_text(value: &str) -> String {
if value.trim().is_empty() {
return "- semantic memory recall unavailable in this packet; do not pretend it ran"
.to_string();
}
value.trim().to_string()
}
fn compact_semantic_recall_line(value: &str, max_len: usize) -> String {
let mut compacted = value.split_whitespace().collect::<Vec<_>>().join(" ");
if compacted.len() > max_len {
let keep = max_len.saturating_sub(3);
compacted.truncate(keep);
compacted.push_str("...");
}
compacted
}
fn fallback<'a>(value: &'a str, fallback: &'a str) -> &'a str {
if value.trim().is_empty() {
fallback
@ -702,11 +592,6 @@ fn fallback<'a>(value: &'a str, fallback: &'a str) -> &'a str {
#[cfg(test)]
mod tests {
use super::*;
use epiphany_state_model::EpiphanyMemoryContextPacket;
use epiphany_state_model::EpiphanyMemoryFreshnessStatus;
use epiphany_state_model::EpiphanyMemoryNode;
use epiphany_state_model::EpiphanyMemorySummary;
use serde_json::json;
fn identity() -> PersonaIdentity {
PersonaIdentity {
@ -786,8 +671,6 @@ mod tests {
identity: identity(),
persona_prompt: persona,
persona_output: "I want to answer, but only if I can name the cut plainly.".to_string(),
semantic_memory_recall: String::new(),
dynamic_semantic_memory_recall: String::new(),
pending_mentions: vec![pending],
allowed_channel_ids: vec!["aquarium".to_string()],
});
@ -798,93 +681,6 @@ mod tests {
assert!(interpreter.contains("a signed Bifrost receipt alone proves"));
}
#[test]
fn persona_turn_prompts_include_semantic_memory_recall_as_hint_not_authority() {
let recall = render_persona_semantic_memory_recall(&EpiphanyMemoryContextPacket {
id: "memctx-persona-current-room".to_string(),
query_id: "persona-current-room".to_string(),
summaries: vec![EpiphanyMemorySummary {
id: "summary-role-persona".to_string(),
target: "role:Persona".to_string(),
claim: "Persona remembers that public speech should preserve dignity and clean contracts."
.to_string(),
action_implication: "Let this pressure shape tone, but route effects through Mind."
.to_string(),
freshness: EpiphanyMemoryFreshnessStatus::Ready,
confidence: 82,
..Default::default()
}],
nodes: vec![EpiphanyMemoryNode {
id: "node-persona-value-contracts".to_string(),
title: "Persona value: clean contracts".to_string(),
claim: "Clean typed contracts matter more than fast improvised glue.".to_string(),
action_implication:
"Use as Persona-local context, not as authorization to mutate state."
.to_string(),
..Default::default()
}],
..Default::default()
});
let projector = build_persona_projector_prompt(&PersonaProjectorInput {
identity: identity(),
semantic_memory_recall: recall.clone(),
..PersonaProjectorInput::default()
});
assert!(projector.contains("Semantic memory recall"));
assert!(projector.contains("typed memory graph"));
assert!(projector.contains("not durable authority"));
let persona = build_persona_turn_prompt(&PersonaTurnInput {
identity: identity(),
projected_state: "Epiphany feels the pressure of the public room.".to_string(),
});
assert!(persona.contains("Epiphany feels the pressure of the public room"));
assert!(!persona.contains("Semantic memory recall"));
assert!(!persona.contains("Clean typed contracts"));
let interpreter = build_persona_interpreter_prompt(&PersonaInterpreterInput {
identity: identity(),
persona_prompt: persona,
persona_output: "I can answer, but the answer should stay review-gated.".to_string(),
semantic_memory_recall: recall,
dynamic_semantic_memory_recall: "Output-triggered recall says review gates matter."
.to_string(),
pending_mentions: Vec::new(),
allowed_channel_ids: vec!["aquarium".to_string()],
});
assert!(interpreter.contains("Dynamic self-memory recall"));
assert!(interpreter.contains("Output-triggered recall"));
assert!(interpreter.contains("STATE NOTE"));
assert!(interpreter.contains("SAY"));
}
#[test]
fn heartbeat_action_recall_extracts_only_when_private_state_is_sealed() {
let action = json!({
"persona_memory_recall": {
"privateStateExposed": false,
"renderedRecall": "Derived Qdrant Persona recall; hints only."
}
});
assert_eq!(
semantic_memory_recall_from_heartbeat_action(&action),
"Derived Qdrant Persona recall; hints only."
);
let leaking_action = json!({
"persona_memory_recall": {
"privateStateExposed": true,
"renderedRecall": "sealed private note"
}
});
let refused = semantic_memory_recall_from_heartbeat_action(&leaking_action);
assert!(refused.contains("refused"));
assert!(!refused.contains("sealed private note"));
let missing = semantic_memory_recall_from_heartbeat_action(&json!({}));
assert!(missing.contains("unavailable"));
}
#[test]
fn projected_surface_rejects_side_effect_syntax() {

View file

@ -146,39 +146,17 @@ impl EpiphanyRepoModelView {
pub fn memory_context_projection(&self) -> EpiphanyMemoryGraphSnapshot {
EpiphanyMemoryGraphSnapshot {
schema_version: None,
graph_id: self.identity.graph_id.clone(),
model_revision: 0,
model_hash: String::new(),
source: None,
domains: self.domains.clone(),
nodes: self.nodes.clone(),
edges: self.edges.clone(),
summaries: self.summaries.clone(),
embedding_manifest: None,
freshness: None,
lifecycle_receipts: self.lifecycle_receipts.clone(),
frontier: self.frontier.clone(),
}
}
}
pub(crate) fn derive_repo_model_semantic_projection_obligation(
view: &EpiphanyRepoModelView,
created_at: &str,
) -> Result<crate::MemorySemanticProjectionObligation> {
let mut projection = view.memory_context_projection();
projection.model_revision = 1;
projection.model_hash = crate::memory_graph_model_hash(&projection)?;
crate::derive_memory_semantic_projection_obligation(
&projection,
&view.identity.swarm_id,
&format!("epiphany.runtime/{}/repo-model", view.identity.runtime_id),
&view.projection_digest,
created_at,
)
}
impl EpiphanyRepoModelBasis {
pub fn validate(&self) -> Result<()> {
let digest = self
@ -579,7 +557,6 @@ pub fn initialize_keyed_repo_model(
if !repo_model_view_matches_seed(&view, seed)? {
return Err(anyhow!("RepoModel seed replay found divergent keyed state"));
}
crate::runtime_modeling_semantic_projection_input(store_path)?;
return Ok(view);
}
@ -647,10 +624,7 @@ pub fn initialize_keyed_repo_model(
writes,
seeded_at,
)? {
crate::EpiphanyMindCommitOutcome::Committed(_) => {
crate::runtime_modeling_semantic_projection_input(store_path)?;
assemble_repo_model_view(store_path)
}
crate::EpiphanyMindCommitOutcome::Committed(_) => assemble_repo_model_view(store_path),
crate::EpiphanyMindCommitOutcome::Conflict { .. } => {
Err(anyhow!("RepoModel seed lost its exact-envelope commit"))
}
@ -1934,14 +1908,6 @@ mod tests {
.iter()
.any(|receipt| receipt.invariant_owner == "Modeling.repo_model_seed")
);
assert_eq!(
cache
.get_all::<crate::MemorySemanticProjectionObligation>()?
.into_iter()
.filter(|obligation| obligation.partition == "modeling")
.count(),
1
);
assert!(
cache
.snapshot_envelopes()
@ -2361,35 +2327,6 @@ mod tests {
.collect::<Vec<_>>(),
["node-left", "node-right"]
);
let semantic = crate::runtime_modeling_semantic_projection_input(&store)?;
assert_eq!(
semantic.source_head().source_commit_id,
reopened.projection_digest
);
assert_eq!(
semantic.obligation().source_commit_id,
reopened.projection_digest
);
assert!(
semantic
.authority
.envelopes
.iter()
.any(|envelope| { envelope.r#type == crate::EpiphanyMindCommitReceipt::TYPE })
);
let mut reopened_cache = runtime_spine_cache(&store)?;
reopened_cache.pull_all_backing_stores()?;
assert_eq!(
reopened_cache
.get_all::<crate::MemorySemanticProjectionObligation>()?
.into_iter()
.filter(|obligation| obligation.partition == "modeling")
.count(),
2
);
let replayed_semantic = crate::runtime_modeling_semantic_projection_input(&store)?;
assert_eq!(replayed_semantic.obligation(), semantic.obligation());
let competing = [
make_proposal(
"proposal-collision-left",

View file

@ -214,97 +214,6 @@ pub struct RepositoryBodyManifest {
pub entries: Vec<RepositoryBodyManifestEntry>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[allow(dead_code)] // Sealed input for the not-yet-wired coverage projector.
pub(crate) struct VerifiedRepositoryBodyBytes {
pub relative_path: String,
pub raw_sha256: String,
pub bytes: Vec<u8>,
}
/// One authenticated view of a persisted Repository Body generation.
///
/// Opening the session authenticates the runtime route and the complete
/// binding/head/observation/manifest chain once. Reads reuse that sealed view;
/// they never reload the whole CultCache store for each file.
pub(crate) struct RepositoryBodyReadSession {
basis: RepositoryBodyObservationBasis,
manifest: RepositoryBodyManifest,
root: PathBuf,
body_store: PathBuf,
}
impl RepositoryBodyReadSession {
pub(crate) fn open(
runtime_store: &Path,
basis: &RepositoryBodyObservationBasis,
) -> Result<Self> {
let route = runtime_repository_body_store_binding(runtime_store)?
.ok_or_else(|| anyhow!("runtime has no repository Body-store binding"))?;
let entries = load_body_envelopes(Path::new(&route.body_store_path))?;
let binding: RepositoryBodyBinding = decode(
find(&entries, BODY_BINDING_TYPE, BODY_BINDING_KEY)
.ok_or_else(|| anyhow!("runtime repository Body store has no Body binding"))?,
)?;
let head: RepositoryBodyHead = decode(
find(&entries, BODY_HEAD_TYPE, BODY_HEAD_KEY)
.ok_or_else(|| anyhow!("runtime repository Body store has no current Body head"))?,
)?;
let (observation, manifest) = validate_body_chain(&entries, &binding, &head)?;
if basis.schema_version != BODY_SCHEMA_VERSION
|| binding.runtime_id != route.runtime_id
|| binding.swarm_id != route.swarm_id
|| binding.workspace_id != route.workspace_id
|| body_binding_sha256(&binding)? != route.body_binding_sha256
|| basis.runtime_id != binding.runtime_id
|| basis.swarm_id != binding.swarm_id
|| basis.workspace_id != binding.workspace_id
|| basis.scope != binding.scope
|| basis.body_binding_sha256 != route.body_binding_sha256
|| head.workspace_id != basis.workspace_id
|| head.observation_id != basis.observation_id
|| head.generation != basis.generation
|| head.manifest_root_sha256 != basis.manifest_root_sha256
|| observation.observation_id != basis.observation_id
|| observation.generation != basis.generation
|| observation.manifest_root_sha256 != basis.manifest_root_sha256
|| observation.scan_started_at != basis.scan_started_at
|| observation.scan_finished_at != basis.scan_finished_at
{
bail!("repository Body read session disagrees with its authenticated route or basis");
}
let root = std::fs::canonicalize(&binding.git_top_level)
.context("bound repository root is missing")?;
if root != PathBuf::from(&binding.git_top_level) {
bail!("bound repository root is no longer canonical");
}
Ok(Self {
basis: basis.clone(),
manifest,
root,
body_store: PathBuf::from(route.body_store_path),
})
}
pub(crate) fn basis(&self) -> &RepositoryBodyObservationBasis {
&self.basis
}
pub(crate) fn manifest(&self) -> &RepositoryBodyManifest {
&self.manifest
}
pub(crate) fn body_store(&self) -> &Path {
&self.body_store
}
pub(crate) fn read_regular_file(
&self,
relative_path: &str,
) -> Result<VerifiedRepositoryBodyBytes> {
read_verified_repository_body_bytes_from_session(self, relative_path)
}
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(type = "epiphany.repository_body.head", schema = "RepositoryBodyHead")]
@ -601,115 +510,6 @@ pub fn authenticated_repository_body_manifest(
let (_, manifest) = validate_body_chain(&entries, &binding, &historical_head)?;
Ok(manifest)
}
#[allow(dead_code)] // Sealed input for the not-yet-wired coverage projector.
pub(crate) fn read_verified_repository_body_bytes(
runtime_store: &Path,
basis: &RepositoryBodyObservationBasis,
relative_path: &str,
) -> Result<VerifiedRepositoryBodyBytes> {
RepositoryBodyReadSession::open(runtime_store, basis)?.read_regular_file(relative_path)
}
fn read_verified_repository_body_bytes_from_session(
session: &RepositoryBodyReadSession,
relative_path: &str,
) -> Result<VerifiedRepositoryBodyBytes> {
validate_portable_relative_path(relative_path)?;
let entry = session
.manifest
.entries
.iter()
.find(|entry| entry.path == relative_path)
.ok_or_else(|| {
anyhow!("path {relative_path:?} is not included in the historical Body manifest")
})?;
if entry.kind != "regular"
|| (entry.git_mode != "100644" && entry.git_mode != "100755")
|| entry.gitlink_oid.is_some()
|| entry.raw_sha256.len() != 64
|| !entry
.raw_sha256
.bytes()
.all(|byte| byte.is_ascii_hexdigit())
{
bail!("historical Body entry {relative_path:?} is not an authenticated regular file");
}
let root = &session.root;
let path = safe_worktree_path(root, relative_path)?;
reject_reparse_or_symlink_components(&root, relative_path)?;
let resolved = std::fs::canonicalize(&path)
.with_context(|| format!("historical Body path {relative_path:?} is missing"))?;
if !resolved.starts_with(&root) || resolved != path {
bail!(
"historical Body path {relative_path:?} does not resolve canonically beneath its bound root"
);
}
let before = std::fs::symlink_metadata(&path)?;
if !before.file_type().is_file() || metadata_is_reparse_point(&before) {
bail!("historical Body path {relative_path:?} is no longer a regular file");
}
#[cfg(test)]
BODY_FILE_READS.set(BODY_FILE_READS.get() + 1);
let bytes = std::fs::read(&path)
.with_context(|| format!("cannot read historical Body path {relative_path:?}"))?;
let after = std::fs::symlink_metadata(&path)?;
require_stable_metadata(relative_path, &before, &after)?;
if metadata_is_reparse_point(&after) || !after.file_type().is_file() {
bail!("historical Body path {relative_path:?} changed kind during raw read");
}
let actual_sha256 = format!("{:x}", Sha256::digest(&bytes));
if bytes.len() as u64 != entry.raw_byte_length || actual_sha256 != entry.raw_sha256 {
bail!(
"historical Body path {relative_path:?} bytes disagree with its authenticated manifest entry"
);
}
Ok(VerifiedRepositoryBodyBytes {
relative_path: relative_path.into(),
raw_sha256: actual_sha256,
bytes,
})
}
fn validate_portable_relative_path(path: &str) -> Result<()> {
if path.is_empty() || path.starts_with('/') || path.contains('\\') {
bail!("unsafe or non-portable Body path {path:?}");
}
if path
.split('/')
.any(|part| part.is_empty() || part == "." || part == "..")
{
bail!("unsafe or non-portable Body path {path:?}");
}
Ok(())
}
fn reject_reparse_or_symlink_components(root: &Path, relative: &str) -> Result<()> {
let mut cursor = root.to_path_buf();
for component in relative.split('/') {
cursor.push(component);
let metadata = std::fs::symlink_metadata(&cursor)
.with_context(|| format!("historical Body path {relative:?} is missing"))?;
if metadata.file_type().is_symlink() || metadata_is_reparse_point(&metadata) {
bail!("historical Body path {relative:?} traverses a symlink or reparse point");
}
}
Ok(())
}
#[cfg(windows)]
fn metadata_is_reparse_point(metadata: &std::fs::Metadata) -> bool {
use std::os::windows::fs::MetadataExt;
metadata.file_attributes() & 0x400 != 0
}
#[cfg(not(windows))]
fn metadata_is_reparse_point(_: &std::fs::Metadata) -> bool {
false
}
pub fn observe_repository_body(
repo: &Path,
store: &Path,
@ -1949,132 +1749,6 @@ mod tests {
Ok(())
}
#[test]
fn coverage_obligation_refuses_manifest_entries_substituted_under_declared_root() -> Result<()>
{
let d = repo()?;
let state = tempfile::tempdir()?;
let (store, runtime) = bound(
d.path(),
state.path(),
"coverage-workspace",
"runtime",
"swarm",
)?;
write(&d.path().join("source.rs"), "fn original() {}")?;
run(d.path(), &["add", "."])?;
let basis = observe_runtime_repository_body_basis(&runtime)?;
let opening = SingleFileMessagePackBackingStore::new(&store).pull_all()?;
let original = find(&opening, BODY_MANIFEST_TYPE, &basis.manifest_root_sha256)
.ok_or_else(|| anyhow!("test Body manifest missing"))?;
let mut substituted: RepositoryBodyManifest = decode(original)?;
substituted.entries[0].raw_sha256 = "attacker-chosen-content-hash".into();
let replacement = envelope(
BODY_MANIFEST_TYPE,
&basis.manifest_root_sha256,
&substituted,
)?;
assert!(
SingleFileMessagePackBackingStore::new(&store)
.compare_and_swap_batch(&[original.clone()], vec![replacement])?
);
let policy = crate::WorkspaceCoveragePolicy::bounded_regular_files_v0(1024)?;
assert!(crate::derive_workspace_coverage_obligation(&runtime, &basis, &policy).is_err());
Ok(())
}
#[test]
fn verified_body_bytes_match_authenticated_historical_entry() -> Result<()> {
let d = repo()?;
let state = tempfile::tempdir()?;
let (_, runtime) = bound(d.path(), state.path(), "verified", "runtime", "swarm")?;
std::fs::write(d.path().join("source.bin"), [0_u8, 255, 7, 9])?;
run(d.path(), &["add", "."])?;
let basis = observe_runtime_repository_body_basis(&runtime)?;
let verified = read_verified_repository_body_bytes(&runtime, &basis, "source.bin")?;
assert_eq!(verified.relative_path, "source.bin");
assert_eq!(verified.bytes, [0_u8, 255, 7, 9]);
assert_eq!(
verified.raw_sha256,
format!("{:x}", Sha256::digest(&verified.bytes))
);
Ok(())
}
#[test]
fn verified_body_bytes_refuse_changed_or_substituted_current_body() -> Result<()> {
let d = repo()?;
let state = tempfile::tempdir()?;
let (_, runtime) = bound(d.path(), state.path(), "verified", "runtime", "swarm")?;
write(&d.path().join("tracked"), "AAAA")?;
run(d.path(), &["add", "."])?;
let historical = observe_runtime_repository_body_basis(&runtime)?;
write(&d.path().join("tracked"), "BBBB")?;
assert!(
read_verified_repository_body_bytes(&runtime, &historical, "tracked")
.unwrap_err()
.to_string()
.contains("bytes disagree")
);
let advanced = observe_runtime_repository_body_basis(&runtime)?;
assert!(advanced.generation > historical.generation);
assert_eq!(
read_verified_repository_body_bytes(&runtime, &advanced, "tracked")?.bytes,
b"BBBB"
);
std::fs::remove_file(d.path().join("tracked"))?;
std::fs::create_dir(d.path().join("tracked"))?;
assert!(
read_verified_repository_body_bytes(&runtime, &advanced, "tracked")
.unwrap_err()
.to_string()
.contains("regular file")
);
Ok(())
}
#[test]
fn verified_body_bytes_refuse_noncanonical_and_nonmanifest_paths() -> Result<()> {
let d = repo()?;
let state = tempfile::tempdir()?;
let (_, runtime) = bound(d.path(), state.path(), "verified", "runtime", "swarm")?;
write(&d.path().join("tracked"), "body")?;
run(d.path(), &["add", "."])?;
let basis = observe_runtime_repository_body_basis(&runtime)?;
for path in [
"",
"/tracked",
"../tracked",
"a/../tracked",
"./tracked",
"a\\tracked",
] {
assert!(read_verified_repository_body_bytes(&runtime, &basis, path).is_err());
}
assert!(read_verified_repository_body_bytes(&runtime, &basis, "untracked").is_err());
Ok(())
}
#[cfg(unix)]
#[test]
fn verified_body_bytes_refuse_symlink_substitution_and_parent_escape() -> Result<()> {
use std::os::unix::fs::symlink;
let d = repo()?;
let state = tempfile::tempdir()?;
let (_, runtime) = bound(d.path(), state.path(), "verified", "runtime", "swarm")?;
std::fs::create_dir(d.path().join("inside"))?;
write(&d.path().join("inside/tracked"), "body")?;
run(d.path(), &["add", "."])?;
let basis = observe_runtime_repository_body_basis(&runtime)?;
std::fs::remove_file(d.path().join("inside/tracked"))?;
symlink("../.git/HEAD", d.path().join("inside/tracked"))?;
assert!(read_verified_repository_body_bytes(&runtime, &basis, "inside/tracked").is_err());
std::fs::remove_file(d.path().join("inside/tracked"))?;
std::fs::remove_dir(d.path().join("inside"))?;
symlink(".git", d.path().join("inside"))?;
assert!(read_verified_repository_body_bytes(&runtime, &basis, "inside/tracked").is_err());
Ok(())
}
#[test]
fn ignored_and_mtime_only_are_unchanged() -> Result<()> {
let d = repo()?;
let state = tempfile::tempdir()?;

File diff suppressed because it is too large Load diff

View file

@ -990,15 +990,6 @@ pub fn runtime_spine_cache(store_path: impl AsRef<Path>) -> Result<CultCache> {
cache.register_entry_type::<crate::AtlasDependencyClaimWriteIntent>()?;
cache.register_entry_type::<crate::AtlasDependencyVerificationWriteIntent>()?;
cache.register_entry_type::<crate::AtlasDependencyImpactWriteIntent>()?;
cache.register_entry_type::<crate::MemorySemanticProjectionObligation>()?;
cache.register_entry_type::<crate::MemorySemanticProjectionClaim>()?;
cache.register_entry_type::<crate::MemorySemanticProjectionAttempt>()?;
cache.register_entry_type::<crate::MemorySemanticIndexReceipt>()?;
cache.register_entry_type::<crate::MemorySemanticProjectorExecutorGrant>()?;
cache.register_entry_type::<crate::MemorySemanticProjectorRecoveryAuthorization>()?;
cache.register_entry_type::<crate::MemorySemanticProjectionRetentionHead>()?;
cache.register_entry_type::<crate::MemorySemanticPhysicalRetirementObligation>()?;
cache.register_entry_type::<crate::MemorySemanticPhysicalRetirementReceipt>()?;
cache.register_entry_type::<EpiphanyRuntimeSession>()?;
cache.register_entry_type::<EpiphanyRuntimeJob>()?;
cache.register_entry_type::<EpiphanyRuntimeModelExecutionBinding>()?;
@ -1012,9 +1003,7 @@ pub fn runtime_spine_cache(store_path: impl AsRef<Path>) -> Result<CultCache> {
cache.register_entry_type::<EpiphanyRuntimeWorkerProcessClaim>()?;
cache.register_entry_type::<EpiphanyArchivedRuntimeWorkerAttempt>()?;
cache.register_entry_type::<EpiphanyRuntimeRoleWorkerResult>()?;
cache.register_entry_type::<crate::RepositoryReadinessProjection>()?;
cache.register_entry_type::<crate::RuntimeRepositoryBodyStoreBinding>()?;
cache.register_entry_type::<crate::RuntimeWorkspaceCoverageStoreBinding>()?;
cache.register_entry_type::<RepoModelClaimChallenge>()?;
cache.register_entry_type::<RepoFrontierRoute>()?;
cache.register_entry_type::<RepoFrontierHandsAuthority>()?;
@ -1253,25 +1242,6 @@ pub fn runtime_swarm_binding(
cache.get(RUNTIME_SWARM_BINDING_KEY)
}
fn require_runtime_swarm_binding(cache: &CultCache) -> Result<EpiphanyRuntimeSwarmBinding> {
let identity = require_identity(cache)?;
let binding = cache
.get::<EpiphanyRuntimeSwarmBinding>(RUNTIME_SWARM_BINDING_KEY)?
.ok_or_else(|| anyhow!("RepoModel admission requires immutable runtime swarm binding"))?;
if binding.schema_version != RUNTIME_SWARM_BINDING_SCHEMA_VERSION
|| binding.binding_id != RUNTIME_SWARM_BINDING_KEY
|| binding.runtime_id != identity.runtime_id
|| binding.swarm_id.trim().is_empty()
|| binding.source_identity_type != RUNTIME_IDENTITY_TYPE
|| binding.source_identity_key != RUNTIME_IDENTITY_KEY
|| binding.source_identity_sha256.trim().is_empty()
|| chrono::DateTime::parse_from_rfc3339(&binding.bound_at).is_err()
{
return Err(anyhow!("runtime swarm binding is invalid"));
}
Ok(binding)
}
pub fn create_runtime_session(
store_path: impl AsRef<Path>,
options: RuntimeSpineSessionOptions,
@ -6349,133 +6319,6 @@ fn keyed_repo_model_basis_after_writes(
Ok(basis)
}
pub fn runtime_modeling_semantic_projection_input(
store_path: impl AsRef<Path>,
) -> Result<crate::MemorySemanticProjectionInput> {
let store_path = store_path.as_ref();
// Projection work is cache physiology, not a Mind commit companion. A
// singleton companion would make otherwise-disjoint RepoModel commits
// contend. Reconstruct the exact current keyed basis on every pulse and
// insert its content-addressed work item under the same envelope CAS used
// by the projector. Concurrent inserts can create an older work item, but
// cannot make it current; the next pulse derives the newly assembled view.
for _ in 0..8 {
let mut cache = runtime_spine_cache(store_path)?;
cache.pull_all_backing_stores()?;
let binding = require_runtime_swarm_binding(&cache)?;
let view = crate::repo_model_documents::assemble_repo_model_view_from_cache(&cache)?;
if view.identity.runtime_id != binding.runtime_id
|| view.identity.swarm_id != binding.swarm_id
{
return Err(anyhow!(
"RepoModel semantic basis crossed its runtime swarm binding"
));
}
let basis = view.reasoning_basis();
basis.validate_against_cache(&cache)?;
let mut snapshot = view.memory_context_projection();
// This is a compatibility-shaped projector DTO, never a causal head.
// Keyed document versions and their commit receipts own the input.
snapshot.model_revision = 1;
snapshot.model_hash = crate::memory_graph_model_hash(&snapshot)?;
let mut authority_envelopes = keyed_repo_model_basis_envelopes(&cache, &basis)?;
let receipts = repo_model_basis_commit_receipts(&cache, &basis)?;
if receipts.is_empty() {
return Err(anyhow!(
"RepoModel semantic basis has no Mind commit receipts"
));
}
for receipt in &receipts {
authority_envelopes.push(
cache
.get_envelope::<crate::EpiphanyMindCommitReceipt>(&receipt.receipt_id)?
.ok_or_else(|| anyhow!("RepoModel semantic basis receipt disappeared"))?,
);
}
authority_envelopes.push(
cache
.get_envelope::<EpiphanyRuntimeSwarmBinding>(RUNTIME_SWARM_BINDING_KEY)?
.ok_or_else(|| anyhow!("Modeling projection lost its swarm binding"))?,
);
authority_envelopes
.sort_by(|left, right| (&left.r#type, &left.key).cmp(&(&right.r#type, &right.key)));
authority_envelopes
.dedup_by(|left, right| left.r#type == right.r#type && left.key == right.key);
let obligation =
crate::repo_model_documents::derive_repo_model_semantic_projection_obligation(
&view,
// Modeling cache work is content-addressed by the complete
// keyed basis. Wall-clock receipt order is not causal state.
"1970-01-01T00:00:00Z",
)?;
if let Some(persisted) =
cache.get::<crate::MemorySemanticProjectionObligation>(&obligation.obligation_id)?
{
if persisted != obligation {
return Err(anyhow!("Modeling projection obligation identity collision"));
}
return Ok(crate::MemorySemanticProjectionInput {
snapshot,
authority: crate::memory_graph::MemorySemanticProjectionAuthoritySnapshot {
head: crate::MemorySemanticProjectionSourceHead {
swarm_id: obligation.swarm_id.clone(),
partition: obligation.partition.clone(),
canonical_source_id: obligation.canonical_source_id.clone(),
source_commit_id: obligation.source_commit_id.clone(),
graph_id: obligation.graph_id.clone(),
source_generation: obligation.source_generation,
source_model_hash: obligation.source_model_hash.clone(),
canonical_content_set_hash: obligation.canonical_content_set_hash.clone(),
},
envelopes: authority_envelopes,
},
obligation,
});
}
let mut replacements = authority_envelopes.clone();
replacements.push(
cache
.prepare_entry(&obligation.obligation_id, &obligation)?
.0,
);
if runtime_spine_backing_store(store_path)?
.compare_and_swap_batch(&authority_envelopes, replacements)?
{
continue;
}
}
Err(anyhow!(
"RepoModel semantic work could not settle under concurrent Mind mutation"
))
}
fn repo_model_basis_commit_receipts(
cache: &CultCache,
basis: &crate::EpiphanyRepoModelBasis,
) -> Result<Vec<crate::EpiphanyMindCommitReceipt>> {
basis.validate_against_cache(cache)?;
let receipts = cache.get_all::<crate::EpiphanyMindCommitReceipt>()?;
let mut owners = BTreeMap::<String, crate::EpiphanyMindCommitReceipt>::new();
for source in &basis.source_documents {
let owner = receipts
.iter()
.filter(|receipt| receipt.writes.iter().any(|write| write == source))
.min_by(|left, right| left.receipt_id.cmp(&right.receipt_id))
.ok_or_else(|| {
anyhow!(
"RepoModel document {}:{} has no exact Mind commit receipt",
source.document_type,
source.document_key
)
})?;
owners.insert(owner.receipt_id.clone(), owner.clone());
}
Ok(owners.into_values().collect())
}
pub fn select_repo_frontier_work_proposal_for_modeling(
store_path: impl AsRef<Path>,
proposal_id: &str,

File diff suppressed because it is too large Load diff

View file

@ -1,293 +0,0 @@
use crate::memory_graph::semantic_projector::{
classify_memory_semantic_projection_for_pulse, execute_memory_semantic_projection,
idunn_acquire_memory_semantic_projection_with_config,
owned_running_memory_semantic_projection_claim,
};
use crate::memory_graph::{
MemorySemanticProjectorPulseClassification, MemorySemanticProjectorPulseOutcome,
MemorySemanticProjectorPulsePort, MemorySemanticProjectorPulser,
};
use crate::{
MemorySemanticIndexConfig, MemorySemanticProjectionInput,
runtime_modeling_semantic_projection_input,
};
use anyhow::{Result, anyhow};
use chrono::{SecondsFormat, Utc};
use sha2::{Digest, Sha256};
#[cfg(windows)]
use std::ffi::OsStr;
#[cfg(unix)]
use std::fs::File;
#[cfg(unix)]
use std::os::fd::AsRawFd;
#[cfg(windows)]
use std::os::windows::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use uuid::Uuid;
#[cfg(windows)]
use windows_sys::Win32::Foundation::{CloseHandle, ERROR_ALREADY_EXISTS, GetLastError, HANDLE};
#[cfg(windows)]
use windows_sys::Win32::System::Threading::{CreateMutexW, ReleaseMutex};
/// Trusted in-process authority held by the one Idunn-managed projector body.
/// The constructor generates process-stable incarnations; callers cannot label
/// themselves as an existing Idunn or executor incarnation.
const SEMANTIC_PROJECTOR_EXECUTOR_ID: &str = "epiphany-memory-semantic-projector";
struct LocalIdunnSemanticProjectorSession {
modeling_store: PathBuf,
executor_id: String,
executor_incarnation: String,
idunn_incarnation: String,
config: MemorySemanticIndexConfig,
}
struct SemanticProjectorSingletonGuard {
#[cfg(windows)]
handle: HANDLE,
#[cfg(unix)]
file: File,
}
impl Drop for SemanticProjectorSingletonGuard {
fn drop(&mut self) {
#[cfg(windows)]
unsafe {
let _ = ReleaseMutex(self.handle);
CloseHandle(self.handle);
}
#[cfg(unix)]
unsafe {
let _ = libc::flock(self.file.as_raw_fd(), libc::LOCK_UN);
}
}
}
fn acquire_semantic_projector_singleton(
modeling_store: impl AsRef<Path>,
) -> Result<SemanticProjectorSingletonGuard> {
let modeling = canonical_file(&modeling_store, "Modeling")?;
let identity = format!(
"{:x}",
Sha256::digest(modeling.to_string_lossy().to_lowercase().as_bytes())
);
#[cfg(windows)]
{
let name = OsStr::new(&format!("Global\\EpiphanySemanticProjector-{identity}"))
.encode_wide()
.chain(std::iter::once(0))
.collect::<Vec<_>>();
let handle = unsafe { CreateMutexW(std::ptr::null_mut(), 1, name.as_ptr()) };
if handle.is_null() {
return Err(anyhow!(
"semantic projector singleton mutex creation failed: {}",
unsafe { GetLastError() }
));
}
if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS {
unsafe { CloseHandle(handle) };
return Err(anyhow!(
"semantic projector owner already exists for canonical store pair"
));
}
return Ok(SemanticProjectorSingletonGuard { handle });
}
#[cfg(unix)]
{
let path = std::env::temp_dir().join(format!("epiphany-semantic-{identity}.lock"));
let file = File::options()
.create(true)
.read(true)
.write(true)
.open(path)?;
if unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) } != 0 {
return Err(anyhow!(
"semantic projector owner already exists for canonical store pair"
));
}
return Ok(SemanticProjectorSingletonGuard { file });
}
#[allow(unreachable_code)]
Err(anyhow!(
"semantic projector singleton is unsupported on this platform"
))
}
impl LocalIdunnSemanticProjectorSession {
fn new(
modeling_store: impl AsRef<Path>,
executor_id: impl Into<String>,
config: MemorySemanticIndexConfig,
) -> Result<Self> {
let modeling_store = canonical_file(&modeling_store, "Modeling")?;
let executor_id = executor_id.into();
if executor_id.trim().is_empty() {
return Err(anyhow!("semantic projector executor id must not be empty"));
}
Ok(Self {
modeling_store,
executor_id,
executor_incarnation: format!("projector-{}", Uuid::new_v4()),
idunn_incarnation: format!("idunn-{}", Uuid::new_v4()),
config,
})
}
pub fn provider_incarnation(&self) -> &str {
&self.executor_incarnation
}
fn store_for(&self, input: &MemorySemanticProjectionInput) -> Result<&Path> {
match input.obligation().partition.as_str() {
"modeling" => Ok(&self.modeling_store),
other => Err(anyhow!(
"unsupported semantic projector partition {other:?}"
)),
}
}
}
/// The only public authority-bearing semantic projector body. Construction
/// atomically takes the host-wide singleton before minting a process session,
/// and the guard remains owned for the body's full lifetime.
pub struct SemanticProjectorServiceBody {
_singleton: SemanticProjectorSingletonGuard,
pulser: MemorySemanticProjectorPulser<LocalIdunnSemanticProjectorSession>,
provider_incarnation: String,
}
pub struct SemanticProjectorServicePulse {
pub outcome: MemorySemanticProjectorPulseOutcome,
pub inputs: Vec<MemorySemanticProjectionInput>,
pub source_fault_count: u32,
}
impl SemanticProjectorServiceBody {
pub fn new(
modeling_store: impl AsRef<Path>,
config: MemorySemanticIndexConfig,
) -> Result<Self> {
let singleton = acquire_semantic_projector_singleton(&modeling_store)?;
let session = LocalIdunnSemanticProjectorSession::new(
modeling_store,
SEMANTIC_PROJECTOR_EXECUTOR_ID,
config,
)?;
let provider_incarnation = session.provider_incarnation().to_string();
Ok(Self {
_singleton: singleton,
pulser: MemorySemanticProjectorPulser::new(session),
provider_incarnation,
})
}
pub fn provider_incarnation(&self) -> &str {
&self.provider_incarnation
}
pub fn pulse(&self) -> SemanticProjectorServicePulse {
let session = &self.pulser.port;
let (inputs, source_fault_count) =
match runtime_modeling_semantic_projection_input(&session.modeling_store) {
Ok(input) => (vec![input], 0),
Err(_) => (Vec::new(), 1),
};
let outcome = inputs.first().map_or_else(
|| MemorySemanticProjectorPulseOutcome {
status: crate::MemorySemanticProjectorPulseStatus::Idle,
selected_scope_id: None,
inspections: Vec::new(),
error: None,
},
|input| self.pulser.pulse(input),
);
SemanticProjectorServicePulse {
outcome,
inputs,
source_fault_count,
}
}
}
impl MemorySemanticProjectorPulsePort for LocalIdunnSemanticProjectorSession {
fn classify(
&self,
input: &MemorySemanticProjectionInput,
) -> Result<MemorySemanticProjectorPulseClassification> {
let store = self.store_for(input)?;
let classification = classify_memory_semantic_projection_for_pulse(store, input)?;
if classification == MemorySemanticProjectorPulseClassification::Running {
if let Some(claim_id) = owned_running_memory_semantic_projection_claim(
store,
input,
&self.executor_id,
&self.executor_incarnation,
)? {
return Ok(MemorySemanticProjectorPulseClassification::RunningOwned { claim_id });
}
}
Ok(classification)
}
fn acquire(
&self,
input: &MemorySemanticProjectionInput,
purpose: &str,
) -> Result<Option<String>> {
let store = self.store_for(input)?;
match idunn_acquire_memory_semantic_projection_with_config(
store,
input,
&self.executor_id,
&self.executor_incarnation,
purpose,
&self.idunn_incarnation,
&Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true),
&self.config,
) {
Ok(acquisition) => Ok(Some(acquisition.claim.claim_id)),
Err(_error)
if classify_memory_semantic_projection_for_pulse(store, input)?
== MemorySemanticProjectorPulseClassification::Running =>
{
Ok(None)
}
Err(error) => Err(error),
}
}
fn execute(&self, input: &MemorySemanticProjectionInput, claim_id: &str) -> Result<()> {
execute_memory_semantic_projection(self.store_for(input)?, input, claim_id, &self.config)?;
Ok(())
}
}
fn canonical_file(path: &impl AsRef<Path>, label: &str) -> Result<PathBuf> {
let path = path.as_ref();
if !path.is_file() {
return Err(anyhow!(
"{label} canonical store is missing: {}",
path.display()
));
}
path.canonicalize()
.map_err(|error| anyhow!("{label} canonical store identity is unavailable: {error}"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn singleton_refuses_a_second_process_owner_identity() -> Result<()> {
let temp = tempfile::tempdir()?;
let modeling = temp.path().join("modeling.ccmp");
std::fs::write(&modeling, [])?;
let first = acquire_semantic_projector_singleton(&modeling)?;
assert!(acquire_semantic_projector_singleton(&modeling).is_err());
drop(first);
assert!(acquire_semantic_projector_singleton(&modeling).is_ok());
Ok(())
}
}

View file

@ -1,91 +0,0 @@
use anyhow::{Context, Result, anyhow, bail};
use std::io::{Read, Write};
use uuid::Uuid;
use zeroize::Zeroize;
const MAGIC: &[u8; 8] = b"EPHWCOV\0";
const FRAME_LEN: usize = MAGIC.len() + 16 + 32;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorkspaceCoverageProcessBootstrap {
pub launch_id: Uuid,
pub provider_signing_seed: [u8; 32],
}
impl Drop for WorkspaceCoverageProcessBootstrap {
fn drop(&mut self) {
self.provider_signing_seed.zeroize();
}
}
pub fn write_workspace_coverage_process_bootstrap(
mut writer: impl Write,
frame: &WorkspaceCoverageProcessBootstrap,
) -> Result<()> {
let mut bytes = [0_u8; FRAME_LEN];
bytes[..MAGIC.len()].copy_from_slice(MAGIC);
bytes[MAGIC.len()..MAGIC.len() + 16].copy_from_slice(frame.launch_id.as_bytes());
bytes[MAGIC.len() + 16..].copy_from_slice(&frame.provider_signing_seed);
writer
.write_all(&bytes)
.context("failed to write workspace coverage bootstrap frame")?;
writer
.flush()
.context("failed to flush workspace coverage bootstrap frame")
}
pub fn read_workspace_coverage_process_bootstrap(
mut reader: impl Read,
) -> Result<WorkspaceCoverageProcessBootstrap> {
let mut bytes = [0_u8; FRAME_LEN];
reader
.read_exact(&mut bytes)
.context("workspace coverage bootstrap frame is truncated")?;
let mut trailing = [0_u8; 1];
if reader
.read(&mut trailing)
.context("failed to verify workspace coverage bootstrap EOF")?
!= 0
{
bail!("workspace coverage bootstrap contains trailing bytes");
}
if &bytes[..MAGIC.len()] != MAGIC {
bail!("workspace coverage bootstrap magic is invalid");
}
let launch_id = Uuid::from_slice(&bytes[MAGIC.len()..MAGIC.len() + 16])
.map_err(|_| anyhow!("workspace coverage bootstrap launch id is invalid"))?;
let provider_signing_seed = bytes[MAGIC.len() + 16..]
.try_into()
.map_err(|_| anyhow!("workspace coverage bootstrap seed length is invalid"))?;
Ok(WorkspaceCoverageProcessBootstrap {
launch_id,
provider_signing_seed,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fixed_frame_round_trips_and_requires_eof() {
let frame = WorkspaceCoverageProcessBootstrap {
launch_id: Uuid::new_v4(),
provider_signing_seed: [7; 32],
};
let mut encoded = Vec::new();
write_workspace_coverage_process_bootstrap(&mut encoded, &frame).unwrap();
assert_eq!(encoded.len(), FRAME_LEN);
assert_eq!(
read_workspace_coverage_process_bootstrap(encoded.as_slice()).unwrap(),
frame
);
encoded.push(0);
assert!(read_workspace_coverage_process_bootstrap(encoded.as_slice()).is_err());
}
#[test]
fn truncated_frame_is_rejected() {
assert!(read_workspace_coverage_process_bootstrap(&[0_u8; FRAME_LEN - 1][..]).is_err());
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,495 +0,0 @@
use crate::repository_body_observer::RepositoryBodyReadSession;
use crate::semantic_backend::{OllamaConfig, OllamaEmbedder, QdrantBackend, QdrantConfig};
use crate::workspace_coverage_process_documents::{
authenticate_workspace_coverage_recovery_directive, publish_workspace_coverage_claim_sight,
publish_workspace_coverage_terminal_sight,
};
use crate::workspace_coverage_projector::{
WorkspaceCoverageAcquireResult, WorkspaceCoverageCurrentState,
WorkspaceCoverageProjectionExecutionAuthority, acquire_workspace_coverage_projection,
classify_current_workspace_coverage, compact_workspace_coverage_history,
execute_workspace_coverage_projection, prepare_workspace_coverage_projection,
retire_workspace_coverage_collections,
};
use crate::{
EPIPHANY_WORKSPACE_COVERAGE_PROJECTOR_DAEMON_ID, HostIncarnationIdentityEntry,
WorkspaceCoverageAuthority, load_current_runtime_repository_body_basis,
open_workspace_coverage_authority, runtime_repository_body_store_binding,
};
use anyhow::{Context, Result, anyhow, bail};
use ed25519_dalek::SigningKey;
use std::path::{Path, PathBuf};
use uuid::Uuid;
const EMBEDDING_PROVIDER_ID: &str = "gamecult-ollama-embedding";
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorkspaceCoverageProjectorConfig {
pub qdrant_url: String,
pub qdrant_api_key: Option<String>,
pub qdrant_timeout_ms: u64,
pub ollama_base_url: String,
pub ollama_model: String,
pub ollama_timeout_ms: u64,
}
impl WorkspaceCoverageProjectorConfig {
pub fn from_env() -> Self {
let semantic = crate::MemorySemanticIndexConfig::from_env();
Self {
qdrant_url: semantic.qdrant_url,
qdrant_api_key: semantic.qdrant_api_key,
qdrant_timeout_ms: semantic.qdrant_timeout_ms,
ollama_base_url: semantic.ollama_base_url,
ollama_model: semantic.ollama_model,
ollama_timeout_ms: semantic.ollama_timeout_ms,
}
}
fn validate(&self) -> Result<()> {
for (label, value) in [
("Qdrant URL", self.qdrant_url.as_str()),
("Ollama base URL", self.ollama_base_url.as_str()),
("Ollama model", self.ollama_model.as_str()),
] {
if value.trim().is_empty() {
bail!("workspace coverage projector requires a non-empty {label}");
}
}
if self.qdrant_timeout_ms == 0 || self.ollama_timeout_ms == 0 {
bail!("workspace coverage projector timeouts must be positive");
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WorkspaceCoverageProjectorPulseStatus {
Idle,
Executed,
Contended,
Refused,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorkspaceCoverageProjectorServicePulse {
pub status: WorkspaceCoverageProjectorPulseStatus,
pub body_observation_id: Option<String>,
pub body_generation: Option<u64>,
pub plan_id: Option<String>,
pub receipt_id: Option<String>,
pub fault: Option<String>,
}
pub struct WorkspaceCoverageProjectorServiceBody {
coverage_authority: WorkspaceCoverageAuthority,
runtime_store: PathBuf,
executor_incarnation: String,
managed_process_launch_id: String,
embedder: OllamaEmbedder,
qdrant: QdrantBackend,
execution_authority: Option<WorkspaceCoverageProjectorExecutionAuthority>,
operation_timeout_ms: u64,
}
#[derive(Clone)]
struct WorkspaceCoverageProjectorExecutionAuthority {
local_verse_store: PathBuf,
runtime_id: String,
trusted_host: HostIncarnationIdentityEntry,
provider_signing_key: SigningKey,
}
impl WorkspaceCoverageProjectorServiceBody {
pub fn new(
runtime_store: impl AsRef<Path>,
runtime_id: impl Into<String>,
config: WorkspaceCoverageProjectorConfig,
executor_incarnation: impl Into<String>,
managed_process_launch_id: impl Into<String>,
) -> Result<Self> {
config.validate()?;
let executor_incarnation = executor_incarnation.into();
if executor_incarnation.trim().is_empty() {
bail!("workspace coverage projector requires its executor incarnation");
}
let managed_process_launch_id = managed_process_launch_id.into();
Uuid::parse_str(&managed_process_launch_id).context(
"workspace coverage projector requires its authenticated managed process launch id",
)?;
let runtime_id = runtime_id.into();
if runtime_id.trim().is_empty() {
bail!("workspace coverage projector requires its CultMesh runtime id");
}
let runtime_store = runtime_store
.as_ref()
.canonicalize()
.context("workspace coverage projector runtime store is missing")?;
if !runtime_store.is_file() {
bail!("workspace coverage projector runtime store is not a file");
}
let route = runtime_repository_body_store_binding(&runtime_store)?
.ok_or_else(|| anyhow!("runtime has no repository Body-store binding"))?;
if route.runtime_id != runtime_id {
bail!(
"workspace coverage projector runtime Body route disagrees with authenticated runtime id"
);
}
let coverage_authority = open_workspace_coverage_authority(&runtime_store)?;
let operation_timeout_ms = config.qdrant_timeout_ms;
let qdrant = QdrantBackend::new(QdrantConfig {
url: config.qdrant_url,
api_key: config.qdrant_api_key,
timeout_ms: config.qdrant_timeout_ms,
})?;
let embedder = OllamaEmbedder::new(OllamaConfig {
base_url: config.ollama_base_url,
model: config.ollama_model,
timeout_ms: config.ollama_timeout_ms,
query_instruction: String::new(),
})?;
Ok(Self {
coverage_authority,
runtime_store,
executor_incarnation,
managed_process_launch_id,
embedder,
qdrant,
execution_authority: None,
operation_timeout_ms,
})
}
pub fn install_execution_authority(
&mut self,
local_verse_store: impl AsRef<Path>,
runtime_id: impl Into<String>,
trusted_host: HostIncarnationIdentityEntry,
provider_signing_key: SigningKey,
) -> Result<()> {
let runtime_id = runtime_id.into();
if runtime_id.trim().is_empty() {
bail!("workspace coverage execution authority requires its runtime id");
}
let bound = runtime_repository_body_store_binding(&self.runtime_store)?
.ok_or_else(|| anyhow!("runtime has no repository Body-store binding"))?;
if bound.runtime_id != runtime_id {
bail!("workspace coverage execution authority runtime disagrees with Body route");
}
self.execution_authority = Some(WorkspaceCoverageProjectorExecutionAuthority {
local_verse_store: local_verse_store.as_ref().to_path_buf(),
runtime_id,
trusted_host,
provider_signing_key,
});
Ok(())
}
pub fn provider_incarnation(&self) -> &str {
&self.executor_incarnation
}
pub fn managed_process_launch_id(&self) -> &str {
&self.managed_process_launch_id
}
pub fn pulse(&mut self) -> WorkspaceCoverageProjectorServicePulse {
match self.pulse_inner() {
Ok(pulse) => pulse,
Err(_error) => {
eprintln!("workspace coverage pulse failed: {_error:#}");
WorkspaceCoverageProjectorServicePulse {
status: WorkspaceCoverageProjectorPulseStatus::Refused,
body_observation_id: None,
body_generation: None,
plan_id: None,
receipt_id: None,
// Managed stdout is an operator-safe projection. Raw backend
// bodies and filesystem paths remain private to the process.
fault: Some("workspace_coverage_pulse_refused".into()),
}
}
}
}
fn pulse_inner(&mut self) -> Result<WorkspaceCoverageProjectorServicePulse> {
let authority = self.execution_authority.as_ref().ok_or_else(|| {
anyhow!("workspace coverage projector has no installed execution authority")
})?;
// Retirement is derived from typed Body history and is deliberately
// performed before the idle fast path. Qdrant never nominates its own
// garbage and incompatible same-name collections stop the pulse.
retire_workspace_coverage_collections(&self.coverage_authority, &mut self.qdrant)?;
let _ = compact_workspace_coverage_history(&self.runtime_store, &self.coverage_authority)?;
let basis = load_current_runtime_repository_body_basis(&self.runtime_store)?;
// Tags are mutable. Re-resolve the installed artifact every pulse so a
// same-dimensional model replacement cannot reuse an older index.
let embedding_model_identity = self.embedder.model_artifact()?.canonical_identity();
let dimensions = self.embedder.embedding_dimensions()?;
if let WorkspaceCoverageCurrentState::Current(receipt) =
classify_current_workspace_coverage(
&self.runtime_store,
&self.coverage_authority.store,
&basis,
EMBEDDING_PROVIDER_ID,
&embedding_model_identity,
dimensions,
)?
{
let authority = self.execution_authority.as_ref().ok_or_else(|| {
anyhow!("workspace coverage projector has no installed execution authority")
})?;
publish_workspace_coverage_terminal_sight(
&authority.local_verse_store,
&self.runtime_store,
&self.coverage_authority,
&authority.runtime_id,
&self.managed_process_launch_id,
&authority.trusted_host,
&authority.provider_signing_key,
chrono::Utc::now(),
)?;
return Ok(WorkspaceCoverageProjectorServicePulse {
status: WorkspaceCoverageProjectorPulseStatus::Idle,
body_observation_id: Some(basis.observation_id),
body_generation: Some(basis.generation),
plan_id: Some(receipt.plan_id.clone()),
receipt_id: Some(receipt.receipt_id),
fault: None,
});
}
// A durable terminal projection outranks process recovery. Its signed
// historical sight remains valid across managed-policy rotation; a new
// daemon observes that fact but does not take ownership of the old
// succeeded claim. Recovery still precedes all nonterminal claim work.
if let Some(directive) = authenticate_workspace_coverage_recovery_directive(
&authority.local_verse_store,
&self.runtime_store,
&authority.runtime_id,
&self.managed_process_launch_id,
&authority.trusted_host,
)? {
if directive.workspace_id != basis.workspace_id
|| directive.body_binding_sha256 != basis.body_binding_sha256
|| directive.body_observation_id != basis.observation_id
|| directive.body_generation != basis.generation
|| directive.manifest_root_sha256 != basis.manifest_root_sha256
|| directive.coverage_store_binding_id
!= self.coverage_authority.store_binding.binding_id
|| directive.coverage_store_binding_envelope_digest
!= self.coverage_authority.store_binding_envelope_sha256
|| directive.coverage_store_file_identity
!= self.coverage_authority.store_binding.store_file_identity
|| directive.runtime_coverage_route_envelope_digest
!= self
.coverage_authority
.runtime_coverage_route_envelope_sha256
{
bail!("workspace coverage recovery directive disagrees with held authority");
}
crate::workspace_coverage_projector::recover_workspace_coverage_projection_with_authority(
&self.coverage_authority, &self.runtime_store, &authority.local_verse_store,
&authority.runtime_id, &authority.trusted_host, &directive.old_launch_id,
&directive.replacement_launch_id, &directive.replacement_ready_heartbeat_id,
&directive.old_claim_id,
)?;
}
let body = RepositoryBodyReadSession::open(&self.runtime_store, &basis)?;
let prepared = prepare_workspace_coverage_projection(
&body,
EMBEDDING_PROVIDER_ID,
&embedding_model_identity,
dimensions,
)?;
let plan_id = prepared.plan.plan_id.clone();
match acquire_workspace_coverage_projection(
&prepared,
&self.coverage_authority.store,
EPIPHANY_WORKSPACE_COVERAGE_PROJECTOR_DAEMON_ID,
&self.executor_incarnation,
&self.managed_process_launch_id,
)? {
WorkspaceCoverageAcquireResult::Contended => {
Ok(WorkspaceCoverageProjectorServicePulse {
status: WorkspaceCoverageProjectorPulseStatus::Contended,
body_observation_id: Some(basis.observation_id),
body_generation: Some(basis.generation),
plan_id: Some(plan_id),
receipt_id: None,
fault: None,
})
}
WorkspaceCoverageAcquireResult::Current(receipt) => {
let authority = self.execution_authority.as_ref().ok_or_else(|| {
anyhow!("workspace coverage projector has no installed execution authority")
})?;
publish_workspace_coverage_terminal_sight(
&authority.local_verse_store,
&self.runtime_store,
&self.coverage_authority,
&authority.runtime_id,
&self.managed_process_launch_id,
&authority.trusted_host,
&authority.provider_signing_key,
chrono::Utc::now(),
)?;
Ok(WorkspaceCoverageProjectorServicePulse {
status: WorkspaceCoverageProjectorPulseStatus::Idle,
body_observation_id: Some(basis.observation_id),
body_generation: Some(basis.generation),
plan_id: Some(plan_id),
receipt_id: Some(receipt.receipt_id),
fault: None,
})
}
WorkspaceCoverageAcquireResult::Acquired(acquisition) => {
let authority = self.execution_authority.as_ref().ok_or_else(|| {
anyhow!("workspace coverage projector has no installed execution authority")
})?;
// Publish the exact provider-signed lease before the first backend
// operation. Recovery sight must exist even when the projector dies
// before it can mint a progress checkpoint.
publish_workspace_coverage_claim_sight(
&authority.local_verse_store,
&self.runtime_store,
&self.coverage_authority,
&acquisition,
&authority.runtime_id,
&self.managed_process_launch_id,
&authority.trusted_host,
&authority.provider_signing_key,
chrono::Utc::now(),
)?;
let receipt = execute_workspace_coverage_projection(
&acquisition,
&prepared,
&self.embedder,
&mut self.qdrant,
&WorkspaceCoverageProjectionExecutionAuthority {
coverage_authority: &self.coverage_authority,
runtime_store: &self.runtime_store,
local_verse_store: &authority.local_verse_store,
runtime_id: &authority.runtime_id,
trusted_host: &authority.trusted_host,
provider_signing_key: &authority.provider_signing_key,
operation_timeout_ms: self.operation_timeout_ms,
},
)?;
Ok(WorkspaceCoverageProjectorServicePulse {
status: WorkspaceCoverageProjectorPulseStatus::Executed,
body_observation_id: Some(basis.observation_id),
body_generation: Some(basis.generation),
plan_id: Some(plan_id),
receipt_id: Some(receipt.receipt_id),
fault: None,
})
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn config() -> WorkspaceCoverageProjectorConfig {
WorkspaceCoverageProjectorConfig {
qdrant_url: "http://127.0.0.1:6333".into(),
qdrant_api_key: None,
qdrant_timeout_ms: 1,
ollama_base_url: "http://127.0.0.1:11434".into(),
ollama_model: "test-model".into(),
ollama_timeout_ms: 1,
}
}
#[test]
fn config_refuses_empty_endpoints_and_zero_timeouts() {
let mut value = config();
value.ollama_model.clear();
assert!(value.validate().is_err());
let mut value = config();
value.qdrant_timeout_ms = 0;
assert!(value.validate().is_err());
}
#[test]
fn constructor_requires_lifecycle_identity_before_touching_runtime() {
assert!(
WorkspaceCoverageProjectorServiceBody::new(
"missing",
"runtime",
config(),
"",
"not-a-uuid"
)
.is_err()
);
assert!(
WorkspaceCoverageProjectorServiceBody::new(
"missing",
"runtime",
config(),
"incarnation",
"not-a-uuid",
)
.is_err()
);
}
#[test]
fn live_service_exclusively_owns_the_bound_coverage_store() -> Result<()> {
let state = tempfile::tempdir()?;
let repo = tempfile::tempdir()?;
let initialized = std::process::Command::new("git")
.arg("init")
.current_dir(repo.path())
.output()?;
if !initialized.status.success() {
bail!("singleton fixture failed to initialize repository");
}
let runtime = state.path().join("runtime.cc");
let body = state.path().join("body.cc");
let coverage = std::fs::canonicalize(state.path())?.join("workspace-coverage.cc");
crate::initialize_runtime_spine(
&runtime,
crate::RuntimeSpineInitOptions {
runtime_id: "singleton-runtime".into(),
display_name: "singleton test".into(),
created_at: "2026-07-17T00:00:00Z".into(),
},
)?;
crate::bind_runtime_to_swarm(&runtime, "singleton-swarm", "2026-07-17T00:00:01Z")?;
crate::bind_repository_body(repo.path(), &body, &runtime, "singleton-workspace")?;
crate::bind_runtime_workspace_coverage_store(&runtime, &coverage, "2026-07-17T00:00:02Z")?;
let launch_id = "019bff10-7426-7a1c-9ce4-33f91660a3a7";
let first = WorkspaceCoverageProjectorServiceBody::new(
&runtime,
"singleton-runtime",
config(),
"first-incarnation",
launch_id,
)?;
assert!(
WorkspaceCoverageProjectorServiceBody::new(
&runtime,
"singleton-runtime",
config(),
"second-incarnation",
launch_id,
)
.is_err(),
"a second live projector must not acquire the same owned store"
);
drop(first);
WorkspaceCoverageProjectorServiceBody::new(
&runtime,
"singleton-runtime",
config(),
"replacement-incarnation",
launch_id,
)?;
Ok(())
}
}

View file

@ -1,829 +0,0 @@
use crate::{
BODY_BINDING_KEY, BODY_BINDING_TYPE, RUNTIME_BODY_STORE_BINDING_KEY,
RUNTIME_BODY_STORE_BINDING_TYPE, RepositoryBodyBinding, RuntimeRepositoryBodyStoreBinding,
runtime_repository_body_store_binding,
};
use anyhow::{Context, Result, anyhow, bail};
use cultcache_rs::{
CacheBackingStore, CultCacheEnvelope, DatabaseEntry, OwnedRedbMessagePackBackingStore,
SingleFileMessagePackBackingStore,
};
use serde::{Serialize, de::DeserializeOwned};
use sha2::{Digest, Sha256};
use std::path::{Path, PathBuf};
pub const RUNTIME_WORKSPACE_COVERAGE_STORE_BINDING_TYPE: &str =
"epiphany.runtime.workspace_coverage_store_binding";
pub const RUNTIME_WORKSPACE_COVERAGE_STORE_BINDING_KEY: &str = "workspace-coverage-store";
pub const RUNTIME_WORKSPACE_COVERAGE_STORE_BINDING_SCHEMA_VERSION: &str =
"epiphany.runtime.workspace_coverage_store_binding.v0";
pub const WORKSPACE_COVERAGE_STORE_BINDING_TYPE: &str =
"gamecult.epiphany.workspace_coverage_store_binding";
pub const WORKSPACE_COVERAGE_STORE_BINDING_KEY: &str = "binding";
pub const WORKSPACE_COVERAGE_STORE_BINDING_SCHEMA_VERSION: &str =
"gamecult.epiphany.workspace_coverage_store_binding.v0";
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "epiphany.runtime.workspace_coverage_store_binding",
schema = "RuntimeWorkspaceCoverageStoreBinding"
)]
pub struct RuntimeWorkspaceCoverageStoreBinding {
#[cultcache(key = 0)]
pub schema_version: String,
#[cultcache(key = 1)]
pub binding_id: String,
#[cultcache(key = 2)]
pub runtime_id: String,
#[cultcache(key = 3)]
pub swarm_id: String,
#[cultcache(key = 4)]
pub workspace_id: String,
#[cultcache(key = 5)]
pub projection_store_path: String,
#[cultcache(key = 6)]
pub store_file_identity: String,
#[cultcache(key = 7)]
pub repository_body_store_binding_id: String,
#[cultcache(key = 8)]
pub repository_body_store_binding_envelope_sha256: String,
#[cultcache(key = 9)]
pub body_binding_sha256: String,
#[cultcache(key = 10)]
pub created_at_utc: String,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "gamecult.epiphany.workspace_coverage_store_binding",
schema = "WorkspaceCoverageStoreBinding"
)]
pub struct WorkspaceCoverageStoreBinding {
#[cultcache(key = 0)]
pub schema_version: String,
#[cultcache(key = 1)]
pub binding_id: String,
#[cultcache(key = 2)]
pub runtime_id: String,
#[cultcache(key = 3)]
pub swarm_id: String,
#[cultcache(key = 4)]
pub workspace_id: String,
#[cultcache(key = 5)]
pub store_file_identity: String,
#[cultcache(key = 6)]
pub body_binding_sha256: String,
#[cultcache(key = 7)]
pub repository_source_identity_sha256: String,
#[cultcache(key = 8)]
pub projection_scope: String,
#[cultcache(key = 9)]
pub storage_backend: String,
#[cultcache(key = 10)]
pub created_at_utc: String,
}
#[derive(Debug)]
pub struct WorkspaceCoverageAuthority {
pub store: OwnedRedbMessagePackBackingStore,
pub runtime_body_route: RuntimeRepositoryBodyStoreBinding,
pub runtime_body_route_envelope: CultCacheEnvelope,
pub runtime_body_route_envelope_sha256: String,
pub body_binding: RepositoryBodyBinding,
pub body_binding_envelope: CultCacheEnvelope,
pub body_binding_envelope_sha256: String,
pub runtime_coverage_route: RuntimeWorkspaceCoverageStoreBinding,
pub runtime_coverage_route_envelope: CultCacheEnvelope,
pub runtime_coverage_route_envelope_sha256: String,
pub store_binding: WorkspaceCoverageStoreBinding,
pub store_binding_envelope: CultCacheEnvelope,
pub store_binding_envelope_sha256: String,
}
pub fn bind_runtime_workspace_coverage_store(
runtime_store: impl AsRef<Path>,
projection_store: impl AsRef<Path>,
created_at_utc: &str,
) -> Result<RuntimeWorkspaceCoverageStoreBinding> {
let runtime_store = runtime_store.as_ref();
let projection_store = projection_store.as_ref();
require_timestamp(created_at_utc)?;
if let Some(existing) = raw_runtime_route(runtime_store)? {
require_same_path(projection_store, Path::new(&existing.projection_store_path))?;
return runtime_workspace_coverage_store_binding(runtime_store)?
.ok_or_else(|| anyhow!("runtime workspace coverage route disappeared during reload"));
}
let (body_route, body_route_envelope, body, _) = load_body_authority(runtime_store)?;
let canonical_projection_store = canonical_new_store_path(projection_store, &body)?;
let store = OwnedRedbMessagePackBackingStore::new(&canonical_projection_store)?;
require_canonical_existing_outside(store.path(), &body)?;
store.validate_path_identity()?;
let store_file_identity = store.file_identity().to_string();
let body_route_digest = envelope_sha256(&body_route_envelope)?;
let binding_id = workspace_coverage_binding_id(
&body_route.runtime_id,
&body_route.swarm_id,
&body_route.workspace_id,
&canonical_projection_store,
&store_file_identity,
&body_route_digest,
&body_route.body_binding_sha256,
)?;
let mut local_binding = WorkspaceCoverageStoreBinding {
schema_version: WORKSPACE_COVERAGE_STORE_BINDING_SCHEMA_VERSION.into(),
binding_id: binding_id.clone(),
runtime_id: body_route.runtime_id.clone(),
swarm_id: body_route.swarm_id.clone(),
workspace_id: body_route.workspace_id.clone(),
store_file_identity: store_file_identity.clone(),
body_binding_sha256: body_route.body_binding_sha256.clone(),
repository_source_identity_sha256: body.source_identity_sha256.clone(),
projection_scope: "workspace_coverage".into(),
storage_backend: "cultcache_redb_v0".into(),
created_at_utc: created_at_utc.to_string(),
};
let local_envelope = envelope(
WORKSPACE_COVERAGE_STORE_BINDING_TYPE,
WORKSPACE_COVERAGE_STORE_BINDING_KEY,
&local_binding,
created_at_utc,
)?;
if !store.insert_entry_if_absent(local_envelope.clone())? {
let entries = store.pull_all()?;
let existing = find(
&entries,
WORKSPACE_COVERAGE_STORE_BINDING_TYPE,
WORKSPACE_COVERAGE_STORE_BINDING_KEY,
)
.ok_or_else(|| anyhow!("workspace coverage store binding lost insert race"))?;
let existing: WorkspaceCoverageStoreBinding = decode(existing)?;
require_timestamp(&existing.created_at_utc)?;
local_binding.created_at_utc = existing.created_at_utc.clone();
if existing != local_binding {
bail!("workspace coverage store immutable binding collision");
}
local_binding = existing;
}
let timestamp = local_binding.created_at_utc.clone();
let runtime_binding = RuntimeWorkspaceCoverageStoreBinding {
schema_version: RUNTIME_WORKSPACE_COVERAGE_STORE_BINDING_SCHEMA_VERSION.into(),
binding_id,
runtime_id: body_route.runtime_id,
swarm_id: body_route.swarm_id,
workspace_id: body_route.workspace_id,
projection_store_path: canonical_projection_store.to_string_lossy().into_owned(),
store_file_identity,
repository_body_store_binding_id: body_route.binding_id,
repository_body_store_binding_envelope_sha256: body_route_digest,
body_binding_sha256: body_route.body_binding_sha256,
created_at_utc: timestamp.clone(),
};
let runtime_envelope = envelope(
RUNTIME_WORKSPACE_COVERAGE_STORE_BINDING_TYPE,
RUNTIME_WORKSPACE_COVERAGE_STORE_BINDING_KEY,
&runtime_binding,
&timestamp,
)?;
let runtime_backing = SingleFileMessagePackBackingStore::new(runtime_store);
if !runtime_backing.insert_entry_if_absent(runtime_envelope)? {
let existing = raw_runtime_route(runtime_store)?
.ok_or_else(|| anyhow!("runtime workspace coverage binding lost insert race"))?;
if existing != runtime_binding {
bail!("runtime workspace coverage-store immutable binding collision");
}
}
store.validate_path_identity()?;
let authority = assemble_workspace_coverage_authority(runtime_store, store)?;
let route = authority.runtime_coverage_route.clone();
drop(authority);
Ok(route)
}
pub fn runtime_workspace_coverage_store_binding(
runtime_store: impl AsRef<Path>,
) -> Result<Option<RuntimeWorkspaceCoverageStoreBinding>> {
let runtime_store = runtime_store.as_ref();
let Some(route) = raw_runtime_route(runtime_store)? else {
return Ok(None);
};
let (body_route, body_route_envelope, _, _) = load_body_authority(runtime_store)?;
validate_runtime_route(
&route,
&body_route,
&body_route_envelope,
&route.store_file_identity,
)?;
Ok(Some(route))
}
pub fn open_workspace_coverage_authority(
runtime_store: impl AsRef<Path>,
) -> Result<WorkspaceCoverageAuthority> {
let runtime_store = runtime_store.as_ref();
let runtime_route = raw_runtime_route(runtime_store)?
.ok_or_else(|| anyhow!("runtime has no workspace coverage-store binding"))?;
let path = PathBuf::from(&runtime_route.projection_store_path);
let store = OwnedRedbMessagePackBackingStore::new(&path)?;
assemble_workspace_coverage_authority(runtime_store, store)
}
fn assemble_workspace_coverage_authority(
runtime_store: &Path,
store: OwnedRedbMessagePackBackingStore,
) -> Result<WorkspaceCoverageAuthority> {
let (body_route, body_route_envelope, body, body_envelope) =
load_body_authority(runtime_store)?;
let runtime_entries = SingleFileMessagePackBackingStore::new(runtime_store).pull_all()?;
let runtime_envelope = find(
&runtime_entries,
RUNTIME_WORKSPACE_COVERAGE_STORE_BINDING_TYPE,
RUNTIME_WORKSPACE_COVERAGE_STORE_BINDING_KEY,
)
.cloned()
.ok_or_else(|| anyhow!("runtime has no workspace coverage-store binding"))?;
let runtime_route: RuntimeWorkspaceCoverageStoreBinding = decode(&runtime_envelope)?;
require_same_path(
store.path(),
Path::new(&runtime_route.projection_store_path),
)?;
require_canonical_existing_outside(store.path(), &body)?;
store.validate_path_identity()?;
validate_runtime_route(
&runtime_route,
&body_route,
&body_route_envelope,
store.file_identity(),
)?;
let local_entries = store.pull_all()?;
let local_envelope = find(
&local_entries,
WORKSPACE_COVERAGE_STORE_BINDING_TYPE,
WORKSPACE_COVERAGE_STORE_BINDING_KEY,
)
.cloned()
.ok_or_else(|| anyhow!("workspace coverage store has no immutable binding"))?;
let local_binding: WorkspaceCoverageStoreBinding = decode(&local_envelope)?;
validate_local_binding(&local_binding, &runtime_route, &body)?;
Ok(WorkspaceCoverageAuthority {
store,
runtime_body_route: body_route,
runtime_body_route_envelope_sha256: envelope_sha256(&body_route_envelope)?,
runtime_body_route_envelope: body_route_envelope,
body_binding: body,
body_binding_envelope_sha256: envelope_sha256(&body_envelope)?,
body_binding_envelope: body_envelope,
runtime_coverage_route: runtime_route,
runtime_coverage_route_envelope_sha256: envelope_sha256(&runtime_envelope)?,
runtime_coverage_route_envelope: runtime_envelope,
store_binding: local_binding,
store_binding_envelope_sha256: envelope_sha256(&local_envelope)?,
store_binding_envelope: local_envelope,
})
}
fn load_body_authority(
runtime_store: &Path,
) -> Result<(
RuntimeRepositoryBodyStoreBinding,
CultCacheEnvelope,
RepositoryBodyBinding,
CultCacheEnvelope,
)> {
let body_route = runtime_repository_body_store_binding(runtime_store)?
.ok_or_else(|| anyhow!("runtime has no repository Body-store binding"))?;
let runtime_entries = SingleFileMessagePackBackingStore::new(runtime_store).pull_all()?;
let route_envelope = find(
&runtime_entries,
RUNTIME_BODY_STORE_BINDING_TYPE,
RUNTIME_BODY_STORE_BINDING_KEY,
)
.cloned()
.ok_or_else(|| anyhow!("runtime repository Body route envelope is missing"))?;
let body_entries =
SingleFileMessagePackBackingStore::new(&body_route.body_store_path).pull_all()?;
let body_envelope = find(&body_entries, BODY_BINDING_TYPE, BODY_BINDING_KEY)
.cloned()
.ok_or_else(|| anyhow!("repository Body binding envelope is missing"))?;
let body: RepositoryBodyBinding = decode(&body_envelope)?;
Ok((body_route, route_envelope, body, body_envelope))
}
fn validate_runtime_route(
route: &RuntimeWorkspaceCoverageStoreBinding,
body_route: &RuntimeRepositoryBodyStoreBinding,
body_route_envelope: &CultCacheEnvelope,
owned_store_file_identity: &str,
) -> Result<()> {
require_timestamp(&route.created_at_utc)?;
let path = PathBuf::from(&route.projection_store_path);
let expected_binding_id = workspace_coverage_binding_id(
&body_route.runtime_id,
&body_route.swarm_id,
&body_route.workspace_id,
&path,
owned_store_file_identity,
&envelope_sha256(body_route_envelope)?,
&body_route.body_binding_sha256,
)?;
if route.schema_version != RUNTIME_WORKSPACE_COVERAGE_STORE_BINDING_SCHEMA_VERSION
|| route.binding_id != expected_binding_id
|| !path.is_absolute()
|| route.store_file_identity.trim().is_empty()
|| route.runtime_id != body_route.runtime_id
|| route.swarm_id != body_route.swarm_id
|| route.workspace_id != body_route.workspace_id
|| route.store_file_identity != owned_store_file_identity
|| route.repository_body_store_binding_id != body_route.binding_id
|| route.repository_body_store_binding_envelope_sha256
!= envelope_sha256(body_route_envelope)?
|| route.body_binding_sha256 != body_route.body_binding_sha256
{
bail!("runtime workspace coverage-store binding disagrees with repository Body authority");
}
Ok(())
}
fn validate_local_binding(
local: &WorkspaceCoverageStoreBinding,
route: &RuntimeWorkspaceCoverageStoreBinding,
body: &RepositoryBodyBinding,
) -> Result<()> {
require_timestamp(&local.created_at_utc)?;
if local.schema_version != WORKSPACE_COVERAGE_STORE_BINDING_SCHEMA_VERSION
|| local.binding_id != route.binding_id
|| local.runtime_id != route.runtime_id
|| local.swarm_id != route.swarm_id
|| local.workspace_id != route.workspace_id
|| local.store_file_identity != route.store_file_identity
|| local.body_binding_sha256 != route.body_binding_sha256
|| local.repository_source_identity_sha256 != body.source_identity_sha256
|| local.projection_scope != "workspace_coverage"
|| local.storage_backend != "cultcache_redb_v0"
|| local.created_at_utc != route.created_at_utc
{
bail!("workspace coverage store binding disagrees with runtime route");
}
Ok(())
}
fn raw_runtime_route(runtime_store: &Path) -> Result<Option<RuntimeWorkspaceCoverageStoreBinding>> {
if !runtime_store.exists() {
return Ok(None);
}
let entries = SingleFileMessagePackBackingStore::new(runtime_store).pull_all()?;
find(
&entries,
RUNTIME_WORKSPACE_COVERAGE_STORE_BINDING_TYPE,
RUNTIME_WORKSPACE_COVERAGE_STORE_BINDING_KEY,
)
.map(decode)
.transpose()
}
fn canonical_new_store_path(path: &Path, body: &RepositoryBodyBinding) -> Result<PathBuf> {
if !path.is_absolute() {
bail!("workspace coverage store path must be absolute");
}
if path.exists() {
return canonical_existing_outside(path, body);
}
let parent = path
.parent()
.ok_or_else(|| anyhow!("workspace coverage store path has no parent"))?;
let canonical_parent =
std::fs::canonicalize(parent).context("workspace coverage store parent is missing")?;
let file_name = path
.file_name()
.ok_or_else(|| anyhow!("workspace coverage store path has no filename"))?;
let canonical = canonical_parent.join(file_name);
require_outside_worktree(&canonical, body)?;
Ok(canonical)
}
fn require_canonical_existing_outside(path: &Path, body: &RepositoryBodyBinding) -> Result<()> {
canonical_existing_outside(path, body).map(|_| ())
}
fn canonical_existing_outside(path: &Path, body: &RepositoryBodyBinding) -> Result<PathBuf> {
let canonical =
std::fs::canonicalize(path).context("bound workspace coverage store is missing")?;
require_outside_worktree(&canonical, body)?;
Ok(canonical)
}
fn require_outside_worktree(path: &Path, body: &RepositoryBodyBinding) -> Result<()> {
let worktree = std::fs::canonicalize(&body.git_top_level)
.context("repository Body worktree is missing")?;
if path.starts_with(&worktree) {
bail!("workspace coverage store must be outside the repository worktree");
}
Ok(())
}
fn require_same_path(requested: &Path, bound: &Path) -> Result<()> {
let requested = std::fs::canonicalize(requested)
.context("requested workspace coverage store is missing")?;
let bound =
std::fs::canonicalize(bound).context("bound workspace coverage store is missing")?;
if requested != bound {
bail!("runtime workspace coverage-store immutable path collision");
}
Ok(())
}
fn require_timestamp(timestamp: &str) -> Result<()> {
chrono::DateTime::parse_from_rfc3339(timestamp)
.map_err(|_| anyhow!("workspace coverage store binding timestamp must be RFC3339"))?;
Ok(())
}
fn find<'a>(
entries: &'a [CultCacheEnvelope],
ty: &str,
key: &str,
) -> Option<&'a CultCacheEnvelope> {
entries
.iter()
.find(|entry| entry.r#type == ty && entry.key == key)
}
fn decode<T: DeserializeOwned>(envelope: &CultCacheEnvelope) -> Result<T> {
rmp_serde::from_slice(&envelope.payload).map_err(Into::into)
}
fn envelope<T: Serialize>(
ty: &str,
key: &str,
value: &T,
stored_at: &str,
) -> Result<CultCacheEnvelope> {
Ok(CultCacheEnvelope {
key: key.into(),
r#type: ty.into(),
payload: rmp_serde::to_vec(value)?,
stored_at: stored_at.into(),
schema_id: Some(ty.into()),
})
}
fn envelope_sha256(envelope: &CultCacheEnvelope) -> Result<String> {
Ok(format!(
"{:x}",
Sha256::digest(rmp_serde::to_vec_named(envelope)?)
))
}
fn workspace_coverage_binding_id(
runtime_id: &str,
swarm_id: &str,
workspace_id: &str,
canonical_projection_store: &Path,
store_file_identity: &str,
body_route_envelope_sha256: &str,
body_binding_sha256: &str,
) -> Result<String> {
let digest = Sha256::digest(rmp_serde::to_vec_named(&(
"gamecult.epiphany.workspace_coverage_store_binding_id.v0",
runtime_id,
swarm_id,
workspace_id,
canonical_projection_store.to_string_lossy().as_ref(),
store_file_identity,
body_route_envelope_sha256,
body_binding_sha256,
))?);
Ok(format!("workspace-coverage:{digest:x}"))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
BODY_SCHEMA_VERSION, EpiphanyMindIdentity, EpiphanyRuntimeIdentity,
EpiphanyRuntimeSwarmBinding, MIND_SCHEMA_EPOCH, RUNTIME_IDENTITY_KEY,
RUNTIME_IDENTITY_TYPE, RUNTIME_SPINE_SCHEMA_VERSION, RUNTIME_SWARM_BINDING_KEY,
RUNTIME_SWARM_BINDING_SCHEMA_VERSION,
};
use std::collections::BTreeMap;
const NOW: &str = "2026-07-17T08:00:00Z";
struct Fixture {
_temp: tempfile::TempDir,
runtime_store: PathBuf,
worktree: PathBuf,
projection_store: PathBuf,
}
fn fixture() -> Result<Fixture> {
let temp = tempfile::tempdir()?;
let worktree = temp.path().join("repo");
let state = temp.path().join("state");
std::fs::create_dir_all(&worktree)?;
std::fs::create_dir_all(&state)?;
let worktree = std::fs::canonicalize(worktree)?;
let state = std::fs::canonicalize(state)?;
let runtime_store = state.join("runtime.cc");
let body_store = state.join("body.cc");
let projection_store = state.join("workspace-coverage.cc");
let body = RepositoryBodyBinding {
schema_version: BODY_SCHEMA_VERSION.into(),
workspace_id: "workspace-1".into(),
swarm_id: "swarm-1".into(),
runtime_id: "runtime-1".into(),
scope: "git_worktree".into(),
source_identity_type: crate::RUNTIME_IDENTITY_TYPE.into(),
source_identity_key: crate::RUNTIME_IDENTITY_KEY.into(),
source_identity_sha256: "a".repeat(64),
git_top_level: worktree.to_string_lossy().into_owned(),
object_format: "sha1".into(),
global_excludes_policy: "disabled_for_observation".into(),
};
let body_digest = format!("{:x}", Sha256::digest(rmp_serde::to_vec_named(&body)?));
let mut body_backing = SingleFileMessagePackBackingStore::new(&body_store);
body_backing.push(&envelope(BODY_BINDING_TYPE, BODY_BINDING_KEY, &body, NOW)?)?;
let body_store = std::fs::canonicalize(body_store)?;
let identity = EpiphanyRuntimeIdentity {
schema_version: RUNTIME_SPINE_SCHEMA_VERSION.into(),
runtime_id: "runtime-1".into(),
display_name: "test".into(),
runtime_kind: "epiphany.native".into(),
created_at: NOW.into(),
updated_at: NOW.into(),
supported_document_types: Vec::new(),
metadata: BTreeMap::new(),
};
let swarm = EpiphanyRuntimeSwarmBinding {
schema_version: RUNTIME_SWARM_BINDING_SCHEMA_VERSION.into(),
binding_id: RUNTIME_SWARM_BINDING_KEY.into(),
runtime_id: "runtime-1".into(),
swarm_id: "swarm-1".into(),
source_identity_type: body.source_identity_type.clone(),
source_identity_key: body.source_identity_key.clone(),
source_identity_sha256: body.source_identity_sha256.clone(),
bound_at: NOW.into(),
};
let route = RuntimeRepositoryBodyStoreBinding {
schema_version: BODY_SCHEMA_VERSION.into(),
binding_id: RUNTIME_BODY_STORE_BINDING_KEY.into(),
runtime_id: "runtime-1".into(),
swarm_id: "swarm-1".into(),
workspace_id: "workspace-1".into(),
body_store_path: body_store.to_string_lossy().into_owned(),
body_binding_sha256: body_digest,
};
let mut runtime_backing = SingleFileMessagePackBackingStore::new(&runtime_store);
runtime_backing.push_all(
&[
envelope(RUNTIME_IDENTITY_TYPE, RUNTIME_IDENTITY_KEY, &identity, NOW)?,
envelope(
EpiphanyMindIdentity::TYPE,
MIND_SCHEMA_EPOCH,
&EpiphanyMindIdentity {
schema_epoch: MIND_SCHEMA_EPOCH.into(),
runtime_id: identity.runtime_id.clone(),
},
NOW,
)?,
envelope(
EpiphanyRuntimeSwarmBinding::TYPE,
RUNTIME_SWARM_BINDING_KEY,
&swarm,
NOW,
)?,
envelope(
RUNTIME_BODY_STORE_BINDING_TYPE,
RUNTIME_BODY_STORE_BINDING_KEY,
&route,
NOW,
)?,
],
Default::default(),
)?;
Ok(Fixture {
_temp: temp,
runtime_store,
worktree,
projection_store,
})
}
#[test]
fn bind_once_and_reopen_return_the_same_validated_authority() -> Result<()> {
let fixture = fixture()?;
let bound = bind_runtime_workspace_coverage_store(
&fixture.runtime_store,
&fixture.projection_store,
NOW,
)?;
let reopened = open_workspace_coverage_authority(&fixture.runtime_store)?;
assert_eq!(reopened.runtime_coverage_route, bound);
assert_eq!(reopened.store_binding.created_at_utc, bound.created_at_utc);
assert_eq!(reopened.runtime_body_route.workspace_id, "workspace-1");
assert_eq!(reopened.body_binding.workspace_id, "workspace-1");
assert_eq!(reopened.store.pull_all()?.len(), 1);
drop(reopened);
let rebound = bind_runtime_workspace_coverage_store(
&fixture.runtime_store,
&fixture.projection_store,
"2026-07-17T09:00:00Z",
)?;
assert_eq!(rebound, bound);
Ok(())
}
#[cfg(windows)]
#[test]
fn ordinary_windows_spelling_binds_and_rebinds_the_canonical_store() -> Result<()> {
let fixture = fixture()?;
let canonical = fixture.projection_store.clone();
let ordinary = PathBuf::from(
canonical
.to_string_lossy()
.strip_prefix(r"\\?\")
.expect("Windows canonical test path should use extended-length spelling"),
);
let bound = bind_runtime_workspace_coverage_store(&fixture.runtime_store, &ordinary, NOW)?;
assert_eq!(PathBuf::from(&bound.projection_store_path), canonical);
assert_eq!(
bind_runtime_workspace_coverage_store(
&fixture.runtime_store,
&ordinary,
"2026-07-17T09:00:00Z",
)?,
bound
);
assert_eq!(
open_workspace_coverage_authority(&fixture.runtime_store)?.runtime_coverage_route,
bound
);
Ok(())
}
#[test]
fn interrupted_first_bind_retry_adopts_valid_local_timestamp_and_binding_id() -> Result<()> {
let fixture = fixture()?;
let first = bind_runtime_workspace_coverage_store(
&fixture.runtime_store,
&fixture.projection_store,
NOW,
)?;
let mut runtime = SingleFileMessagePackBackingStore::new(&fixture.runtime_store);
let route_envelope = runtime
.pull_all()?
.into_iter()
.find(|entry| {
entry.r#type == RUNTIME_WORKSPACE_COVERAGE_STORE_BINDING_TYPE
&& entry.key == RUNTIME_WORKSPACE_COVERAGE_STORE_BINDING_KEY
})
.unwrap();
runtime.delete(&route_envelope)?;
let recovered = bind_runtime_workspace_coverage_store(
&fixture.runtime_store,
&fixture.projection_store,
"2026-07-17T09:00:00Z",
)?;
assert_eq!(recovered.binding_id, first.binding_id);
assert_eq!(recovered.created_at_utc, NOW);
assert_ne!(
recovered.binding_id,
RUNTIME_WORKSPACE_COVERAGE_STORE_BINDING_KEY
);
assert_ne!(recovered.binding_id, WORKSPACE_COVERAGE_STORE_BINDING_KEY);
Ok(())
}
#[test]
fn live_authority_does_not_block_route_lookup_or_idempotent_bind() -> Result<()> {
let fixture = fixture()?;
let bound = bind_runtime_workspace_coverage_store(
&fixture.runtime_store,
&fixture.projection_store,
NOW,
)?;
let authority = open_workspace_coverage_authority(&fixture.runtime_store)?;
assert_eq!(
runtime_workspace_coverage_store_binding(&fixture.runtime_store)?,
Some(bound.clone())
);
assert_eq!(
bind_runtime_workspace_coverage_store(
&fixture.runtime_store,
&fixture.projection_store,
"2026-07-17T09:00:00Z",
)?,
bound
);
assert!(open_workspace_coverage_authority(&fixture.runtime_store).is_err());
authority.store.validate_path_identity()?;
Ok(())
}
#[test]
fn bind_once_refuses_destination_substitution_and_inside_worktree_path() -> Result<()> {
let first = fixture()?;
bind_runtime_workspace_coverage_store(&first.runtime_store, &first.projection_store, NOW)?;
let alternate = first.projection_store.with_file_name("alternate.cc");
assert!(
bind_runtime_workspace_coverage_store(&first.runtime_store, alternate, NOW).is_err()
);
let second = fixture()?;
let inside = second.worktree.join("coverage.cc");
assert!(bind_runtime_workspace_coverage_store(&second.runtime_store, inside, NOW).is_err());
Ok(())
}
#[test]
fn open_refuses_exact_body_route_envelope_substitution() -> Result<()> {
let fixture = fixture()?;
bind_runtime_workspace_coverage_store(
&fixture.runtime_store,
&fixture.projection_store,
NOW,
)?;
let mut runtime = SingleFileMessagePackBackingStore::new(&fixture.runtime_store);
let route = runtime
.pull_all()?
.into_iter()
.find(|entry| {
entry.r#type == RUNTIME_BODY_STORE_BINDING_TYPE
&& entry.key == RUNTIME_BODY_STORE_BINDING_KEY
})
.unwrap();
let mut substituted = route.clone();
substituted.stored_at = "2026-07-17T08:00:01Z".into();
runtime.push(&substituted)?;
assert!(open_workspace_coverage_authority(&fixture.runtime_store).is_err());
Ok(())
}
#[test]
fn open_refuses_store_local_binding_mismatch() -> Result<()> {
let fixture = fixture()?;
bind_runtime_workspace_coverage_store(
&fixture.runtime_store,
&fixture.projection_store,
NOW,
)?;
let mut store = OwnedRedbMessagePackBackingStore::new(&fixture.projection_store)?;
let current = store.pull_all()?.into_iter().next().unwrap();
let mut binding: WorkspaceCoverageStoreBinding = decode(&current)?;
binding.workspace_id = "substituted".into();
store.push(&envelope(
WORKSPACE_COVERAGE_STORE_BINDING_TYPE,
WORKSPACE_COVERAGE_STORE_BINDING_KEY,
&binding,
NOW,
)?)?;
drop(store);
assert_eq!(
open_workspace_coverage_authority(&fixture.runtime_store)
.unwrap_err()
.to_string(),
"workspace coverage store binding disagrees with runtime route"
);
Ok(())
}
#[test]
fn opened_authority_stays_on_pinned_file_and_reopen_refuses_copied_replacement() -> Result<()> {
let fixture = fixture()?;
bind_runtime_workspace_coverage_store(
&fixture.runtime_store,
&fixture.projection_store,
NOW,
)?;
let copied = fixture
.projection_store
.with_file_name("workspace-coverage-copy.cc");
std::fs::copy(&fixture.projection_store, &copied)?;
let mut authority = open_workspace_coverage_authority(&fixture.runtime_store)?;
let original_identity = authority.store.file_identity().to_string();
let displaced = fixture
.projection_store
.with_file_name("workspace-coverage-displaced.cc");
std::fs::rename(&fixture.projection_store, &displaced)?;
std::fs::rename(&copied, &fixture.projection_store)?;
assert!(authority.store.validate_path_identity().is_err());
assert_eq!(authority.store.file_identity(), original_identity);
let pinned_entry = envelope("test.pinned", "after-replacement", &"still-original", NOW)?;
authority.store.push(&pinned_entry)?;
assert!(
authority
.store
.pull_all()?
.iter()
.any(|entry| entry.r#type == "test.pinned")
);
drop(authority);
assert!(open_workspace_coverage_authority(&fixture.runtime_store).is_err());
Ok(())
}
}

View file

@ -1,952 +0,0 @@
use crate::{
RepositoryBodyManifest, RepositoryBodyManifestEntry, RepositoryBodyObservationBasis,
authenticated_repository_body_manifest,
};
use anyhow::{Result, anyhow, bail};
use cultcache_rs::DatabaseEntry;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::path::Path;
pub const WORKSPACE_COVERAGE_POLICY_SCHEMA_VERSION: &str =
"gamecult.epiphany.workspace_coverage_policy.v0";
pub const WORKSPACE_COVERAGE_OBLIGATION_SCHEMA_VERSION: &str =
"gamecult.epiphany.workspace_coverage_obligation.v0";
pub const WORKSPACE_COVERAGE_RECEIPT_SCHEMA_VERSION: &str =
"gamecult.epiphany.workspace_coverage_receipt.v0";
pub const WORKSPACE_COVERAGE_PROJECTION_PLAN_SCHEMA_VERSION: &str =
"gamecult.epiphany.workspace_coverage_projection_plan.v0";
pub const WORKSPACE_COVERAGE_HEAD_SCHEMA_VERSION: &str =
"gamecult.epiphany.workspace_coverage_head.v0";
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WorkspaceCoverageDisposition {
IncludeRegularFile,
ExcludeGitlink,
ExcludeSymlink,
ExcludeUnsupportedKind,
ExcludeEmptyRegularFile,
ExcludeNonUtf8RegularFile,
ExcludeOversize,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorkspaceCoveragePolicy {
pub schema_version: String,
pub policy_id: String,
pub maximum_file_bytes: u64,
}
impl WorkspaceCoveragePolicy {
pub fn bounded_regular_files_v0(maximum_file_bytes: u64) -> Result<Self> {
if maximum_file_bytes == 0 {
bail!("workspace coverage policy maximum_file_bytes must be positive");
}
Ok(Self {
schema_version: WORKSPACE_COVERAGE_POLICY_SCHEMA_VERSION.into(),
policy_id: "bounded_regular_files_v0".into(),
maximum_file_bytes,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorkspaceCoverageClassification {
pub path: String,
pub raw_sha256: String,
pub raw_byte_length: u64,
pub disposition: WorkspaceCoverageDisposition,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "gamecult.epiphany.workspace_coverage_obligation",
schema = "WorkspaceCoverageObligation"
)]
pub(crate) struct WorkspaceCoverageObligation {
#[cultcache(key = 0)]
pub schema_version: String,
#[cultcache(key = 1)]
pub obligation_id: String,
#[cultcache(key = 2)]
pub workspace_id: String,
#[cultcache(key = 3)]
pub swarm_id: String,
#[cultcache(key = 4)]
pub runtime_id: String,
#[cultcache(key = 5)]
pub body_binding_sha256: String,
#[cultcache(key = 6)]
pub body_observation_id: String,
#[cultcache(key = 7)]
pub body_generation: u64,
#[cultcache(key = 8)]
pub manifest_root_sha256: String,
#[cultcache(key = 9)]
pub policy_id: String,
#[cultcache(key = 10)]
pub policy_sha256: String,
#[cultcache(key = 11)]
pub classification_set_sha256: String,
#[cultcache(key = 12)]
pub classifications: Vec<WorkspaceCoverageClassification>,
#[cultcache(key = 13)]
pub included_entry_count: u64,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkspaceCoverageChunkDescriptor {
pub body_path: String,
pub source_raw_sha256: String,
pub source_raw_byte_length: u64,
pub chunk_index: u32,
pub byte_start: u64,
pub byte_end: u64,
pub chunk_sha256: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkspaceCoveragePlannedPoint {
pub point_id: String,
pub body_path: String,
pub source_raw_sha256: String,
pub source_raw_byte_length: u64,
pub chunk_index: u32,
pub byte_start: u64,
pub byte_end: u64,
pub chunk_sha256: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkspaceCoveragePointPayload {
pub obligation_id: String,
pub projection_schema_version: String,
pub chunker_id: String,
pub body_path: String,
pub source_raw_sha256: String,
pub source_raw_byte_length: u64,
pub chunk_index: u32,
pub byte_start: u64,
pub byte_end: u64,
pub chunk_sha256: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkspaceCoveragePointBinding {
pub point_id: String,
pub payload_sha256: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkspaceCoverageVectorBinding {
pub point_id: String,
pub vector_sha256: String,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "gamecult.epiphany.workspace_coverage_projection_plan",
schema = "WorkspaceCoverageProjectionPlan"
)]
pub(crate) struct WorkspaceCoverageProjectionPlan {
#[cultcache(key = 0)]
pub schema_version: String,
#[cultcache(key = 1)]
pub plan_id: String,
#[cultcache(key = 2)]
pub obligation_id: String,
#[cultcache(key = 3)]
pub projection_schema_version: String,
#[cultcache(key = 4)]
pub chunker_id: String,
#[cultcache(key = 5)]
pub embedding_provider_id: String,
#[cultcache(key = 6)]
pub embedding_model: String,
#[cultcache(key = 7)]
pub vector_dimensions: u32,
#[cultcache(key = 8)]
pub planned_points: Vec<WorkspaceCoveragePlannedPoint>,
#[cultcache(key = 9)]
pub expected_point_count: u64,
#[cultcache(key = 10)]
pub expected_point_set_sha256: String,
#[cultcache(key = 11)]
pub point_bindings: Vec<WorkspaceCoveragePointBinding>,
#[cultcache(key = 12)]
pub point_binding_set_sha256: String,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "gamecult.epiphany.workspace_coverage_receipt",
schema = "WorkspaceCoverageReceipt"
)]
pub(crate) struct WorkspaceCoverageReceipt {
#[cultcache(key = 0)]
pub schema_version: String,
#[cultcache(key = 1)]
pub receipt_id: String,
#[cultcache(key = 2)]
pub obligation_id: String,
#[cultcache(key = 3)]
pub plan_id: String,
#[cultcache(key = 4)]
pub collection_name: String,
#[cultcache(key = 5)]
pub embedding_provider_id: String,
#[cultcache(key = 6)]
pub embedding_model: String,
#[cultcache(key = 7)]
pub vector_dimensions: u32,
#[cultcache(key = 8)]
pub observed_point_count: u64,
#[cultcache(key = 9)]
pub observed_point_set_sha256: String,
#[cultcache(key = 10)]
pub observed_at: String,
#[cultcache(key = 11)]
pub observation_method: String,
#[cultcache(key = 12)]
pub observed_point_binding_set_sha256: String,
#[cultcache(key = 13)]
pub claim_id: String,
#[cultcache(key = 14)]
pub claim_epoch: u64,
#[cultcache(key = 15)]
pub observed_vector_binding_set_sha256: String,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "gamecult.epiphany.workspace_coverage_head",
schema = "WorkspaceCoverageHead"
)]
pub(crate) struct WorkspaceCoverageHead {
#[cultcache(key = 0)]
pub schema_version: String,
#[cultcache(key = 1)]
pub workspace_id: String,
#[cultcache(key = 2)]
pub obligation_id: String,
#[cultcache(key = 3)]
pub plan_id: String,
#[cultcache(key = 4)]
pub receipt_id: String,
#[cultcache(key = 5)]
pub body_observation_id: String,
#[cultcache(key = 6)]
pub body_generation: u64,
#[cultcache(key = 7)]
pub manifest_root_sha256: String,
#[cultcache(key = 8)]
pub claim_id: String,
#[cultcache(key = 9)]
pub claim_epoch: u64,
}
pub(crate) fn derive_workspace_coverage_obligation(
runtime_store: &Path,
basis: &RepositoryBodyObservationBasis,
policy: &WorkspaceCoveragePolicy,
) -> Result<WorkspaceCoverageObligation> {
let manifest = authenticated_repository_body_manifest(runtime_store, basis)?;
derive_workspace_coverage_obligation_from_authenticated_manifest(basis, &manifest, policy)
}
pub(crate) fn derive_workspace_coverage_obligation_from_authenticated_manifest(
basis: &RepositoryBodyObservationBasis,
manifest: &RepositoryBodyManifest,
policy: &WorkspaceCoveragePolicy,
) -> Result<WorkspaceCoverageObligation> {
if policy.schema_version != WORKSPACE_COVERAGE_POLICY_SCHEMA_VERSION
|| policy.policy_id.trim().is_empty()
|| policy.maximum_file_bytes == 0
{
bail!("unsupported or invalid workspace coverage policy");
}
if manifest.workspace_id != basis.workspace_id
|| manifest.scope != basis.scope
|| manifest.manifest_root_sha256 != basis.manifest_root_sha256
|| basis.generation == 0
{
bail!("workspace coverage input manifest disagrees with authenticated Body basis");
}
let mut prior_path: Option<&str> = None;
let mut classifications = Vec::with_capacity(manifest.entries.len());
for entry in &manifest.entries {
if entry.path.trim().is_empty()
|| (entry.raw_sha256.trim().is_empty() && entry.kind != "gitlink_nonrecursive")
|| prior_path.is_some_and(|prior| prior >= entry.path.as_str())
{
bail!("Body manifest entries must have unique, strictly ordered paths and hashes");
}
prior_path = Some(&entry.path);
classifications.push(classify(entry, policy));
}
let policy_sha256 = digest(policy)?;
let classification_set_sha256 = digest(&classifications)?;
let included_entry_count = classifications
.iter()
.filter(|entry| entry.disposition == WorkspaceCoverageDisposition::IncludeRegularFile)
.count() as u64;
let obligation_id = format!(
"workspace-coverage:{}",
digest(&(
&basis.body_binding_sha256,
&basis.observation_id,
&basis.manifest_root_sha256,
&policy_sha256,
&classification_set_sha256,
))?
);
Ok(WorkspaceCoverageObligation {
schema_version: WORKSPACE_COVERAGE_OBLIGATION_SCHEMA_VERSION.into(),
obligation_id,
workspace_id: basis.workspace_id.clone(),
swarm_id: basis.swarm_id.clone(),
runtime_id: basis.runtime_id.clone(),
body_binding_sha256: basis.body_binding_sha256.clone(),
body_observation_id: basis.observation_id.clone(),
body_generation: basis.generation,
manifest_root_sha256: basis.manifest_root_sha256.clone(),
policy_id: policy.policy_id.clone(),
policy_sha256,
classification_set_sha256,
classifications,
included_entry_count,
})
}
pub(crate) fn refine_workspace_coverage_obligation_utf8(
obligation: &WorkspaceCoverageObligation,
non_utf8_paths: &[String],
) -> Result<WorkspaceCoverageObligation> {
if !non_utf8_paths.windows(2).all(|pair| pair[0] < pair[1]) {
bail!("non-UTF-8 Body paths must be unique and canonically ordered");
}
let mut refined = obligation.clone();
for path in non_utf8_paths {
let classification = refined
.classifications
.iter_mut()
.find(|entry| &entry.path == path)
.ok_or_else(|| anyhow!("non-UTF-8 path is absent from Body obligation"))?;
if classification.disposition != WorkspaceCoverageDisposition::IncludeRegularFile {
bail!("non-UTF-8 refinement may only demote an included regular file");
}
classification.disposition = WorkspaceCoverageDisposition::ExcludeNonUtf8RegularFile;
}
refined.included_entry_count = refined
.classifications
.iter()
.filter(|entry| entry.disposition == WorkspaceCoverageDisposition::IncludeRegularFile)
.count() as u64;
refined.classification_set_sha256 = digest(&refined.classifications)?;
refined.obligation_id = format!(
"workspace-coverage:{}",
digest(&(
&refined.body_binding_sha256,
&refined.body_observation_id,
&refined.manifest_root_sha256,
&refined.policy_sha256,
&refined.classification_set_sha256,
))?
);
Ok(refined)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn derive_workspace_coverage_projection_plan(
obligation: &WorkspaceCoverageObligation,
projection_schema_version: &str,
chunker_id: &str,
embedding_provider_id: &str,
embedding_model: &str,
vector_dimensions: u32,
chunk_descriptors: Vec<WorkspaceCoverageChunkDescriptor>,
) -> Result<WorkspaceCoverageProjectionPlan> {
if obligation.schema_version != WORKSPACE_COVERAGE_OBLIGATION_SCHEMA_VERSION
|| projection_schema_version.trim().is_empty()
|| chunker_id.trim().is_empty()
|| embedding_provider_id.trim().is_empty()
|| embedding_model.trim().is_empty()
|| vector_dimensions == 0
|| (obligation.included_entry_count == 0) != chunk_descriptors.is_empty()
{
bail!("workspace coverage projection plan is incomplete or noncanonical");
}
let included = obligation
.classifications
.iter()
.filter(|entry| entry.disposition == WorkspaceCoverageDisposition::IncludeRegularFile)
.map(|entry| (entry.path.as_str(), entry))
.collect::<std::collections::HashMap<_, _>>();
let mut planned_points = Vec::with_capacity(chunk_descriptors.len());
let mut prior_descriptor: Option<(String, u32)> = None;
for descriptor in chunk_descriptors {
let Some(source) = included.get(descriptor.body_path.as_str()) else {
bail!("workspace coverage chunk does not name an included Body entry");
};
let valid_range = descriptor.byte_start < descriptor.byte_end
&& descriptor.byte_end <= descriptor.source_raw_byte_length;
if descriptor.source_raw_sha256 != source.raw_sha256
|| descriptor.source_raw_byte_length != source.raw_byte_length
|| !sha256_hex(&descriptor.source_raw_sha256)
|| !sha256_hex(&descriptor.chunk_sha256)
|| !valid_range
|| (prior_descriptor.is_none() && descriptor.chunk_index != 0)
|| prior_descriptor.as_ref().is_some_and(|prior| {
if prior.0 == descriptor.body_path {
prior.1.checked_add(1) != Some(descriptor.chunk_index)
} else {
descriptor.body_path <= prior.0 || descriptor.chunk_index != 0
}
})
{
bail!("workspace coverage chunk descriptor disagrees with Body or canonical order");
}
prior_descriptor = Some((descriptor.body_path.clone(), descriptor.chunk_index));
let point_id = uuid::Uuid::new_v5(
&uuid::Uuid::NAMESPACE_URL,
rmp_serde::to_vec_named(&(
&obligation.obligation_id,
projection_schema_version,
chunker_id,
&descriptor,
))?
.as_slice(),
)
.to_string();
planned_points.push(WorkspaceCoveragePlannedPoint {
point_id,
body_path: descriptor.body_path,
source_raw_sha256: descriptor.source_raw_sha256,
source_raw_byte_length: descriptor.source_raw_byte_length,
chunk_index: descriptor.chunk_index,
byte_start: descriptor.byte_start,
byte_end: descriptor.byte_end,
chunk_sha256: descriptor.chunk_sha256,
});
}
if included.values().any(|entry| {
!planned_points
.iter()
.any(|point| point.body_path == entry.path)
}) {
bail!("workspace coverage plan omits an included Body entry");
}
let mut point_ids = planned_points
.iter()
.map(|point| point.point_id.clone())
.collect::<Vec<_>>();
point_ids.sort();
let mut point_bindings = planned_points
.iter()
.map(|point| {
let payload = point_payload(obligation, projection_schema_version, chunker_id, point);
Ok(WorkspaceCoveragePointBinding {
point_id: point.point_id.clone(),
payload_sha256: digest(&payload)?,
})
})
.collect::<Result<Vec<_>>>()?;
point_bindings.sort_by(|left, right| left.point_id.cmp(&right.point_id));
let expected_point_count = point_ids.len() as u64;
let expected_point_set_sha256 = digest(&point_ids)?;
let point_binding_set_sha256 = digest(&point_bindings)?;
let identity = (
&obligation.obligation_id,
projection_schema_version,
chunker_id,
embedding_provider_id,
embedding_model,
vector_dimensions,
&planned_points,
&expected_point_set_sha256,
&point_bindings,
&point_binding_set_sha256,
);
let plan_id = format!("workspace-coverage-plan:{}", digest(&identity)?);
Ok(WorkspaceCoverageProjectionPlan {
schema_version: WORKSPACE_COVERAGE_PROJECTION_PLAN_SCHEMA_VERSION.into(),
plan_id,
obligation_id: obligation.obligation_id.clone(),
projection_schema_version: projection_schema_version.into(),
chunker_id: chunker_id.into(),
embedding_provider_id: embedding_provider_id.into(),
embedding_model: embedding_model.into(),
vector_dimensions,
planned_points,
expected_point_count,
expected_point_set_sha256,
point_bindings,
point_binding_set_sha256,
})
}
pub(crate) fn validate_workspace_coverage_projection_plan(
obligation: &WorkspaceCoverageObligation,
plan: &WorkspaceCoverageProjectionPlan,
) -> Result<()> {
let expected = derive_workspace_coverage_projection_plan(
obligation,
&plan.projection_schema_version,
&plan.chunker_id,
&plan.embedding_provider_id,
&plan.embedding_model,
plan.vector_dimensions,
plan.planned_points
.iter()
.map(|point| WorkspaceCoverageChunkDescriptor {
body_path: point.body_path.clone(),
source_raw_sha256: point.source_raw_sha256.clone(),
source_raw_byte_length: point.source_raw_byte_length,
chunk_index: point.chunk_index,
byte_start: point.byte_start,
byte_end: point.byte_end,
chunk_sha256: point.chunk_sha256.clone(),
})
.collect(),
)?;
if *plan != expected {
bail!("workspace coverage projection plan identity or derived fields were altered");
}
Ok(())
}
pub(crate) fn validate_workspace_coverage_receipt(
obligation: &WorkspaceCoverageObligation,
plan: &WorkspaceCoverageProjectionPlan,
receipt: &WorkspaceCoverageReceipt,
) -> Result<()> {
validate_workspace_coverage_projection_plan(obligation, plan)?;
let expected_collection = workspace_coverage_execution_collection(
&plan.plan_id,
&receipt.claim_id,
receipt.claim_epoch,
)?;
if receipt.schema_version != WORKSPACE_COVERAGE_RECEIPT_SCHEMA_VERSION
|| receipt.obligation_id != obligation.obligation_id
|| receipt.plan_id != plan.plan_id
|| receipt.receipt_id.trim().is_empty()
|| receipt.collection_name != expected_collection
|| receipt.embedding_provider_id != plan.embedding_provider_id
|| receipt.embedding_model != plan.embedding_model
|| receipt.vector_dimensions != plan.vector_dimensions
|| receipt.observed_at.trim().is_empty()
|| receipt.observation_method != "qdrant_scroll_exact_point_bindings"
|| !sha256_hex(&receipt.observed_vector_binding_set_sha256)
{
bail!("workspace coverage receipt lacks exact observed-index authority");
}
if receipt.observed_point_count != plan.expected_point_count
|| receipt.observed_point_set_sha256 != plan.expected_point_set_sha256
|| receipt.observed_point_binding_set_sha256 != plan.point_binding_set_sha256
{
bail!("workspace coverage receipt does not prove the exact expected point set");
}
Ok(())
}
pub(crate) fn validate_workspace_coverage_head(
obligation: &WorkspaceCoverageObligation,
plan: &WorkspaceCoverageProjectionPlan,
receipt: &WorkspaceCoverageReceipt,
head: &WorkspaceCoverageHead,
) -> Result<()> {
validate_workspace_coverage_receipt(obligation, plan, receipt)?;
if head.schema_version != WORKSPACE_COVERAGE_HEAD_SCHEMA_VERSION
|| head.workspace_id != obligation.workspace_id
|| head.obligation_id != obligation.obligation_id
|| head.plan_id != plan.plan_id
|| head.receipt_id != receipt.receipt_id
|| receipt.obligation_id != obligation.obligation_id
|| receipt.plan_id != plan.plan_id
|| head.body_observation_id != obligation.body_observation_id
|| head.body_generation != obligation.body_generation
|| head.manifest_root_sha256 != obligation.manifest_root_sha256
|| head.claim_id != receipt.claim_id
|| head.claim_epoch != receipt.claim_epoch
{
bail!("workspace coverage head does not join one obligation to its exact receipt");
}
Ok(())
}
pub fn workspace_coverage_execution_collection(
plan_id: &str,
claim_id: &str,
claim_epoch: u64,
) -> Result<String> {
if plan_id.trim().is_empty() || uuid::Uuid::parse_str(claim_id).is_err() || claim_epoch == 0 {
bail!("workspace coverage execution collection requires plan and fenced UUID claim");
}
let namespace = digest(&(plan_id, claim_id, claim_epoch))?;
Ok(format!("epiphany_workspace_{}", &namespace[..40]))
}
fn point_payload(
obligation: &WorkspaceCoverageObligation,
projection_schema_version: &str,
chunker_id: &str,
point: &WorkspaceCoveragePlannedPoint,
) -> WorkspaceCoveragePointPayload {
WorkspaceCoveragePointPayload {
obligation_id: obligation.obligation_id.clone(),
projection_schema_version: projection_schema_version.into(),
chunker_id: chunker_id.into(),
body_path: point.body_path.clone(),
source_raw_sha256: point.source_raw_sha256.clone(),
source_raw_byte_length: point.source_raw_byte_length,
chunk_index: point.chunk_index,
byte_start: point.byte_start,
byte_end: point.byte_end,
chunk_sha256: point.chunk_sha256.clone(),
}
}
fn sha256_hex(value: &str) -> bool {
value.len() == 64
&& value
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
}
fn classify(
entry: &RepositoryBodyManifestEntry,
policy: &WorkspaceCoveragePolicy,
) -> WorkspaceCoverageClassification {
let disposition = match entry.kind.as_str() {
"gitlink_nonrecursive" => WorkspaceCoverageDisposition::ExcludeGitlink,
"symlink_target" => WorkspaceCoverageDisposition::ExcludeSymlink,
"regular" if entry.raw_byte_length == 0 => {
WorkspaceCoverageDisposition::ExcludeEmptyRegularFile
}
"regular" if entry.raw_byte_length > policy.maximum_file_bytes => {
WorkspaceCoverageDisposition::ExcludeOversize
}
"regular" => WorkspaceCoverageDisposition::IncludeRegularFile,
_ => WorkspaceCoverageDisposition::ExcludeUnsupportedKind,
};
WorkspaceCoverageClassification {
path: entry.path.clone(),
raw_sha256: entry.raw_sha256.clone(),
raw_byte_length: entry.raw_byte_length,
disposition,
}
}
fn digest(value: &(impl Serialize + ?Sized)) -> Result<String> {
Ok(format!(
"{:x}",
Sha256::digest(rmp_serde::to_vec_named(value).map_err(|error| anyhow!(error))?)
))
}
#[cfg(test)]
mod tests {
use super::*;
fn basis() -> RepositoryBodyObservationBasis {
RepositoryBodyObservationBasis {
schema_version: "epiphany.repository_body.v2".into(),
workspace_id: "workspace-1".into(),
swarm_id: "swarm-1".into(),
runtime_id: "runtime-1".into(),
scope: "git_worktree".into(),
body_binding_sha256: "binding-hash".into(),
observation_id: "workspace-1:7".into(),
generation: 7,
manifest_root_sha256: "manifest-hash".into(),
scan_started_at: "start".into(),
scan_finished_at: "finish".into(),
}
}
fn entry(path: &str, kind: &str, size: u64) -> RepositoryBodyManifestEntry {
RepositoryBodyManifestEntry {
path: path.into(),
git_mode: "100644".into(),
kind: kind.into(),
raw_byte_length: size,
raw_sha256: format!("hash-{path}"),
gitlink_oid: None,
}
}
#[test]
fn every_manifest_entry_receives_one_policy_disposition() -> Result<()> {
let manifest = RepositoryBodyManifest {
schema_version: "epiphany.repository_body.v2".into(),
manifest_root_sha256: "manifest-hash".into(),
workspace_id: "workspace-1".into(),
scope: "git_worktree".into(),
entries: vec![
entry("a-empty", "regular", 0),
entry("a.rs", "regular", 12),
entry("b.bin", "regular", 101),
entry("c-link", "symlink_target", 3),
entry("d-submodule", "gitlink_nonrecursive", 0),
],
};
let obligation = derive_workspace_coverage_obligation_from_authenticated_manifest(
&basis(),
&manifest,
&WorkspaceCoveragePolicy::bounded_regular_files_v0(100)?,
)?;
assert_eq!(obligation.classifications.len(), manifest.entries.len());
assert_eq!(obligation.included_entry_count, 1);
assert_eq!(
obligation.classifications[0].disposition,
WorkspaceCoverageDisposition::ExcludeEmptyRegularFile
);
assert_eq!(
obligation.classifications[2].disposition,
WorkspaceCoverageDisposition::ExcludeOversize
);
Ok(())
}
#[test]
fn manifest_substitution_and_duplicate_paths_are_refused() -> Result<()> {
let mut manifest = RepositoryBodyManifest {
schema_version: "epiphany.repository_body.v2".into(),
manifest_root_sha256: "wrong".into(),
workspace_id: "workspace-1".into(),
scope: "git_worktree".into(),
entries: vec![entry("a", "regular", 1)],
};
let policy = WorkspaceCoveragePolicy::bounded_regular_files_v0(100)?;
assert!(
derive_workspace_coverage_obligation_from_authenticated_manifest(
&basis(),
&manifest,
&policy
)
.is_err()
);
manifest.manifest_root_sha256 = "manifest-hash".into();
manifest.entries.push(entry("a", "regular", 1));
assert!(
derive_workspace_coverage_obligation_from_authenticated_manifest(
&basis(),
&manifest,
&policy
)
.is_err()
);
Ok(())
}
fn coverage_obligation(id: &str) -> WorkspaceCoverageObligation {
WorkspaceCoverageObligation {
schema_version: WORKSPACE_COVERAGE_OBLIGATION_SCHEMA_VERSION.into(),
obligation_id: id.into(),
workspace_id: "workspace-1".into(),
swarm_id: "swarm-1".into(),
runtime_id: "runtime-1".into(),
body_binding_sha256: "binding".into(),
body_observation_id: "workspace-1:1".into(),
body_generation: 1,
manifest_root_sha256: "manifest".into(),
policy_id: "bounded_regular_files_v0".into(),
policy_sha256: "policy".into(),
classification_set_sha256: "classes".into(),
classifications: vec![WorkspaceCoverageClassification {
path: "src/lib.rs".into(),
raw_sha256: "11".repeat(32),
raw_byte_length: 10,
disposition: WorkspaceCoverageDisposition::IncludeRegularFile,
}],
included_entry_count: 1,
}
}
fn chunks() -> Vec<WorkspaceCoverageChunkDescriptor> {
vec![
WorkspaceCoverageChunkDescriptor {
body_path: "src/lib.rs".into(),
source_raw_sha256: "11".repeat(32),
source_raw_byte_length: 10,
chunk_index: 0,
byte_start: 0,
byte_end: 5,
chunk_sha256: "22".repeat(32),
},
WorkspaceCoverageChunkDescriptor {
body_path: "src/lib.rs".into(),
source_raw_sha256: "11".repeat(32),
source_raw_byte_length: 10,
chunk_index: 1,
byte_start: 3,
byte_end: 10,
chunk_sha256: "33".repeat(32),
},
]
}
fn plan(obligation: &WorkspaceCoverageObligation) -> Result<WorkspaceCoverageProjectionPlan> {
derive_workspace_coverage_projection_plan(
obligation,
"projection-v0",
"chunker-v0",
"provider",
"model",
3,
chunks(),
)
}
#[test]
fn immutable_model_artifact_identity_changes_projection_plan() -> Result<()> {
let obligation = coverage_obligation("obligation-model-artifact");
let first = derive_workspace_coverage_projection_plan(
&obligation,
"projection-v0",
"chunker-v0",
"provider",
&format!("model:latest@sha256:{}", "aa".repeat(32)),
3,
chunks(),
)?;
let second = derive_workspace_coverage_projection_plan(
&obligation,
"projection-v0",
"chunker-v0",
"provider",
&format!("model:latest@sha256:{}", "bb".repeat(32)),
3,
chunks(),
)?;
assert_ne!(first.plan_id, second.plan_id);
Ok(())
}
#[test]
fn receipt_and_head_cannot_substitute_payload_or_claim_authority() -> Result<()> {
let obligation = coverage_obligation("obligation-1");
let plan = derive_workspace_coverage_projection_plan(
&obligation,
"projection-v0",
"chunker-v0",
"provider",
"model",
3,
chunks(),
)?;
let claim_id = uuid::Uuid::new_v4().to_string();
let claim_epoch = 7;
let mut receipt = WorkspaceCoverageReceipt {
schema_version: WORKSPACE_COVERAGE_RECEIPT_SCHEMA_VERSION.into(),
receipt_id: "receipt-1".into(),
obligation_id: obligation.obligation_id.clone(),
plan_id: plan.plan_id.clone(),
collection_name: workspace_coverage_execution_collection(
&plan.plan_id,
&claim_id,
claim_epoch,
)?,
embedding_provider_id: "provider".into(),
embedding_model: "model".into(),
vector_dimensions: 3,
observed_point_count: plan.expected_point_count,
observed_point_set_sha256: plan.expected_point_set_sha256.clone(),
observed_at: "now".into(),
observation_method: "qdrant_scroll_exact_point_bindings".into(),
observed_point_binding_set_sha256: plan.point_binding_set_sha256.clone(),
claim_id: claim_id.clone(),
claim_epoch,
observed_vector_binding_set_sha256: "55".repeat(32),
};
assert!(validate_workspace_coverage_receipt(&obligation, &plan, &receipt).is_ok());
let mut head = WorkspaceCoverageHead {
schema_version: WORKSPACE_COVERAGE_HEAD_SCHEMA_VERSION.into(),
workspace_id: obligation.workspace_id.clone(),
obligation_id: obligation.obligation_id.clone(),
plan_id: plan.plan_id.clone(),
receipt_id: receipt.receipt_id.clone(),
body_observation_id: obligation.body_observation_id.clone(),
body_generation: obligation.body_generation,
manifest_root_sha256: obligation.manifest_root_sha256.clone(),
claim_id,
claim_epoch,
};
assert!(validate_workspace_coverage_head(&obligation, &plan, &receipt, &head).is_ok());
receipt.observed_point_binding_set_sha256 = "44".repeat(32);
assert!(validate_workspace_coverage_receipt(&obligation, &plan, &receipt).is_err());
receipt.observed_point_binding_set_sha256 = plan.point_binding_set_sha256.clone();
receipt.claim_epoch += 1;
assert!(validate_workspace_coverage_receipt(&obligation, &plan, &receipt).is_err());
receipt.claim_epoch = claim_epoch;
receipt.claim_id = "not-a-uuid".into();
assert!(validate_workspace_coverage_receipt(&obligation, &plan, &receipt).is_err());
receipt.claim_id = head.claim_id.clone();
head.claim_id = uuid::Uuid::new_v4().to_string();
assert!(validate_workspace_coverage_head(&obligation, &plan, &receipt, &head).is_err());
receipt.observation_method = "claimed_upsert_count".into();
assert!(validate_workspace_coverage_head(&obligation, &plan, &receipt, &head).is_err());
Ok(())
}
#[test]
fn projection_plan_seals_point_set_and_rejects_cross_obligation_use() -> Result<()> {
let mut obligation = coverage_obligation("obligation-a");
let mut duplicate = chunks();
duplicate[1].chunk_index = 0;
assert!(
derive_workspace_coverage_projection_plan(
&obligation,
"projection",
"chunker",
"provider",
"model",
3,
duplicate
)
.is_err()
);
let mut invalid = chunks();
invalid[0].byte_end = 11;
assert!(
derive_workspace_coverage_projection_plan(
&obligation,
"projection",
"chunker",
"provider",
"model",
3,
invalid
)
.is_err()
);
let mut plan = plan(&obligation)?;
assert!(uuid::Uuid::parse_str(&plan.planned_points[0].point_id).is_ok());
plan.planned_points[0].chunk_sha256 = "44".repeat(32);
assert!(validate_workspace_coverage_projection_plan(&obligation, &plan).is_err());
obligation.obligation_id = "obligation-b".into();
assert!(validate_workspace_coverage_projection_plan(&obligation, &plan).is_err());
Ok(())
}
#[test]
fn execution_claim_fences_collection_without_changing_plan() -> Result<()> {
let obligation = coverage_obligation("body-policy-epoch-a");
let first = plan(&obligation)?;
let claim_a = uuid::Uuid::new_v4().to_string();
let claim_b = uuid::Uuid::new_v4().to_string();
let collection_a = workspace_coverage_execution_collection(&first.plan_id, &claim_a, 1)?;
let collection_b = workspace_coverage_execution_collection(&first.plan_id, &claim_b, 2)?;
assert_ne!(collection_a, collection_b);
assert_eq!(first, plan(&obligation)?);
assert!(collection_a.starts_with("epiphany_workspace_"));
assert_eq!(collection_a.len(), "epiphany_workspace_".len() + 40);
Ok(())
}
}

View file

@ -213,7 +213,6 @@ fn ensure_persona_pass_input_admitted(
timestamp: mention.queued_at.clone(),
})
.collect::<Vec<_>>();
let semantic_recall = String::new();
let social_affordances = request
.mentions
.iter()
@ -242,7 +241,6 @@ fn ensure_persona_pass_input_admitted(
let projector_input = PersonaProjectorInput {
identity,
memories: memories.clone(),
semantic_memory_recall: semantic_recall.clone(),
pending_mentions: request.mentions.clone(),
repo_activity,
social_affordances,
@ -278,7 +276,6 @@ fn ensure_persona_pass_input_admitted(
.iter()
.map(|mention| mention.channel_id.clone())
.collect(),
dynamic_semantic_memory_recall: semantic_recall,
observed_sources,
admitted_at: request.reserved_at.clone(),
};
@ -421,7 +418,6 @@ mod tests {
},
transcript: Vec::new(),
allowed_channel_ids: vec!["aquarium".into()],
dynamic_semantic_memory_recall: String::new(),
observed_sources: vec![provenance.clone()],
admitted_at: "2026-08-18T03:00:01Z".into(),
};

View file

@ -31,7 +31,6 @@ pub struct PersonaModelExecutionPlan {
projector_input: PersonaProjectorInput,
transcript: Vec<PersonaTranscriptMessage>,
allowed_channel_ids: Vec<String>,
dynamic_semantic_memory_recall: String,
source_documents: Vec<epiphany_core::EpiphanyMindDocumentVersion>,
cultmesh_store: PathBuf,
runtime_id: String,
@ -56,7 +55,6 @@ impl PersonaModelExecutionPlan {
projector_input: document.projector_input,
transcript: document.transcript,
allowed_channel_ids: document.allowed_channel_ids,
dynamic_semantic_memory_recall: document.dynamic_semantic_memory_recall,
source_documents: vec![source],
cultmesh_store,
runtime_id: runtime_id.into(),
@ -284,8 +282,6 @@ pub async fn execute_persona_model_turn_with_runner<R: PersonaModelRunner>(
identity: plan.projector_input.identity.clone(),
persona_prompt: projector.output.clone(),
persona_output: persona.output.clone(),
semantic_memory_recall: plan.projector_input.semantic_memory_recall.clone(),
dynamic_semantic_memory_recall: plan.dynamic_semantic_memory_recall.clone(),
pending_mentions: plan.projector_input.pending_mentions.clone(),
allowed_channel_ids: plan.allowed_channel_ids.clone(),
};
@ -532,7 +528,6 @@ fn validate_plan_source(store_path: &PathBuf, plan: &PersonaModelExecutionPlan)
|| document.projector_input != plan.projector_input
|| document.transcript != plan.transcript
|| document.allowed_channel_ids != plan.allowed_channel_ids
|| document.dynamic_semantic_memory_recall != plan.dynamic_semantic_memory_recall
{
return Err(anyhow!(
"Persona execution plan diverges from its admitted Mind input"
@ -813,7 +808,6 @@ mod tests {
},
transcript: vec![],
allowed_channel_ids,
dynamic_semantic_memory_recall: String::new(),
observed_sources: vec![provenance.clone()],
admitted_at: "2026-08-14T00:00:00Z".into(),
};

View file

@ -82,17 +82,8 @@ pub struct PackageReleaseRequest<'a> {
pub fn required_packaged_release_binaries(target_triple: &str) -> Vec<(&'static str, String)> {
let file_name = |name: &str| target_binary_file_name(target_triple, name);
vec![
("supervisor", file_name("epiphany-daemon-supervisor")),
("release-publisher", file_name("epiphany-release")),
("state-steward", file_name("epiphany-state")),
(
"semantic-projector",
file_name("epiphany-memory-semantic-projector"),
),
(
"workspace-coverage-projector",
file_name("epiphany-workspace-coverage-projector"),
),
("atlas-publisher", file_name("epiphany-atlas-publisher")),
(
"model-entanglement-projector",
@ -102,7 +93,6 @@ pub fn required_packaged_release_binaries(target_triple: &str) -> Vec<(&'static
"atlas-impact-ingress",
file_name("epiphany-atlas-impact-ingress"),
),
("semantic-query", file_name("epiphany-memory-semantic")),
("repository-body", file_name("epiphany-repository-body")),
("host-identity", file_name("epiphany-host-identity")),
("swarm", file_name("epiphany-swarm")),
@ -615,19 +605,13 @@ fn release_bundle_target_dir(
fn required_release_build_target(role: &str) -> Result<(&'static str, &'static str)> {
match role {
"supervisor" => Ok(("epiphany-core", "epiphany-daemon-supervisor")),
"release-publisher" => Ok(("epiphany-core", "epiphany-release")),
"state-steward" => Ok(("epiphany-core", "epiphany-state")),
"semantic-projector" => Ok(("epiphany-core", "epiphany-memory-semantic-projector")),
"workspace-coverage-projector" => {
Ok(("epiphany-core", "epiphany-workspace-coverage-projector"))
}
"atlas-publisher" => Ok(("epiphany-core", "epiphany-atlas-publisher")),
"model-entanglement-projector" => {
Ok(("epiphany-core", "epiphany-model-entanglement-projector"))
}
"atlas-impact-ingress" => Ok(("epiphany-core", "epiphany-atlas-impact-ingress")),
"semantic-query" => Ok(("epiphany-core", "epiphany-memory-semantic")),
"repository-body" => Ok(("epiphany-core", "epiphany-repository-body")),
"host-identity" => Ok(("epiphany-core", "epiphany-host-identity")),
"swarm" => Ok(("epiphany-core", "epiphany-swarm")),

View file

@ -647,19 +647,7 @@ pub struct EpiphanyObjectiveDraftLanePlan {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)]
pub struct EpiphanyMemoryGraphSnapshot {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "string | null")]
pub schema_version: Option<String>,
pub graph_id: String,
/// Monotonic identity of the canonical aggregate. Legacy snapshots decode as revision zero.
#[serde(default)]
pub model_revision: u64,
/// SHA-256 of the snapshot with this field cleared.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub model_hash: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "EpiphanyMemoryGraphSource | null")]
pub source: Option<EpiphanyMemoryGraphSource>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyMemoryDomain>")]
pub domains: Vec<EpiphanyMemoryDomain>,
@ -672,12 +660,6 @@ pub struct EpiphanyMemoryGraphSnapshot {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyMemorySummary>")]
pub summaries: Vec<EpiphanyMemorySummary>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "EpiphanyMemoryEmbeddingManifest | null")]
pub embedding_manifest: Option<EpiphanyMemoryEmbeddingManifest>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "EpiphanyMemoryFreshness | null")]
pub freshness: Option<EpiphanyMemoryFreshness>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyMemoryLifecycleReceipt>")]
pub lifecycle_receipts: Vec<EpiphanyMemoryLifecycleReceipt>,
@ -794,7 +776,7 @@ pub fn reorient_checkpoint_from_admitted_repo_model(
disposition: EpiphanyInvestigationDisposition::ResumeReady,
focus: "Current Mind-admitted RepoModel frontier".to_string(),
summary: Some(
"Derived from the authenticated current RepoModel semantic projection.".to_string(),
"Derived from the authenticated current keyed RepoModel view.".to_string(),
),
next_action: Some("Resume from the current Mind-admitted RepoModel frontier.".to_string()),
captured_at_turn_id: None,
@ -816,13 +798,6 @@ pub fn reorient_checkpoint_from_admitted_repo_model(
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS)]
pub struct EpiphanyMemoryGraphSource {
pub kind: String,
pub identity: String,
pub revision: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)]
pub struct EpiphanyMemoryDomain {
pub id: String,
@ -1030,8 +1005,6 @@ pub struct EpiphanyMemorySummary {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<string>")]
pub source_hashes: Vec<String>,
#[serde(default)]
pub freshness: EpiphanyMemoryFreshnessStatus,
#[serde(default, skip_serializing_if = "is_zero_u32")]
pub confidence: u32,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
@ -1058,133 +1031,7 @@ pub struct EpiphanyMemoryLifecycleReceipt {
pub created_at: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)]
pub struct EpiphanyMemoryEmbeddingManifest {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "string | null")]
pub collection_name: Option<String>,
pub embedding_model: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "number | null")]
pub vector_dimensions: Option<u32>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<string>")]
pub indexed_document_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<string>")]
pub stale_document_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<string>")]
pub source_hashes: Vec<String>,
}
#[derive(
Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash, JsonSchema, TS, Default,
)]
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
pub enum EpiphanyMemoryFreshnessStatus {
#[default]
Missing,
Ready,
Stale,
Indexing,
Unavailable,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)]
pub struct EpiphanyMemoryFreshness {
pub status: EpiphanyMemoryFreshnessStatus,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<string>")]
pub stale_node_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<string>")]
pub stale_edge_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<string>")]
pub stale_summary_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<string>")]
pub dirty_source_hashes: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "string | null")]
pub note: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)]
pub struct EpiphanyMemoryContextQuery {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "EpiphanyMemoryProfile | null")]
pub profile: Option<EpiphanyMemoryProfile>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<string>")]
pub domain_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<string>")]
pub node_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<string>")]
pub edge_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "string | null")]
pub text: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "number | null")]
pub budget: Option<u32>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)]
pub struct EpiphanyMemoryContextPacket {
pub id: String,
pub query_id: String,
#[serde(default)]
pub repo_model_revision: u64,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub repo_model_hash: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyMemoryNode>")]
pub nodes: Vec<EpiphanyMemoryNode>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyMemoryEdge>")]
pub edges: Vec<EpiphanyMemoryEdge>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyMemorySummary>")]
pub summaries: Vec<EpiphanyMemorySummary>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<RepoFrontierItem>")]
pub frontier: Vec<RepoFrontierItem>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyMemoryAnchor>")]
pub anchors: Vec<EpiphanyMemoryAnchor>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<string>")]
pub warnings: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<string>")]
pub missing_node_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<string>")]
pub missing_edge_ids: Vec<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)]
pub struct EpiphanyMemoryPatchCandidate {
pub id: String,
pub profile: EpiphanyMemoryProfile,
pub status: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyMemoryNode>")]
pub proposed_nodes: Vec<EpiphanyMemoryNode>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyMemoryEdge>")]
pub proposed_edges: Vec<EpiphanyMemoryEdge>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<string>")]
pub reasons: Vec<String>,
}
fn is_zero_u32(value: &u32) -> bool {
*value == 0

View file

@ -1,25 +0,0 @@
schema_version = "epiphany.void_memory_bridge.v0"
database_dsn_env = "VOID_DATABASE_DSN"
database_dsn = "postgres://voidbot:voidbot@localhost:5432/voidbot"
qdrant_url = "http://127.0.0.1:6333"
qdrant_api_key_env = "QDRANT_API_KEY"
qdrant_history_collection = "voidbot_discord_history_chunks"
qdrant_source_collection = "voidbot_repository_source_chunks"
qdrant_timeout_ms = 30000
ollama_base_url = "http://127.0.0.1:11434"
ollama_model = "qwen3-embedding:0.6b"
ollama_timeout_ms = 30000
history_query_instruction = "Given a Discord history question, retrieve relevant messages and discussion snippets that answer it."
source_query_instruction = "Given a source-tree, codebase, or lore question, retrieve relevant files, code snippets, and lore passages that answer it."
archive_path = "E:\\Projects\\VoidBot\\.voidbot\\rag\\messages.json"
policy = [
"Persona may search archived Discord memory for context before speaking.",
"Qdrant is the semantic memory backend for Discord history and source/lore vectors.",
"Postgres is checked as the live Void state spine for jobs, audit, interaction memory, and rate limits.",
"Raw archive rows remain evidence, not public speech; Persona summarizes with restraint.",
]