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
|
|
@ -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 头、节点、模型实例、活跃器官、事件数量、链头和
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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