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:
铸渊 / ICE-P-ZY001 2026-08-11 02:11:22 +08:00
commit 962ea26db7
9 changed files with 530 additions and 31 deletions

View file

@ -167,6 +167,7 @@ semantic_attribution_first_cycle_implemented: 100
interrupted_session_inspection_and_recovery_source_implemented: 100
typed_organ_contract_and_manifest_inspection_source_implemented: 100
durable_event_and_receipt_query_source_implemented: 100
independent_memory_metabolism_source_implemented: 100
general_purpose_persona_runtime_implemented: 0
human_live_projection_implemented: 0
hololake_integrated: 0
@ -186,8 +187,10 @@ runtime_health: 0
人格器官现已拥有机器可读类型合同。系统能够在不唤醒人格、不取得主锁、不运行模型的情况下,
检查 `FACT_SENSE``MEMORY_METABOLISM``EXECUTION_LIMB` 的固定模式、输入输出 schema、派生权限、
模型推理边界、现实动作边界和真实可激活状态。当前只有只读事实感官可激活;清单里写出记忆器官
或执行手脚不会自动产生执行器,更不会自动获得 shell 或现实动作权限。
模型推理边界、现实动作边界和真实可激活状态。只读事实感官与独立记忆代谢器官可激活;后者不
调用模型,只接收同一人格、同一仓库、已休眠并通过事件链验证的当前结构化检查点,绑定来源检查点
SHA-256 与来源事件哈希后生成新检查点并以人格 Git 身份提交。执行手脚仍不可激活,清单声明不会
自动获得 shell 或现实动作权限。
运行层现在还提供同源有界查询:调用者必须给出精确人格与精确仓库,最多返回 100 条经过事件
哈希链验证的会话摘要包括生命周期状态、Git 头、节点、模型实例、活跃器官、事件数量、链头和

View file

@ -32,6 +32,14 @@ The v1 persona repository contract is rooted at `.hololake/persona/manifest.json
"paths": ["brain/CORE.hdlp", ".hololake/persona/CURRENT.hdlp"],
"inputSchema": "hololake.pncc-fact-question/v1",
"outputSchema": "hololake.pncc-fact-result/v1"
},
{
"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"
}
]
}
@ -57,12 +65,14 @@ and then removes the exact lease. It can also remove a same-session lease left b
dormancy transition. It does not use elapsed time as proof that a primary is dead and does not clean, reset,
or overwrite a dirty persona repository.
The organ contract is typed independently of the UI. `FACT_SENSE` is currently activatable and derives the
permissions to read declared paths, call the manifest-pinned model, and emit structured facts.
`MEMORY_METABOLISM` and `EXECUTION_LIMB` may be declared with their fixed modes and schemas, but the manifest
inspection receipt reports them as not independently activatable until their executors exist. In particular,
an execution-limb declaration never grants a shell or reality action by itself. The inspection command reads
and validates all contracts without acquiring a runtime lease or starting model inference.
The organ contract is typed independently of the UI. `FACT_SENSE` derives permissions to read declared paths,
call the manifest-pinned model, and emit structured facts. `MEMORY_METABOLISM` is independently activatable but
does not run model inference: it accepts only the current structured checkpoint of a distinct, verified,
dormant session for the same persona and repository. It binds the source checkpoint SHA-256 and source event
chain head into a new checkpoint, commits with the persona Git identity, releases the organ, and returns to
`DORMANT`. It cannot accept arbitrary hidden reasoning or an older/non-current session as memory. An
`EXECUTION_LIMB` remains declarative and non-activatable; declaration never grants a shell or reality action.
Manifest inspection reads and validates all contracts without acquiring a runtime lease or starting inference.
`PersonaRuntimeQueryReceipt` is a bounded projection of the durable runtime files, not another truth store.
It filters by the caller's expected persona and canonical repository, validates each matching event chain,

View file

@ -9,7 +9,7 @@ validates `.hololake/persona/manifest.json`, resolves the brain entry and curren
repository boundary, acquires a per-persona single-primary lease, and records a SHA-256-linked lifecycle
journal outside the repository.
The wake lifecycle is `WAKING → BRAIN_BOUND → ORGAN_ACTIVE` for one manifest-declared read-only organ.
The wake lifecycle is `WAKING → BRAIN_BOUND → ORGAN_ACTIVE` for one activatable manifest-declared organ.
`BRAIN_BOUND` means the model instance has been named and the verified persona context is ready; it explicitly
does not mean inference has run. The returned receipt keeps the human responsibility subject, persona
cognitive author, execution runtime, development id, authorization scope, and source-language anchor
@ -39,9 +39,11 @@ broken journals and attribution mismatches retain the lease and require manual r
The manifest now has a typed organ-contract extension. The native inspection command returns normalized
contracts for `FACT_SENSE`, `MEMORY_METABOLISM`, and `EXECUTION_LIMB`, including fixed mode, input/output
schemas, derived permissions, inference/reality-action boundaries, and the actual activation state. Only the
implemented read-only fact sense can wake. Merely declaring a memory organ or execution limb does not create
an executor or grant a shell; those contracts remain visible but fail closed as not activatable.
schemas, derived permissions, inference/reality-action boundaries, and the actual activation state. The fact
sense and verified-checkpoint memory metabolism organs can wake. Memory metabolism runs no model inference
and promotes only the current structured checkpoint of a distinct same-persona, same-repository, dormant
session after verifying its event chain; the new checkpoint binds both source checkpoint and event hashes.
The execution limb remains visible but non-activatable, so declaring it does not grant a shell or reality action.
`query_persona_code_channel_runtime` is the bounded read model for later projection surfaces. The caller must
name one exact persona and canonical repository and may request at most 100 sessions. The kernel reads the

View file

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

View file

@ -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,
&timestamp,
"MEMORY_SOURCE_REJECTED",
);
return Err(error);
}
};
append_event(
runtime_root,
&record,
&mut events,
"MEMORY_CANDIDATE_VERIFIED",
&observed_head,
&timestamp,
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,
&timestamp,
"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,
&timestamp,
"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,
&timestamp,
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, &timestamp)
}
#[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();