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:
铸渊 / ICE-P-ZY001 2026-08-11 02:38:28 +08:00
commit 18944f5362
9 changed files with 270 additions and 22 deletions

View file

@ -32,6 +32,7 @@ Windows / macOS / Linux 构建机与安装包
| 时间 | 版本 | 记录 | 状态 |
| --- | --- | --- | --- |
| 2026-08-11 | GH-PNCC 记忆失败闭环 | [记忆代谢失败闭环与运行时命令接入](operations/2026-08-11-hololake-pncc-memory-failure-closure-runtime-command.md) | 本地源码、完整 Rust/路由测试与 clippy 已通过GHNQG、提交与发布待验收 |
| 2026-08-11 | GH-PNCC 运行查询 | [人格持久事件与回执有界查询](operations/2026-08-11-hololake-pncc-durable-runtime-query.md) | 本地源码、完整 Rust/路由测试与 clippy 已通过GHNQG 和发布待验收 |
| 2026-08-11 | GH-PNCC 器官合同 | [人格器官机器合同与清单检查](operations/2026-08-11-hololake-pncc-typed-organ-contract.md) | 本地源码、完整 Rust/路由测试与 clippy 已通过GHNQG 和发布待验收 |
| 2026-08-11 | GH-PNCC 中断恢复 | [人格代码频道中断会话检查与安全恢复](operations/2026-08-11-hololake-pncc-interrupted-session-recovery.md) | 实现提交已通过 GHNQG仓库发布、桌面集成与部署待验收 |

View file

@ -0,0 +1,41 @@
# GH-PNCC memory failure closure and runtime command integration
- Development ID: `DEV-20260810-014`
- Persona cognitive author: `ICE-P-ZY001 / 铸渊`
- Human responsibility subject: `ICE-GL∞ / 冰朔`
- Starting repository head: `962ea26db7044f7f17fafd55a054b0214b5e37cf`
- State: `LOCAL_SOURCE_IMPLEMENTED_FOCUSED_TESTED`
## Implemented facts
The registered `run_persona_code_channel_memory_metabolism` command now executes its filesystem and Git work
inside Tokio's blocking task pool. This keeps the native desktop command runtime responsive without adding a
renderer, UI workflow, execution limb, shell permission, or second truth store.
Memory rejection and pre-commit failure paths now use a checked closure. A complete closure appends the
specific failure event, `ORGAN_RELEASED`, and `DORMANT`, persists `DORMANT_AFTER_FAILURE`, and removes the
exact primary lease. If event persistence, session persistence, or lease removal fails, the returned error
includes `MEMORY_FAILURE_CLOSURE_INCOMPLETE_REQUIRES_RECOVERY`. The runtime therefore cannot hide a closure
failure behind the original candidate error. When completion cannot be proven, the lease remains available to
the existing evidence-bound inspection and recovery path.
Checkpoint rollback also precedes failure dormancy. If rollback itself fails, the session retains its lease
and returns `MEMORY_CHECKPOINT_ROLLBACK_INCOMPLETE_REQUIRES_RECOVERY` instead of claiming a clean failure.
## Current verification
- PNCC focused Rust tests: `17 passed, 0 failed`.
- Full Rust suite: `1157 passed, 0 failed, 2 ignored`; integration test: `1 passed`.
- HoloLake architecture routing: `29 passed, 0 failed`.
- Rust clippy for all targets: `PASS` with `-D warnings`.
- The corrupted-source case proves the durable terminal chain
`MEMORY_SOURCE_REJECTED → ORGAN_RELEASED → DORMANT` and exact lease release.
- The incomplete-closure case forces lease-removal failure, proves the error is surfaced, and proves the
unresolved lease is preserved rather than silently discarded.
## Truth boundary and next minimum
- GHNQG, commit, publication, and fresh-clone readback remain pending for this stage.
- UI, packaged desktop, installation, deployment, online health, and the execution limb remain `0`.
- The next source stage is a non-UI lifecycle coordinator for already registered safe organs; it must not make
`EXECUTION_LIMB` activatable or create a second runtime authority.

View file

@ -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 头、节点、模型实例、活跃器官、事件数量、链头和
双层归因。该查询不复制第二套数据库、不启动模型、不取得主锁;匹配会话的事件链损坏时失败关闭。

View file

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

View file

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

View file

@ -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,
&timestamp,
"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,
&timestamp,
Some(&record.active_organ),
)?;
) {
return Err(memory_failure_error(
runtime_root,
&mut record,
&mut events,
&lease_path,
&timestamp,
"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,
&timestamp,
"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,
&timestamp,
"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,
&timestamp,
"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, &timestamp)
tokio::task::spawn_blocking(move || {
let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true);
run_memory_metabolism_at(&runtime_root, input, &timestamp)
})
.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();

View file

@ -118,7 +118,7 @@
"human_projection": "HOLOLAKE_LIVE_READ_MODEL",
"forgejo_role": "OPTIONAL_COMPATIBILITY_COLLABORATION_ADAPTER",
"runtime_implemented": true,
"runtime_scope": "READ_ONLY_FACT_CYCLE_FAIL_CLOSED_RECOVERY_TYPED_ORGANS_DURABLE_VERIFIED_SESSION_QUERY_AND_INDEPENDENT_VERIFIED_MEMORY_METABOLISM_SOURCE_IMPLEMENTED_AND_TESTED",
"runtime_scope": "READ_ONLY_FACT_CYCLE_FAIL_CLOSED_RECOVERY_TYPED_ORGANS_DURABLE_VERIFIED_SESSION_QUERY_INDEPENDENT_VERIFIED_MEMORY_METABOLISM_CHECKED_FAILURE_CLOSURE_AND_NONBLOCKING_RUNTIME_COMMAND_SOURCE_IMPLEMENTED_AND_TESTED",
"desktop_integrated": false,
"development_id": "DEV-20260810-014"
},
@ -343,7 +343,7 @@
"DEV-20260810-013"
],
"closeout_record": "HLP-DEV-20260809-007-CLOSEOUT-001",
"next_minimum_stage": "GH_PNCC_MEMORY_FAILURE_CLOSURE_AND_RUNTIME_COMMAND_INTEGRATION_WITHOUT_UI_OR_EXECUTION_LIMB",
"next_minimum_stage": "GH_PNCC_NON_UI_LIFECYCLE_COORDINATOR_FOR_REGISTERED_SAFE_ORGANS_WITHOUT_EXECUTION_LIMB",
"next_stage_started": true,
"heartbeat_automation": "pncc",
"heartbeat_state": "ACTIVE_EVERY_10_MINUTES_UNTIL_TASK_TERMINAL"

View file

@ -1,8 +1,8 @@
{
"schema": "hololake.persona-native-code-channel/v1",
"record_id": "HLP-PERSONA-NATIVE-CODE-CHANNEL-001",
"version": "2026-08-11.3",
"state": "CURRENT_FIRST_PRODUCT_CORE_FACT_AND_VERIFIED_MEMORY_RUNTIME_SOURCE_IMPLEMENTED",
"version": "2026-08-11.4",
"state": "CURRENT_FIRST_PRODUCT_CORE_FACT_AND_FAIL_CLOSED_VERIFIED_MEMORY_RUNTIME_SOURCE_IMPLEMENTED",
"development_id": "DEV-20260810-014",
"product": {
"formal_name_zh": "光湖人格原生代码频道",
@ -105,6 +105,7 @@
"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,

View file

@ -82,12 +82,16 @@ test("the first source runtime cycle stays distinct from integration and deploym
100,
);
assert.equal(channel.truth.independent_memory_metabolism_source_implemented, 100);
assert.equal(
channel.truth.memory_failure_closure_and_nonblocking_runtime_command_implemented,
100,
);
assert.equal(channel.truth.general_purpose_persona_runtime_implemented, 0);
assert.equal(channel.truth.human_live_projection_implemented, 0);
assert.equal(architecture.persona_native_code_channel.runtime_implemented, true);
assert.equal(
architecture.persona_native_code_channel.runtime_scope,
"READ_ONLY_FACT_CYCLE_FAIL_CLOSED_RECOVERY_TYPED_ORGANS_DURABLE_VERIFIED_SESSION_QUERY_AND_INDEPENDENT_VERIFIED_MEMORY_METABOLISM_SOURCE_IMPLEMENTED_AND_TESTED",
"READ_ONLY_FACT_CYCLE_FAIL_CLOSED_RECOVERY_TYPED_ORGANS_DURABLE_VERIFIED_SESSION_QUERY_INDEPENDENT_VERIFIED_MEMORY_METABOLISM_CHECKED_FAILURE_CLOSURE_AND_NONBLOCKING_RUNTIME_COMMAND_SOURCE_IMPLEMENTED_AND_TESTED",
);
assert.equal(channel.truth.hololake_integrated, 0);
assert.equal(channel.truth.artifact_built, 0);