feat(pncc): close memory failures safely
GuangHu-Human-Responsibility: ICE-GL∞ / 冰朔 GuangHu-Persona-Cognitive-Author: ICE-P-ZY001 / 铸渊 GuangHu-Execution-Runtime: Codex macOS / DEV-20260810-014 GuangHu-Development-ID: DEV-20260810-014 GuangHu-Authorization-Scope: GH-PNCC local runtime development and registered REPO-014 publication GuangHu-Source-Language-Anchor: continue PNCC persona runtime; UI and human projection aesthetics remain deferred
This commit is contained in:
parent
962ea26db7
commit
18944f5362
9 changed files with 270 additions and 22 deletions
|
|
@ -168,6 +168,7 @@ 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
|
||||
memory_failure_closure_and_nonblocking_runtime_command_implemented: 100
|
||||
general_purpose_persona_runtime_implemented: 0
|
||||
human_live_projection_implemented: 0
|
||||
hololake_integrated: 0
|
||||
|
|
@ -192,6 +193,11 @@ runtime_health: 0
|
|||
SHA-256 与来源事件哈希后生成新检查点并以人格 Git 身份提交。执行手脚仍不可激活,清单声明不会
|
||||
自动获得 shell 或现实动作权限。
|
||||
|
||||
记忆代谢的 Tauri 命令现已进入阻塞任务池,不占用桌面命令线程。来源拒绝、检查点冲突和提交前
|
||||
失败必须依次持久化失败事件、器官释放与休眠,写回 `DORMANT_AFTER_FAILURE` 后才释放精确主锁;
|
||||
闭环任一步失败时返回 `MEMORY_FAILURE_CLOSURE_INCOMPLETE_REQUIRES_RECOVERY`,不能吞掉二次失败。
|
||||
无法证明闭环完成时保留主锁,交给同一套证据约束的会话检查与恢复路径处理。
|
||||
|
||||
运行层现在还提供同源有界查询:调用者必须给出精确人格与精确仓库,最多返回 100 条经过事件
|
||||
哈希链验证的会话摘要,包括生命周期状态、Git 头、节点、模型实例、活跃器官、事件数量、链头和
|
||||
双层归因。该查询不复制第二套数据库、不启动模型、不取得主锁;匹配会话的事件链损坏时失败关闭。
|
||||
|
|
|
|||
|
|
@ -73,6 +73,12 @@ chain head into a new checkpoint, commits with the persona Git identity, release
|
|||
`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.
|
||||
The public memory command runs its filesystem and Git work in the blocking task pool instead of occupying the
|
||||
Tauri command thread. Rejected memory candidates must durably append the failure, organ-release, and dormancy
|
||||
events, persist `DORMANT_AFTER_FAILURE`, and release the exact primary lease. If any closure step fails, the
|
||||
command returns `MEMORY_FAILURE_CLOSURE_INCOMPLETE_REQUIRES_RECOVERY`; it never reports only the original
|
||||
candidate error while silently losing the closure failure. An incomplete closure preserves the lease for the
|
||||
existing evidence-bound session inspection and recovery path.
|
||||
|
||||
`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,
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ schemas, derived permissions, inference/reality-action boundaries, and the actua
|
|||
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 registered Tauri memory command executes the Git/filesystem transaction on the blocking task pool. A
|
||||
rejected candidate closes through a checked failure transaction that records the failure, organ release and
|
||||
dormancy before releasing the exact lease. If event persistence, session persistence, or lease release fails,
|
||||
the command returns an explicit recovery-required error and retains the lease whenever closure cannot be
|
||||
proven complete; it does not swallow the secondary failure.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1367,6 +1367,102 @@ fn finish_failed_session(
|
|||
let _ = fs::remove_file(lease_path);
|
||||
}
|
||||
|
||||
fn finish_failed_session_checked_with_release<F>(
|
||||
runtime_root: &Path,
|
||||
record: &mut PersonaSessionRecord,
|
||||
events: &mut Vec<PersonaLifecycleEvent>,
|
||||
lease_path: &Path,
|
||||
timestamp: &str,
|
||||
failure_kind: &str,
|
||||
release_lease: F,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
F: FnOnce(&Path) -> std::io::Result<()>,
|
||||
{
|
||||
let git_head = record.git_head.clone();
|
||||
let organ = record.active_organ.clone();
|
||||
append_event(
|
||||
runtime_root,
|
||||
record,
|
||||
events,
|
||||
failure_kind,
|
||||
&git_head,
|
||||
timestamp,
|
||||
Some(&organ),
|
||||
)?;
|
||||
append_event(
|
||||
runtime_root,
|
||||
record,
|
||||
events,
|
||||
"ORGAN_RELEASED",
|
||||
&git_head,
|
||||
timestamp,
|
||||
Some(&organ),
|
||||
)?;
|
||||
append_event(
|
||||
runtime_root,
|
||||
record,
|
||||
events,
|
||||
"DORMANT",
|
||||
&git_head,
|
||||
timestamp,
|
||||
None,
|
||||
)?;
|
||||
record.state = "DORMANT_AFTER_FAILURE".into();
|
||||
write_session_record(runtime_root, record)?;
|
||||
release_lease(lease_path)
|
||||
.map_err(|error| format!("PERSONA_PRIMARY_LEASE_RELEASE_FAILED: {error}"))
|
||||
}
|
||||
|
||||
fn memory_failure_error(
|
||||
runtime_root: &Path,
|
||||
record: &mut PersonaSessionRecord,
|
||||
events: &mut Vec<PersonaLifecycleEvent>,
|
||||
lease_path: &Path,
|
||||
timestamp: &str,
|
||||
failure_kind: &str,
|
||||
cause: String,
|
||||
) -> String {
|
||||
memory_failure_error_with_release(
|
||||
runtime_root,
|
||||
record,
|
||||
events,
|
||||
lease_path,
|
||||
timestamp,
|
||||
(failure_kind, cause),
|
||||
|path| fs::remove_file(path),
|
||||
)
|
||||
}
|
||||
|
||||
fn memory_failure_error_with_release<F>(
|
||||
runtime_root: &Path,
|
||||
record: &mut PersonaSessionRecord,
|
||||
events: &mut Vec<PersonaLifecycleEvent>,
|
||||
lease_path: &Path,
|
||||
timestamp: &str,
|
||||
failure: (&str, String),
|
||||
release_lease: F,
|
||||
) -> String
|
||||
where
|
||||
F: FnOnce(&Path) -> std::io::Result<()>,
|
||||
{
|
||||
let (failure_kind, cause) = failure;
|
||||
match finish_failed_session_checked_with_release(
|
||||
runtime_root,
|
||||
record,
|
||||
events,
|
||||
lease_path,
|
||||
timestamp,
|
||||
failure_kind,
|
||||
release_lease,
|
||||
) {
|
||||
Ok(()) => cause,
|
||||
Err(closure_error) => {
|
||||
format!("{cause}; MEMORY_FAILURE_CLOSURE_INCOMPLETE_REQUIRES_RECOVERY: {closure_error}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_recoverable_persona_commit(
|
||||
repository: &Path,
|
||||
record: &PersonaSessionRecord,
|
||||
|
|
@ -2066,18 +2162,18 @@ fn run_memory_metabolism_at(
|
|||
) {
|
||||
Ok(source) => source,
|
||||
Err(error) => {
|
||||
finish_failed_session(
|
||||
return Err(memory_failure_error(
|
||||
runtime_root,
|
||||
&mut record,
|
||||
&mut events,
|
||||
&lease_path,
|
||||
×tamp,
|
||||
"MEMORY_SOURCE_REJECTED",
|
||||
);
|
||||
return Err(error);
|
||||
error,
|
||||
));
|
||||
}
|
||||
};
|
||||
append_event(
|
||||
if let Err(error) = append_event(
|
||||
runtime_root,
|
||||
&record,
|
||||
&mut events,
|
||||
|
|
@ -2085,7 +2181,17 @@ fn run_memory_metabolism_at(
|
|||
&observed_head,
|
||||
×tamp,
|
||||
Some(&record.active_organ),
|
||||
)?;
|
||||
) {
|
||||
return Err(memory_failure_error(
|
||||
runtime_root,
|
||||
&mut record,
|
||||
&mut events,
|
||||
&lease_path,
|
||||
×tamp,
|
||||
"MEMORY_CANDIDATE_EVENT_FAILED",
|
||||
error,
|
||||
));
|
||||
}
|
||||
|
||||
let checkpoint_relative = format!(
|
||||
".hololake/persona/checkpoints/{}.json",
|
||||
|
|
@ -2093,15 +2199,15 @@ fn run_memory_metabolism_at(
|
|||
);
|
||||
let checkpoint_path = exact_repository.join(&checkpoint_relative);
|
||||
if checkpoint_path.exists() {
|
||||
finish_failed_session(
|
||||
return Err(memory_failure_error(
|
||||
runtime_root,
|
||||
&mut record,
|
||||
&mut events,
|
||||
&lease_path,
|
||||
×tamp,
|
||||
"CHECKPOINT_COLLISION",
|
||||
);
|
||||
return Err("PERSONA_CHECKPOINT_ALREADY_EXISTS".into());
|
||||
"PERSONA_CHECKPOINT_ALREADY_EXISTS".into(),
|
||||
));
|
||||
}
|
||||
let checkpoint = serde_json::json!({
|
||||
"schema": "hololake.persona-checkpoint/v1",
|
||||
|
|
@ -2118,8 +2224,20 @@ fn run_memory_metabolism_at(
|
|||
"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 original_manifest = match fs::read(&manifest_path) {
|
||||
Ok(manifest) => manifest,
|
||||
Err(error) => {
|
||||
return Err(memory_failure_error(
|
||||
runtime_root,
|
||||
&mut record,
|
||||
&mut events,
|
||||
&lease_path,
|
||||
×tamp,
|
||||
"MEMORY_MANIFEST_READ_FAILED",
|
||||
format!("PERSONA_MANIFEST_READ_FAILED: {error}"),
|
||||
));
|
||||
}
|
||||
};
|
||||
let mut promoted_manifest = manifest;
|
||||
promoted_manifest.current_checkpoint = checkpoint_relative.clone();
|
||||
let committed_head = match (|| {
|
||||
|
|
@ -2147,16 +2265,20 @@ fn run_memory_metabolism_at(
|
|||
&checkpoint_relative,
|
||||
&original_manifest,
|
||||
);
|
||||
finish_failed_session(
|
||||
if let Err(rollback_error) = rollback {
|
||||
return Err(format!(
|
||||
"{error}; MEMORY_CHECKPOINT_ROLLBACK_INCOMPLETE_REQUIRES_RECOVERY: {rollback_error}"
|
||||
));
|
||||
}
|
||||
return Err(memory_failure_error(
|
||||
runtime_root,
|
||||
&mut record,
|
||||
&mut events,
|
||||
&lease_path,
|
||||
×tamp,
|
||||
"CHECKPOINT_COMMIT_FAILED",
|
||||
);
|
||||
rollback?;
|
||||
return Err(error);
|
||||
error,
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -2277,12 +2399,16 @@ pub async fn run_persona_code_channel_fact_task(
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn run_persona_code_channel_memory_metabolism(
|
||||
pub async 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)
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true);
|
||||
run_memory_metabolism_at(&runtime_root, input, ×tamp)
|
||||
})
|
||||
.await
|
||||
.map_err(|error| format!("PERSONA_MEMORY_METABOLISM_JOIN_FAILED: {error}"))?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
|
@ -2789,9 +2915,67 @@ mod tests {
|
|||
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");
|
||||
let events = verify_event_journal(runtime.path(), &record).unwrap();
|
||||
let terminal_kinds = events
|
||||
.iter()
|
||||
.rev()
|
||||
.take(3)
|
||||
.map(|event| event.kind.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
terminal_kinds,
|
||||
vec!["DORMANT", "ORGAN_RELEASED", "MEMORY_SOURCE_REJECTED"]
|
||||
);
|
||||
require_clean_repository(repo.path()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_an_incomplete_memory_failure_closure_and_preserves_the_lease() {
|
||||
let repo = persona_repo();
|
||||
declare_memory_organ(repo.path());
|
||||
let runtime = tempfile::TempDir::new().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-MEMORY-CLOSURE",
|
||||
"2026-08-11T00:00:00.000Z",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut record = load_session_record(runtime.path(), "PNCC-MEMORY-CLOSURE").unwrap();
|
||||
let mut events = read_ready_event_journal(runtime.path(), &record).unwrap();
|
||||
let lease_path = runtime.path().join("leases/ICE-P-ZY001.json");
|
||||
let error = memory_failure_error_with_release(
|
||||
runtime.path(),
|
||||
&mut record,
|
||||
&mut events,
|
||||
&lease_path,
|
||||
"2026-08-11T00:00:01.000Z",
|
||||
("MEMORY_TEST_FAILURE", "MEMORY_TEST_CAUSE".into()),
|
||||
|_| {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::PermissionDenied,
|
||||
"injected lease release refusal",
|
||||
))
|
||||
},
|
||||
);
|
||||
assert!(error.contains("MEMORY_TEST_CAUSE"));
|
||||
assert!(error.contains("MEMORY_FAILURE_CLOSURE_INCOMPLETE_REQUIRES_RECOVERY"));
|
||||
assert!(error.contains("PERSONA_PRIMARY_LEASE_RELEASE_FAILED"));
|
||||
assert!(lease_path.is_file());
|
||||
assert_eq!(
|
||||
require_primary_lease(runtime.path(), &record).unwrap(),
|
||||
lease_path
|
||||
);
|
||||
let persisted = load_session_record(runtime.path(), "PNCC-MEMORY-CLOSURE").unwrap();
|
||||
assert_eq!(persisted.state, "DORMANT_AFTER_FAILURE");
|
||||
let persisted_events = verify_event_journal(runtime.path(), &persisted).unwrap();
|
||||
assert_eq!(persisted_events.last().unwrap().kind, "DORMANT");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_unscoped_model_evidence_and_releases_primary() {
|
||||
let repo = persona_repo();
|
||||
|
|
|
|||
Loading…
Reference in a new issue