From e9ee0a8bb15447584a799d23c88653fe3ee3a53a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=93=B8=E6=B8=8A=20/=20ICE-P-ZY001?= Date: Tue, 11 Aug 2026 04:51:20 +0800 Subject: [PATCH] fix(pncc): revalidate terminal success evidence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Human-Responsibility: ICE-GL∞ / 冰朔 Persona-Author: ICE-P-ZY001 / 铸渊 Execution-Runtime: Codex macOS Development-ID: DEV-20260810-014 Authorization-Scope: GH-PNCC local runtime and REPO-014 publication Source-Anchor: UI and execution limb deferred --- engineering/INDEX.md | 1 + ...c-success-receipt-terminal-revalidation.md | 32 +++ ...KE-PERSONA-NATIVE-CODE-CHANNEL-20260810.md | 5 + .../hololake-platform/docs/ABSTRACTIONS.md | 5 + .../hololake-platform/docs/ARCHITECTURE.md | 5 + .../src-tauri/src/persona_code_channel.rs | 196 +++++++++++++++--- routing/hololake-current-architecture.json | 2 +- .../hololake-persona-native-code-channel.json | 5 +- ...olake-persona-native-code-channel.test.mjs | 6 +- 9 files changed, 219 insertions(+), 38 deletions(-) create mode 100644 engineering/operations/2026-08-11-hololake-pncc-success-receipt-terminal-revalidation.md diff --git a/engineering/INDEX.md b/engineering/INDEX.md index aadfd30..2ff02dd 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-success-receipt-terminal-revalidation.md) | 本地源码、完整 Rust/路由测试与严格 clippy 已通过;GHNQG 和发布待验收 | | 2026-08-11 | GH-PNCC 重放仓库复核 | [幂等重放时重新验证人格 Git 状态](operations/2026-08-11-hololake-pncc-replay-repository-state-revalidation.md) | 本地源码、完整 Rust/路由测试与严格 clippy 已通过;GHNQG 和发布待验收 | | 2026-08-11 | GH-PNCC 终态失败重放 | [终态失败结构化回执与幂等重放](operations/2026-08-11-hololake-pncc-idempotent-terminal-failure-replay.md) | 已发布至 REPO-014 main abc7dbd;GHNQG、全新克隆与严格读回通过 | | 2026-08-11 | GH-PNCC 幂等中断恢复 | [不完整幂等请求检查与安全回执恢复](operations/2026-08-11-hololake-pncc-incomplete-idempotent-request-recovery.md) | 已发布至 REPO-014 main b8c3fcf;GHNQG、全新克隆与严格 fsck 通过 | diff --git a/engineering/operations/2026-08-11-hololake-pncc-success-receipt-terminal-revalidation.md b/engineering/operations/2026-08-11-hololake-pncc-success-receipt-terminal-revalidation.md new file mode 100644 index 0000000..029fa57 --- /dev/null +++ b/engineering/operations/2026-08-11-hololake-pncc-success-receipt-terminal-revalidation.md @@ -0,0 +1,32 @@ +# GH-PNCC successful receipt terminal evidence revalidation + +- Development ID: `DEV-20260810-014` +- Persona cognitive author: `ICE-P-ZY001 / 铸渊` +- Human responsibility subject: `ICE-GL∞ / 冰朔` +- Starting repository head: `b067c7bcaacc31cb69cb649518f69edce6932f9f` +- State: `LOCAL_SOURCE_IMPLEMENTED_FULLY_TESTED` + +## Corrected runtime fact + +An organ completion is not by itself sufficient authority to persist and return a current successful +lifecycle receipt. Immediately before the first `COMPLETED` receipt is written and bound, the kernel now +re-verifies the session event chain, final `DORMANT` event, released primary lease, canonical repository, +recorded Git head, clean worktree, and lifecycle receipt identity. + +If the persona repository advances or becomes dirty after organ completion, the command fails closed. It +does not persist `lifecycle-receipt.json`, bind the request fingerprint or receipt hash, or claim a current +successful lifecycle result from stale repository evidence. + +## Verification + +- PNCC focused Rust tests: `31 passed, 0 failed`. +- Full Rust suite: `1171 passed, 2 ignored`; integration test: `1 passed`. +- Routing suite: `29 passed, 0 failed`; `cargo fmt`, strict clippy, and diff checks passed. +- A completed lifecycle followed by an untracked change cannot persist or bind a successful receipt. +- A completed lifecycle followed by a new Git commit cannot persist or bind a successful receipt. +- GHNQG, publication, and fresh-clone readback remain pending for this stage. + +## Truth boundary + +- This revalidation does not reset, delete, or repair a changed persona repository. +- UI, human projection aesthetics, background scheduling, and `EXECUTION_LIMB` remain outside this stage. 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 f70dfc8..424d9f5 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 @@ -174,6 +174,7 @@ idempotent_lifecycle_request_and_receipt_replay_source_implemented: 100 incomplete_idempotent_request_inspection_and_safe_receipt_recovery_source_implemented: 100 idempotent_terminal_failure_receipt_and_replay_source_implemented: 100 idempotent_replay_repository_state_revalidation_source_implemented: 100 +successful_completion_receipt_terminal_revalidation_source_implemented: 100 general_purpose_persona_runtime_implemented: 0 human_live_projection_implemented: 0 hololake_integrated: 0 @@ -200,6 +201,10 @@ runtime_health: 0 生成时的状态。当前提交偏离会话记录或工作树变脏时,检查结果降级为人工复核,重放失败关闭, 且不会重新启动器官。 +首次成功回执落盘前也必须重新验证事件链、最终休眠事件、租约释放、规范仓库、会话记录的 +Git 提交、干净工作树与回执身份。器官已经完成不等于仍可签发当前成功回执;完成后仓库发生 +漂移时不落盘、不绑定并失败关闭。 + 人格器官现已拥有机器可读类型合同。系统能够在不唤醒人格、不取得主锁、不运行模型的情况下, 检查 `FACT_SENSE`、`MEMORY_METABOLISM` 与 `EXECUTION_LIMB` 的固定模式、输入输出 schema、派生权限、 模型推理边界、现实动作边界和真实可激活状态。只读事实感官与独立记忆代谢器官可激活;后者不 diff --git a/product-source/hololake-platform/docs/ABSTRACTIONS.md b/product-source/hololake-platform/docs/ABSTRACTIONS.md index bca5463..676352c 100644 --- a/product-source/hololake-platform/docs/ABSTRACTIONS.md +++ b/product-source/hololake-platform/docs/ABSTRACTIONS.md @@ -114,6 +114,11 @@ Repository state is revalidated at replay time for both successful and failed re repository must still be clean and its head must equal the session record; otherwise inspection returns `MANUAL_REVIEW_REQUIRED` and ordinary replay fails without invoking the organ. +The first successful receipt is also conditional on freshly verified terminal evidence. Before persisting +and binding `COMPLETED`, the coordinator checks the event chain, final dormancy, released lease, canonical +repository, recorded head, clean worktree, and receipt identity. A completed organ is not enough to issue a +current lifecycle receipt after repository drift. + `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, and returns at most 100 newest session summaries. Dormant sessions expose no active organ. The receipt keeps diff --git a/product-source/hololake-platform/docs/ARCHITECTURE.md b/product-source/hololake-platform/docs/ARCHITECTURE.md index 82ee32f..d81da46 100644 --- a/product-source/hololake-platform/docs/ARCHITECTURE.md +++ b/product-source/hololake-platform/docs/ARCHITECTURE.md @@ -86,6 +86,11 @@ not only when the receipt is first persisted. If the current head differs from t or the worktree has become dirty, replay fails closed and request inspection reports manual review instead of restarting the organ or treating stale evidence as current. +Successful receipt persistence now applies the same terminal-evidence boundary before the first receipt is +written. The kernel re-verifies the event journal and final `DORMANT` event, released lease, canonical +repository, recorded Git head, clean worktree, and lifecycle identity. Repository drift after organ +completion therefore cannot be bound or returned as a current `COMPLETED` command result. + `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 existing session records and event journals directly, verifies every returned hash chain, sorts by the last 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 9d16cd6..e780761 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 @@ -2978,6 +2978,70 @@ fn persist_terminal_lifecycle_failure( })) } +fn persist_successful_lifecycle_receipt( + runtime_root: &Path, + request_id: &str, + request_fingerprint: &str, + session_id: &str, + repository: &Path, + lifecycle: serde_json::Value, +) -> Result { + let lifecycle_bytes = serde_json::to_vec(&lifecycle) + .map_err(|error| format!("PERSONA_LIFECYCLE_RECEIPT_HASH_INPUT_FAILED: {error}"))?; + let lifecycle_receipt_hash = hex_digest(&lifecycle_bytes); + let mut record = load_session_record(runtime_root, session_id)?; + let recorded_repository = Path::new(&record.repository_path) + .canonicalize() + .map_err(|error| format!("PERSONA_REPOSITORY_UNAVAILABLE: {error}"))?; + if recorded_repository != repository { + return Err("PERSONA_LIFECYCLE_REQUEST_SESSION_IDENTITY_MISMATCH".into()); + } + let events = verify_event_journal(runtime_root, &record)?; + if record.state != "DORMANT" + || events.last().map(|event| event.kind.as_str()) != Some("DORMANT") + || primary_lease_held_by_session(runtime_root, &record)? + { + return Err("PERSONA_LIFECYCLE_REQUEST_INCOMPLETE_REQUIRES_RECOVERY".into()); + } + let (_, observed_head) = exact_repository(repository)?; + if observed_head != record.git_head { + return Err("PERSONA_LIFECYCLE_COMPLETION_GIT_HEAD_MISMATCH".into()); + } + require_clean_repository(repository)?; + let persisted = PersistedPersonaLifecycleReceipt { + schema: "hololake.pncc-persisted-lifecycle-receipt/v1".into(), + request_id: request_id.into(), + request_fingerprint: request_fingerprint.into(), + lifecycle_receipt_hash: lifecycle_receipt_hash.clone(), + outcome: "COMPLETED".into(), + lifecycle: lifecycle.clone(), + failure: None, + }; + if !persisted_lifecycle_identity_matches(&persisted, session_id, &record.persona_id) { + return Err("PERSONA_LIFECYCLE_RECEIPT_IDENTITY_MISMATCH".into()); + } + // Persist the immutable full receipt before binding it into the mutable session record. A + // crash between these atomic writes leaves a verifiable, explicitly recoverable state. + write_json_file( + &session_directory(runtime_root, session_id)?.join("lifecycle-receipt.json"), + &persisted, + "PERSONA_LIFECYCLE_RECEIPT", + )?; + record.request_id = Some(request_id.into()); + record.request_fingerprint = Some(request_fingerprint.into()); + record.lifecycle_receipt_hash = Some(lifecycle_receipt_hash); + write_session_record(runtime_root, &record)?; + Ok(PersonaLifecycleCommandReceipt { + schema: "hololake.pncc-lifecycle-command-receipt/v1", + request_id: request_id.into(), + request_fingerprint: request_fingerprint.into(), + replayed: false, + outcome: "COMPLETED", + lifecycle, + failure: None, + }) +} + fn run_idempotent_lifecycle_at( runtime_root: &Path, input: PersonaLifecycleRunInput, @@ -3024,41 +3088,14 @@ where }; let lifecycle = serde_json::to_value(lifecycle) .map_err(|error| format!("PERSONA_LIFECYCLE_RECEIPT_SERIALIZATION_FAILED: {error}"))?; - let lifecycle_bytes = serde_json::to_vec(&lifecycle) - .map_err(|error| format!("PERSONA_LIFECYCLE_RECEIPT_HASH_INPUT_FAILED: {error}"))?; - let lifecycle_receipt_hash = hex_digest(&lifecycle_bytes); - let mut record = load_session_record(runtime_root, &session_id)?; - if record.state != "DORMANT" || primary_lease_held_by_session(runtime_root, &record)? { - return Err("PERSONA_LIFECYCLE_REQUEST_INCOMPLETE_REQUIRES_RECOVERY".into()); - } - // Persist the immutable full receipt before binding it into the mutable session record. A - // crash between these atomic writes leaves a verifiable, explicitly recoverable state. - write_json_file( - &session_directory(runtime_root, &session_id)?.join("lifecycle-receipt.json"), - &PersistedPersonaLifecycleReceipt { - schema: "hololake.pncc-persisted-lifecycle-receipt/v1".into(), - request_id: request_id.clone(), - request_fingerprint: request_fingerprint.clone(), - lifecycle_receipt_hash: lifecycle_receipt_hash.clone(), - outcome: "COMPLETED".into(), - lifecycle: lifecycle.clone(), - failure: None, - }, - "PERSONA_LIFECYCLE_RECEIPT", - )?; - record.request_id = Some(request_id.clone()); - record.request_fingerprint = Some(request_fingerprint.clone()); - record.lifecycle_receipt_hash = Some(lifecycle_receipt_hash); - write_session_record(runtime_root, &record)?; - Ok(PersonaLifecycleCommandReceipt { - schema: "hololake.pncc-lifecycle-command-receipt/v1", - request_id, - request_fingerprint, - replayed: false, - outcome: "COMPLETED", + persist_successful_lifecycle_receipt( + runtime_root, + &request_id, + &request_fingerprint, + &session_id, + &repository, lifecycle, - failure: None, - }) + ) } #[tauri::command] @@ -3750,6 +3787,97 @@ mod tests { assert!(error.contains("PERSONA_REPOSITORY_DIRTY")); } + #[test] + fn refuses_to_persist_success_when_the_repository_becomes_dirty_after_completion() { + let repo = persona_repo(); + let runtime = tempfile::TempDir::new().unwrap(); + let input = lifecycle_fact_input(repo.path()); + let (request_id, request_fingerprint, session_id, repository) = + lifecycle_request_identity(&input).unwrap(); + let lifecycle = run_lifecycle_at( + runtime.path(), + input, + &session_id, + "2026-08-11T00:00:00.000Z", + "2026-08-11T00:00:01.000Z", + |runtime_root, input, timestamp| { + run_fact_task_at(runtime_root, input, timestamp, |_, _| { + Ok(r#"{"summary":"Completed before repository drift.","facts":[{"statement":"The brain exists.","evidencePaths":["brain/CORE.hdlp"]}],"limitations":[]}"#.into()) + }) + }, + ) + .unwrap(); + fs::write(repo.path().join("dirty-before-receipt.txt"), "dirty\n").unwrap(); + + let error = persist_successful_lifecycle_receipt( + runtime.path(), + &request_id, + &request_fingerprint, + &session_id, + &repository, + serde_json::to_value(lifecycle).unwrap(), + ) + .unwrap_err(); + assert!(error.contains("PERSONA_REPOSITORY_DIRTY")); + assert!(!session_directory(runtime.path(), &session_id) + .unwrap() + .join("lifecycle-receipt.json") + .exists()); + let record = load_session_record(runtime.path(), &session_id).unwrap(); + assert!(record.request_id.is_none()); + assert!(record.lifecycle_receipt_hash.is_none()); + } + + #[test] + fn refuses_to_persist_success_when_the_repository_head_advances_after_completion() { + let repo = persona_repo(); + let runtime = tempfile::TempDir::new().unwrap(); + let input = lifecycle_fact_input(repo.path()); + let (request_id, request_fingerprint, session_id, repository) = + lifecycle_request_identity(&input).unwrap(); + let lifecycle = run_lifecycle_at( + runtime.path(), + input, + &session_id, + "2026-08-11T00:00:00.000Z", + "2026-08-11T00:00:01.000Z", + |runtime_root, input, timestamp| { + run_fact_task_at(runtime_root, input, timestamp, |_, _| { + Ok(r#"{"summary":"Completed before a new commit.","facts":[{"statement":"The brain exists.","evidencePaths":["brain/CORE.hdlp"]}],"limitations":[]}"#.into()) + }) + }, + ) + .unwrap(); + fs::write( + repo.path().join("advanced-before-receipt.txt"), + "new head\n", + ) + .unwrap(); + run_git(repo.path(), &["add", "advanced-before-receipt.txt"]); + run_git( + repo.path(), + &["commit", "-m", "advance before lifecycle receipt"], + ); + + let error = persist_successful_lifecycle_receipt( + runtime.path(), + &request_id, + &request_fingerprint, + &session_id, + &repository, + serde_json::to_value(lifecycle).unwrap(), + ) + .unwrap_err(); + assert!(error.contains("PERSONA_LIFECYCLE_COMPLETION_GIT_HEAD_MISMATCH")); + assert!(!session_directory(runtime.path(), &session_id) + .unwrap() + .join("lifecycle-receipt.json") + .exists()); + let record = load_session_record(runtime.path(), &session_id).unwrap(); + assert!(record.request_id.is_none()); + assert!(record.lifecycle_receipt_hash.is_none()); + } + #[test] fn rejects_reusing_a_lifecycle_request_id_for_a_different_operation() { let repo = persona_repo(); diff --git a/routing/hololake-current-architecture.json b/routing/hololake-current-architecture.json index a5ad9a1..b932591 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_INDEPENDENT_VERIFIED_MEMORY_METABOLISM_CHECKED_FAILURE_CLOSURE_NONBLOCKING_RUNTIME_COMMAND_SAFE_ORGAN_LIFECYCLE_COORDINATOR_IDEMPOTENT_SUCCESS_AND_TERMINAL_FAILURE_RECEIPT_REPLAY_SAFE_RECEIPT_BINDING_RECOVERY_AND_REPLAY_TIME_REPOSITORY_STATE_REVALIDATION_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_NONBLOCKING_RUNTIME_COMMAND_SAFE_ORGAN_LIFECYCLE_COORDINATOR_IDEMPOTENT_SUCCESS_AND_TERMINAL_FAILURE_RECEIPT_REPLAY_SAFE_RECEIPT_BINDING_RECOVERY_REPLAY_TIME_REPOSITORY_STATE_REVALIDATION_AND_SUCCESS_RECEIPT_TERMINAL_REVALIDATION_SOURCE_IMPLEMENTED_AND_TESTED", "desktop_integrated": false, "development_id": "DEV-20260810-014" }, diff --git a/routing/hololake-persona-native-code-channel.json b/routing/hololake-persona-native-code-channel.json index 0f5a682..0735eea 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.9", - "state": "CURRENT_FIRST_PRODUCT_CORE_IDEMPOTENT_REPLAY_REPOSITORY_STATE_REVALIDATION_SOURCE_IMPLEMENTED", + "version": "2026-08-11.10", + "state": "CURRENT_FIRST_PRODUCT_CORE_SUCCESS_RECEIPT_TERMINAL_REVALIDATION_SOURCE_IMPLEMENTED", "development_id": "DEV-20260810-014", "product": { "formal_name_zh": "光湖人格原生代码频道", @@ -111,6 +111,7 @@ "incomplete_idempotent_request_inspection_and_safe_receipt_recovery_source_implemented": 100, "idempotent_terminal_failure_receipt_and_replay_source_implemented": 100, "idempotent_replay_repository_state_revalidation_source_implemented": 100, + "successful_completion_receipt_terminal_revalidation_source_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 44b0923..a06889f 100644 --- a/routing/hololake-persona-native-code-channel.test.mjs +++ b/routing/hololake-persona-native-code-channel.test.mjs @@ -106,12 +106,16 @@ test("the first source runtime cycle stays distinct from integration and deploym channel.truth.idempotent_replay_repository_state_revalidation_source_implemented, 100, ); + assert.equal( + channel.truth.successful_completion_receipt_terminal_revalidation_source_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_INDEPENDENT_VERIFIED_MEMORY_METABOLISM_CHECKED_FAILURE_CLOSURE_NONBLOCKING_RUNTIME_COMMAND_SAFE_ORGAN_LIFECYCLE_COORDINATOR_IDEMPOTENT_SUCCESS_AND_TERMINAL_FAILURE_RECEIPT_REPLAY_SAFE_RECEIPT_BINDING_RECOVERY_AND_REPLAY_TIME_REPOSITORY_STATE_REVALIDATION_SOURCE_IMPLEMENTED_AND_TESTED", + "READ_ONLY_FACT_CYCLE_FAIL_CLOSED_RECOVERY_TYPED_ORGANS_DURABLE_VERIFIED_SESSION_QUERY_INDEPENDENT_VERIFIED_MEMORY_METABOLISM_CHECKED_FAILURE_CLOSURE_NONBLOCKING_RUNTIME_COMMAND_SAFE_ORGAN_LIFECYCLE_COORDINATOR_IDEMPOTENT_SUCCESS_AND_TERMINAL_FAILURE_RECEIPT_REPLAY_SAFE_RECEIPT_BINDING_RECOVERY_REPLAY_TIME_REPOSITORY_STATE_REVALIDATION_AND_SUCCESS_RECEIPT_TERMINAL_REVALIDATION_SOURCE_IMPLEMENTED_AND_TESTED", ); assert.equal(channel.truth.hololake_integrated, 0); assert.equal(channel.truth.artifact_built, 0);