feat(pncc): add verified memory metabolism organ
GuangHu-Human-Responsibility: ICE-GL∞ / 冰朔 GuangHu-Persona-Cognitive-Author: ICE-P-ZY001 / 铸渊 GuangHu-Execution-Runtime: Codex desktop / GPT-5 GuangHu-Development-ID: DEV-20260810-014 GuangHu-Authorization-Scope: GH-PNCC persona runtime layer without UI or execution limb
This commit is contained in:
parent
33b3b67ce7
commit
962ea26db7
9 changed files with 530 additions and 31 deletions
|
|
@ -527,6 +527,7 @@ macro_rules! app_invoke_handler {
|
|||
persona_code_channel::inspect_persona_code_channel_manifest,
|
||||
persona_code_channel::query_persona_code_channel_runtime,
|
||||
persona_code_channel::run_persona_code_channel_fact_task,
|
||||
persona_code_channel::run_persona_code_channel_memory_metabolism,
|
||||
persona_code_channel::inspect_persona_code_channel_session,
|
||||
persona_code_channel::recover_persona_code_channel_session,
|
||||
guanghu_router::guanghu_router_connect,
|
||||
|
|
|
|||
|
|
@ -218,14 +218,14 @@ pub struct PersonaFactTaskInput {
|
|||
pub api_key_override: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PersonaFact {
|
||||
pub statement: String,
|
||||
pub evidence_paths: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PersonaFactResult {
|
||||
pub summary: String,
|
||||
|
|
@ -253,6 +253,35 @@ pub struct PersonaFactTaskReceipt {
|
|||
pub attribution: PersonaAttribution,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PersonaMemoryMetabolismInput {
|
||||
pub session_id: String,
|
||||
pub source_session_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PersonaMemoryMetabolismReceipt {
|
||||
pub schema: &'static str,
|
||||
pub receipt_id: String,
|
||||
pub session_id: String,
|
||||
pub source_session_id: String,
|
||||
pub persona_id: String,
|
||||
pub previous_git_head: String,
|
||||
pub committed_git_head: String,
|
||||
pub source_checkpoint_path: String,
|
||||
pub source_checkpoint_hash: String,
|
||||
pub source_event_hash: String,
|
||||
pub checkpoint_path: String,
|
||||
pub runtime_state: &'static str,
|
||||
pub model_inference_started: bool,
|
||||
pub active_organ: Option<String>,
|
||||
pub result: PersonaFactResult,
|
||||
pub events: Vec<PersonaLifecycleEvent>,
|
||||
pub attribution: PersonaAttribution,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PersonaSessionControlInput {
|
||||
|
|
@ -581,8 +610,8 @@ fn organ_contract(organ: &PersonaOrgan) -> Result<PersonaOrganContract, String>
|
|||
],
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
"DECLARED_NOT_INDEPENDENTLY_ACTIVATABLE",
|
||||
true,
|
||||
"IMPLEMENTED_VERIFIED_CHECKPOINT_PROMOTION",
|
||||
),
|
||||
PersonaOrganKind::ExecutionLimb => (
|
||||
"bounded-execution",
|
||||
|
|
@ -1087,19 +1116,24 @@ fn prepare_wake_at(
|
|||
.find(|candidate| candidate.organ_id == organ_id)
|
||||
.ok_or_else(|| "ORGAN_NOT_DECLARED".to_string())?;
|
||||
let contract = organ_contract(organ)?;
|
||||
if contract.kind != PersonaOrganKind::FactSense || !contract.activatable {
|
||||
return Err("ORGAN_NOT_ACTIVATABLE: only the implemented fact sense may wake".into());
|
||||
if !contract.activatable {
|
||||
return Err("ORGAN_NOT_ACTIVATABLE".into());
|
||||
}
|
||||
for path in &organ.paths {
|
||||
repository_file(&repository, path)?;
|
||||
}
|
||||
if organ.paths.is_empty() {
|
||||
return Err("FACT_ORGAN_HAS_NO_DECLARED_PATHS".into());
|
||||
}
|
||||
if !organ.paths.contains(&manifest.brain_entry)
|
||||
|| !organ.paths.contains(&manifest.current_checkpoint)
|
||||
{
|
||||
return Err("FACT_ORGAN_MUST_INCLUDE_BRAIN_AND_CURRENT_CHECKPOINT".into());
|
||||
match contract.kind {
|
||||
PersonaOrganKind::FactSense => {
|
||||
if !organ.paths.contains(&manifest.brain_entry)
|
||||
|| !organ.paths.contains(&manifest.current_checkpoint)
|
||||
{
|
||||
return Err("FACT_ORGAN_MUST_INCLUDE_BRAIN_AND_CURRENT_CHECKPOINT".into());
|
||||
}
|
||||
}
|
||||
// The current checkpoint is a moving manifest pointer and is already resolved above.
|
||||
// Declared memory paths remain additional bounded sources, not a stale duplicate pointer.
|
||||
PersonaOrganKind::MemoryMetabolism => {}
|
||||
PersonaOrganKind::ExecutionLimb => return Err("ORGAN_NOT_ACTIVATABLE".into()),
|
||||
}
|
||||
|
||||
let lease_path = acquire_primary_lease(
|
||||
|
|
@ -1185,7 +1219,11 @@ fn prepare_wake_at(
|
|||
runtime_state: "BOUND_NOT_INFERENCING",
|
||||
model_inference_started: false,
|
||||
active_organ: organ.organ_id.clone(),
|
||||
organ_mode: "read-only",
|
||||
organ_mode: match contract.kind {
|
||||
PersonaOrganKind::FactSense => "read-only",
|
||||
PersonaOrganKind::MemoryMetabolism => "checkpoint-write",
|
||||
PersonaOrganKind::ExecutionLimb => "bounded-execution",
|
||||
},
|
||||
organ_contract: contract,
|
||||
event_journal: journal.to_string_lossy().into_owned(),
|
||||
events,
|
||||
|
|
@ -1917,6 +1955,259 @@ or claims not supported by the sources.\n{}",
|
|||
})
|
||||
}
|
||||
|
||||
fn verified_memory_source(
|
||||
runtime_root: &Path,
|
||||
record: &PersonaSessionRecord,
|
||||
repository: &Path,
|
||||
observed_head: &str,
|
||||
source_session_id: &str,
|
||||
) -> Result<(PersonaSessionRecord, PersonaFactResult, String, String), String> {
|
||||
let source = load_session_record(runtime_root, source_session_id)?;
|
||||
let source_repository = PathBuf::from(&source.repository_path)
|
||||
.canonicalize()
|
||||
.map_err(|error| format!("PERSONA_SOURCE_REPOSITORY_UNAVAILABLE: {error}"))?;
|
||||
if source.persona_id != record.persona_id
|
||||
|| source_repository != repository
|
||||
|| source.state != "DORMANT"
|
||||
|| source.git_head != observed_head
|
||||
|| source.checkpoint_path != record.checkpoint_path
|
||||
{
|
||||
return Err("MEMORY_SOURCE_NOT_CURRENT_VERIFIED_DORMANT_SESSION".into());
|
||||
}
|
||||
let source_events = verify_event_journal(runtime_root, &source)?;
|
||||
let source_event = source_events
|
||||
.last()
|
||||
.ok_or_else(|| "MEMORY_SOURCE_EVENTS_REQUIRED".to_string())?;
|
||||
if source_event.kind != "DORMANT" || source_event.git_head != observed_head {
|
||||
return Err("MEMORY_SOURCE_EVENT_CHAIN_NOT_DORMANT_AT_CURRENT_HEAD".into());
|
||||
}
|
||||
let source_checkpoint_path = repository_file(repository, &source.checkpoint_path)?;
|
||||
let source_checkpoint_bytes = fs::read(&source_checkpoint_path)
|
||||
.map_err(|error| format!("MEMORY_SOURCE_CHECKPOINT_READ_FAILED: {error}"))?;
|
||||
let source_checkpoint: serde_json::Value = serde_json::from_slice(&source_checkpoint_bytes)
|
||||
.map_err(|error| format!("MEMORY_SOURCE_CHECKPOINT_INVALID: {error}"))?;
|
||||
if source_checkpoint
|
||||
.get("schema")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
!= Some("hololake.persona-checkpoint/v1")
|
||||
|| source_checkpoint
|
||||
.get("sessionId")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
!= Some(source_session_id)
|
||||
|| source_checkpoint
|
||||
.get("personaId")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
!= Some(record.persona_id.as_str())
|
||||
{
|
||||
return Err("MEMORY_SOURCE_CHECKPOINT_IDENTITY_MISMATCH".into());
|
||||
}
|
||||
let result: PersonaFactResult = serde_json::from_value(
|
||||
source_checkpoint
|
||||
.get("result")
|
||||
.cloned()
|
||||
.ok_or_else(|| "MEMORY_SOURCE_STRUCTURED_RESULT_REQUIRED".to_string())?,
|
||||
)
|
||||
.map_err(|error| format!("MEMORY_SOURCE_STRUCTURED_RESULT_INVALID: {error}"))?;
|
||||
validated_fact_result(
|
||||
&serde_json::to_string(&result)
|
||||
.map_err(|error| format!("MEMORY_SOURCE_RESULT_SERIALIZE_FAILED: {error}"))?,
|
||||
&source.fact_source_paths,
|
||||
)?;
|
||||
Ok((
|
||||
source,
|
||||
result,
|
||||
hex_digest(&source_checkpoint_bytes),
|
||||
source_event.event_hash.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
fn run_memory_metabolism_at(
|
||||
runtime_root: &Path,
|
||||
input: PersonaMemoryMetabolismInput,
|
||||
timestamp: &str,
|
||||
) -> Result<PersonaMemoryMetabolismReceipt, String> {
|
||||
let session_id = validated_id("SESSION_ID", &input.session_id)?;
|
||||
let source_session_id = validated_id("SOURCE_SESSION_ID", &input.source_session_id)?;
|
||||
if session_id == source_session_id {
|
||||
return Err("MEMORY_SOURCE_SESSION_MUST_BE_DISTINCT".into());
|
||||
}
|
||||
let timestamp = validated_text("TIMESTAMP", timestamp, MAX_ID_BYTES)?;
|
||||
let mut record = load_session_record(runtime_root, &session_id)?;
|
||||
if record.state != "BOUND_NOT_INFERENCING" {
|
||||
return Err("PERSONA_SESSION_NOT_BOUND".into());
|
||||
}
|
||||
let repository = PathBuf::from(&record.repository_path)
|
||||
.canonicalize()
|
||||
.map_err(|error| format!("PERSONA_REPOSITORY_UNAVAILABLE: {error}"))?;
|
||||
let (exact_repository, observed_head) = exact_repository(&repository)?;
|
||||
if observed_head != record.git_head {
|
||||
return Err("PERSONA_GIT_CHANGED_AFTER_WAKE".into());
|
||||
}
|
||||
require_clean_repository(&exact_repository)?;
|
||||
let manifest = load_manifest(&exact_repository)?;
|
||||
let organ = manifest
|
||||
.organs
|
||||
.iter()
|
||||
.find(|candidate| candidate.organ_id == record.active_organ)
|
||||
.ok_or_else(|| "ORGAN_NOT_DECLARED".to_string())?;
|
||||
let contract = organ_contract(organ)?;
|
||||
if contract.kind != PersonaOrganKind::MemoryMetabolism || !contract.activatable {
|
||||
return Err("PERSONA_SESSION_NOT_MEMORY_METABOLISM".into());
|
||||
}
|
||||
let lease_path = require_primary_lease(runtime_root, &record)?;
|
||||
let mut events = read_ready_event_journal(runtime_root, &record)?;
|
||||
|
||||
let (source, result, source_checkpoint_hash, source_event_hash) = match verified_memory_source(
|
||||
runtime_root,
|
||||
&record,
|
||||
&exact_repository,
|
||||
&observed_head,
|
||||
&source_session_id,
|
||||
) {
|
||||
Ok(source) => source,
|
||||
Err(error) => {
|
||||
finish_failed_session(
|
||||
runtime_root,
|
||||
&mut record,
|
||||
&mut events,
|
||||
&lease_path,
|
||||
×tamp,
|
||||
"MEMORY_SOURCE_REJECTED",
|
||||
);
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
append_event(
|
||||
runtime_root,
|
||||
&record,
|
||||
&mut events,
|
||||
"MEMORY_CANDIDATE_VERIFIED",
|
||||
&observed_head,
|
||||
×tamp,
|
||||
Some(&record.active_organ),
|
||||
)?;
|
||||
|
||||
let checkpoint_relative = format!(
|
||||
".hololake/persona/checkpoints/{}.json",
|
||||
validated_id("SESSION_ID", &record.session_id)?
|
||||
);
|
||||
let checkpoint_path = exact_repository.join(&checkpoint_relative);
|
||||
if checkpoint_path.exists() {
|
||||
finish_failed_session(
|
||||
runtime_root,
|
||||
&mut record,
|
||||
&mut events,
|
||||
&lease_path,
|
||||
×tamp,
|
||||
"CHECKPOINT_COLLISION",
|
||||
);
|
||||
return Err("PERSONA_CHECKPOINT_ALREADY_EXISTS".into());
|
||||
}
|
||||
let checkpoint = serde_json::json!({
|
||||
"schema": "hololake.persona-checkpoint/v1",
|
||||
"sessionId": record.session_id.clone(),
|
||||
"personaId": record.persona_id.clone(),
|
||||
"previousGitHead": record.git_head.clone(),
|
||||
"createdAt": timestamp,
|
||||
"organId": record.active_organ.clone(),
|
||||
"sourceSessionId": source_session_id.clone(),
|
||||
"sourceCheckpointPath": source.checkpoint_path.clone(),
|
||||
"sourceCheckpointHash": source_checkpoint_hash.clone(),
|
||||
"sourceEventHash": source_event_hash.clone(),
|
||||
"result": result.clone(),
|
||||
"attribution": record.attribution.clone(),
|
||||
});
|
||||
let manifest_path = exact_repository.join(MANIFEST_PATH);
|
||||
let original_manifest = fs::read(&manifest_path)
|
||||
.map_err(|error| format!("PERSONA_MANIFEST_READ_FAILED: {error}"))?;
|
||||
let mut promoted_manifest = manifest;
|
||||
promoted_manifest.current_checkpoint = checkpoint_relative.clone();
|
||||
let committed_head = match (|| {
|
||||
write_json_file(&checkpoint_path, &checkpoint, "PERSONA_CHECKPOINT")?;
|
||||
write_json_file(&manifest_path, &promoted_manifest, "PERSONA_MANIFEST")?;
|
||||
persona_git_commit(
|
||||
&exact_repository,
|
||||
&promoted_manifest,
|
||||
&checkpoint_relative,
|
||||
&record,
|
||||
)
|
||||
})() {
|
||||
Ok(head) => head,
|
||||
Err(error) => {
|
||||
let post_attempt_head = git_output(
|
||||
&exact_repository,
|
||||
&["rev-parse", "HEAD"],
|
||||
"PERSONA_GIT_POST_ATTEMPT_HEAD",
|
||||
)?;
|
||||
if post_attempt_head != observed_head {
|
||||
post_attempt_head
|
||||
} else {
|
||||
let rollback = rollback_uncommitted_checkpoint(
|
||||
&exact_repository,
|
||||
&checkpoint_relative,
|
||||
&original_manifest,
|
||||
);
|
||||
finish_failed_session(
|
||||
runtime_root,
|
||||
&mut record,
|
||||
&mut events,
|
||||
&lease_path,
|
||||
×tamp,
|
||||
"CHECKPOINT_COMMIT_FAILED",
|
||||
);
|
||||
rollback?;
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut final_events = Vec::new();
|
||||
for (kind, organ_id) in [
|
||||
("CHECKPOINT_COMMITTED", Some(record.active_organ.as_str())),
|
||||
("ORGAN_RELEASED", Some(record.active_organ.as_str())),
|
||||
("DORMANT", None),
|
||||
] {
|
||||
final_events.push(append_event(
|
||||
runtime_root,
|
||||
&record,
|
||||
&mut events,
|
||||
kind,
|
||||
&committed_head,
|
||||
×tamp,
|
||||
organ_id,
|
||||
)?);
|
||||
}
|
||||
record.git_head = committed_head.clone();
|
||||
record.checkpoint_path = checkpoint_relative.clone();
|
||||
record.state = "DORMANT".into();
|
||||
write_session_record(runtime_root, &record)?;
|
||||
fs::remove_file(&lease_path)
|
||||
.map_err(|error| format!("PERSONA_PRIMARY_LEASE_RELEASE_FAILED: {error}"))?;
|
||||
require_clean_repository(&exact_repository)?;
|
||||
let receipt_hash = final_events
|
||||
.last()
|
||||
.map(|event| event.event_hash.as_str())
|
||||
.ok_or_else(|| "PERSONA_FINAL_EVENTS_MISSING".to_string())?;
|
||||
Ok(PersonaMemoryMetabolismReceipt {
|
||||
schema: "hololake.pncc-memory-metabolism-receipt/v1",
|
||||
receipt_id: format!("PNCC-MEMORY-{}", &receipt_hash[..20]),
|
||||
session_id: record.session_id,
|
||||
source_session_id,
|
||||
persona_id: record.persona_id,
|
||||
previous_git_head: observed_head,
|
||||
committed_git_head: committed_head,
|
||||
source_checkpoint_path: source.checkpoint_path,
|
||||
source_checkpoint_hash,
|
||||
source_event_hash,
|
||||
checkpoint_path: checkpoint_relative,
|
||||
runtime_state: "DORMANT",
|
||||
model_inference_started: false,
|
||||
active_organ: None,
|
||||
result,
|
||||
events,
|
||||
attribution: record.attribution,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn prepare_persona_code_channel_wake(
|
||||
input: PersonaWakeInput,
|
||||
|
|
@ -1985,6 +2276,15 @@ pub async fn run_persona_code_channel_fact_task(
|
|||
.map_err(|error| format!("PERSONA_FACT_TASK_JOIN_FAILED: {error}"))?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn run_persona_code_channel_memory_metabolism(
|
||||
input: PersonaMemoryMetabolismInput,
|
||||
) -> Result<PersonaMemoryMetabolismReceipt, String> {
|
||||
let runtime_root = crate::app_config::preferred_app_config_path("pncc-runtime")?;
|
||||
let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true);
|
||||
run_memory_metabolism_at(&runtime_root, input, ×tamp)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn inspect_persona_code_channel_session(
|
||||
input: PersonaSessionControlInput,
|
||||
|
|
@ -2070,6 +2370,30 @@ mod tests {
|
|||
repo
|
||||
}
|
||||
|
||||
fn declare_memory_organ(repo: &Path) {
|
||||
let manifest_path = repo.join(MANIFEST_PATH);
|
||||
let mut manifest: serde_json::Value =
|
||||
serde_json::from_slice(&fs::read(&manifest_path).unwrap()).unwrap();
|
||||
manifest["organs"]
|
||||
.as_array_mut()
|
||||
.unwrap()
|
||||
.push(serde_json::json!({
|
||||
"organId": "memory-metabolism.checkpoint",
|
||||
"kind": "MEMORY_METABOLISM",
|
||||
"mode": "checkpoint-write",
|
||||
"paths": [".hololake/persona/CURRENT.hdlp"],
|
||||
"inputSchema": "hololake.pncc-checkpoint-candidate/v1",
|
||||
"outputSchema": "hololake.persona-checkpoint/v1"
|
||||
}));
|
||||
fs::write(
|
||||
&manifest_path,
|
||||
serde_json::to_vec_pretty(&manifest).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
run_git(repo, &["add", "."]);
|
||||
run_git(repo, &["commit", "-m", "declare memory organ"]);
|
||||
}
|
||||
|
||||
fn head(repo: &Path) -> String {
|
||||
let output = crate::git::git_command_at(repo)
|
||||
.unwrap()
|
||||
|
|
@ -2353,6 +2677,121 @@ mod tests {
|
|||
require_clean_repository(repo.path()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn independently_promotes_only_the_current_verified_structured_checkpoint() {
|
||||
let repo = persona_repo();
|
||||
declare_memory_organ(repo.path());
|
||||
let runtime = tempfile::TempDir::new().unwrap();
|
||||
prepare_wake_at(
|
||||
runtime.path(),
|
||||
wake_input(repo.path()),
|
||||
"PNCC-SOURCE-FACT",
|
||||
"2026-08-11T00:00:00.000Z",
|
||||
)
|
||||
.unwrap();
|
||||
let fact = run_fact_task_at(
|
||||
runtime.path(),
|
||||
fact_task_input("PNCC-SOURCE-FACT"),
|
||||
"2026-08-11T00:00:01.000Z",
|
||||
|_, _| {
|
||||
Ok(r#"{"summary":"Verified external memory candidate.","facts":[{"statement":"The persona brain source is present.","evidencePaths":["brain/CORE.hdlp"]}],"limitations":[]}"#.into())
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut memory_wake = wake_input(repo.path());
|
||||
memory_wake.organ_id = "memory-metabolism.checkpoint".into();
|
||||
memory_wake.expected_head = head(repo.path());
|
||||
let wake = prepare_wake_at(
|
||||
runtime.path(),
|
||||
memory_wake,
|
||||
"PNCC-MEMORY-SESSION",
|
||||
"2026-08-11T00:00:02.000Z",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(wake.organ_contract.kind, PersonaOrganKind::MemoryMetabolism);
|
||||
assert!(wake.organ_contract.activatable);
|
||||
assert!(!wake.organ_contract.model_inference_allowed);
|
||||
assert_eq!(wake.organ_mode, "checkpoint-write");
|
||||
|
||||
let receipt = run_memory_metabolism_at(
|
||||
runtime.path(),
|
||||
PersonaMemoryMetabolismInput {
|
||||
session_id: "PNCC-MEMORY-SESSION".into(),
|
||||
source_session_id: "PNCC-SOURCE-FACT".into(),
|
||||
},
|
||||
"2026-08-11T00:00:03.000Z",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(receipt.source_checkpoint_path, fact.checkpoint_path);
|
||||
assert_eq!(receipt.result, fact.result);
|
||||
assert!(!receipt.model_inference_started);
|
||||
assert_eq!(receipt.committed_git_head, head(repo.path()));
|
||||
assert_eq!(receipt.events[3].kind, "MEMORY_CANDIDATE_VERIFIED");
|
||||
assert_eq!(receipt.events.last().unwrap().kind, "DORMANT");
|
||||
assert!(!receipt.source_checkpoint_hash.is_empty());
|
||||
assert!(!receipt.source_event_hash.is_empty());
|
||||
assert!(!runtime.path().join("leases/ICE-P-ZY001.json").exists());
|
||||
require_clean_repository(repo.path()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_a_corrupted_memory_source_chain_and_releases_the_primary() {
|
||||
let repo = persona_repo();
|
||||
declare_memory_organ(repo.path());
|
||||
let runtime = tempfile::TempDir::new().unwrap();
|
||||
prepare_wake_at(
|
||||
runtime.path(),
|
||||
wake_input(repo.path()),
|
||||
"PNCC-CORRUPT-SOURCE",
|
||||
"2026-08-11T00:00:00.000Z",
|
||||
)
|
||||
.unwrap();
|
||||
run_fact_task_at(
|
||||
runtime.path(),
|
||||
fact_task_input("PNCC-CORRUPT-SOURCE"),
|
||||
"2026-08-11T00:00:01.000Z",
|
||||
|_, _| {
|
||||
Ok(r#"{"summary":"Verified candidate.","facts":[{"statement":"The brain exists.","evidencePaths":["brain/CORE.hdlp"]}],"limitations":[]}"#.into())
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let source_journal = runtime
|
||||
.path()
|
||||
.join("sessions/PNCC-CORRUPT-SOURCE/events.jsonl");
|
||||
let source_events = fs::read_to_string(&source_journal).unwrap();
|
||||
fs::write(
|
||||
&source_journal,
|
||||
source_events.replacen("\"eventHash\":\"", "\"eventHash\":\"00", 1),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut memory_wake = wake_input(repo.path());
|
||||
memory_wake.organ_id = "memory-metabolism.checkpoint".into();
|
||||
memory_wake.expected_head = head(repo.path());
|
||||
prepare_wake_at(
|
||||
runtime.path(),
|
||||
memory_wake,
|
||||
"PNCC-CORRUPT-MEMORY",
|
||||
"2026-08-11T00:00:02.000Z",
|
||||
)
|
||||
.unwrap();
|
||||
let error = run_memory_metabolism_at(
|
||||
runtime.path(),
|
||||
PersonaMemoryMetabolismInput {
|
||||
session_id: "PNCC-CORRUPT-MEMORY".into(),
|
||||
source_session_id: "PNCC-CORRUPT-SOURCE".into(),
|
||||
},
|
||||
"2026-08-11T00:00:03.000Z",
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(error.contains("EVENT_HASH_INVALID"));
|
||||
assert!(!runtime.path().join("leases/ICE-P-ZY001.json").exists());
|
||||
let record = load_session_record(runtime.path(), "PNCC-CORRUPT-MEMORY").unwrap();
|
||||
assert_eq!(record.state, "DORMANT_AFTER_FAILURE");
|
||||
require_clean_repository(repo.path()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_unscoped_model_evidence_and_releases_primary() {
|
||||
let repo = persona_repo();
|
||||
|
|
|
|||
Loading…
Reference in a new issue