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

@ -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();