diff --git a/engineering/INDEX.md b/engineering/INDEX.md index 1734027..dac0865 100644 --- a/engineering/INDEX.md +++ b/engineering/INDEX.md @@ -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;仓库发布、桌面集成与部署待验收 | diff --git a/engineering/operations/2026-08-11-hololake-pncc-memory-failure-closure-runtime-command.md b/engineering/operations/2026-08-11-hololake-pncc-memory-failure-closure-runtime-command.md new file mode 100644 index 0000000..bd4fcd0 --- /dev/null +++ b/engineering/operations/2026-08-11-hololake-pncc-memory-failure-closure-runtime-command.md @@ -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. diff --git a/product-source/hololake-platform/architecture/HOLOLAKE-PERSONA-NATIVE-CODE-CHANNEL-20260810.md b/product-source/hololake-platform/architecture/HOLOLAKE-PERSONA-NATIVE-CODE-CHANNEL-20260810.md index e5cfd1a..12d85cb 100644 --- a/product-source/hololake-platform/architecture/HOLOLAKE-PERSONA-NATIVE-CODE-CHANNEL-20260810.md +++ b/product-source/hololake-platform/architecture/HOLOLAKE-PERSONA-NATIVE-CODE-CHANNEL-20260810.md @@ -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 头、节点、模型实例、活跃器官、事件数量、链头和 双层归因。该查询不复制第二套数据库、不启动模型、不取得主锁;匹配会话的事件链损坏时失败关闭。 diff --git a/product-source/hololake-platform/docs/ABSTRACTIONS.md b/product-source/hololake-platform/docs/ABSTRACTIONS.md index ce9658b..e07b801 100644 --- a/product-source/hololake-platform/docs/ABSTRACTIONS.md +++ b/product-source/hololake-platform/docs/ABSTRACTIONS.md @@ -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, diff --git a/product-source/hololake-platform/docs/ARCHITECTURE.md b/product-source/hololake-platform/docs/ARCHITECTURE.md index 740d6eb..88b265f 100644 --- a/product-source/hololake-platform/docs/ARCHITECTURE.md +++ b/product-source/hololake-platform/docs/ARCHITECTURE.md @@ -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 diff --git a/product-source/hololake-platform/src-tauri/src/persona_code_channel.rs b/product-source/hololake-platform/src-tauri/src/persona_code_channel.rs index 4f45661..0133761 100644 --- a/product-source/hololake-platform/src-tauri/src/persona_code_channel.rs +++ b/product-source/hololake-platform/src-tauri/src/persona_code_channel.rs @@ -1367,6 +1367,102 @@ fn finish_failed_session( let _ = fs::remove_file(lease_path); } +fn finish_failed_session_checked_with_release( + runtime_root: &Path, + record: &mut PersonaSessionRecord, + events: &mut Vec, + 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, + 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( + runtime_root: &Path, + record: &mut PersonaSessionRecord, + events: &mut Vec, + 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 { 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::>(); + 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(); diff --git a/routing/hololake-current-architecture.json b/routing/hololake-current-architecture.json index 169e8c9..94e33bf 100644 --- a/routing/hololake-current-architecture.json +++ b/routing/hololake-current-architecture.json @@ -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" diff --git a/routing/hololake-persona-native-code-channel.json b/routing/hololake-persona-native-code-channel.json index d853825..3ebed62 100644 --- a/routing/hololake-persona-native-code-channel.json +++ b/routing/hololake-persona-native-code-channel.json @@ -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, diff --git a/routing/hololake-persona-native-code-channel.test.mjs b/routing/hololake-persona-native-code-channel.test.mjs index 7652104..9ed6248 100644 --- a/routing/hololake-persona-native-code-channel.test.mjs +++ b/routing/hololake-persona-native-code-channel.test.mjs @@ -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);