From 21d120d7b5b929ad639f8035c15dd393b4be4d08 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 05:19:28 +0800 Subject: [PATCH] fix(pncc): revalidate receipt binding 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 desktop / DEV-20260810-014 Development-ID: DEV-20260810-014 Authorization-Scope: GH-PNCC persona runtime source and tests only; no UI, deployment, or execution limb Source-Anchor: user instruction to continue GH-PNCC from repository facts and verifiable receipts --- engineering/INDEX.md | 1 + ...e-receipt-binding-terminal-revalidation.md | 34 ++++ ...KE-PERSONA-NATIVE-CODE-CHANNEL-20260810.md | 1 + .../hololake-platform/docs/ARCHITECTURE.md | 6 + .../src-tauri/src/persona_code_channel.rs | 157 +++++++++++++++++- routing/hololake-current-architecture.json | 2 +- .../hololake-persona-native-code-channel.json | 5 +- ...olake-persona-native-code-channel.test.mjs | 6 +- 8 files changed, 199 insertions(+), 13 deletions(-) create mode 100644 engineering/operations/2026-08-11-hololake-pncc-safe-receipt-binding-terminal-revalidation.md diff --git a/engineering/INDEX.md b/engineering/INDEX.md index 2ff02dd..045f4bb 100644 --- a/engineering/INDEX.md +++ b/engineering/INDEX.md @@ -32,6 +32,7 @@ Windows / macOS / Linux 构建机与安装包 | 时间 | 版本 | 记录 | 状态 | | --- | --- | --- | --- | +| 2026-08-11 | GH-PNCC 安全回执绑定终态复核 | [检查后、绑定前重新验证回执与人格 Git 终态](operations/2026-08-11-hololake-pncc-safe-receipt-binding-terminal-revalidation.md) | 本地源码、完整 Rust/路由测试与严格 clippy 已通过;GHNQG 和发布待验收 | | 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、全新克隆与严格读回通过 | diff --git a/engineering/operations/2026-08-11-hololake-pncc-safe-receipt-binding-terminal-revalidation.md b/engineering/operations/2026-08-11-hololake-pncc-safe-receipt-binding-terminal-revalidation.md new file mode 100644 index 0000000..1e674e3 --- /dev/null +++ b/engineering/operations/2026-08-11-hololake-pncc-safe-receipt-binding-terminal-revalidation.md @@ -0,0 +1,34 @@ +# GH-PNCC safe receipt-binding terminal revalidation + +- Development ID: `DEV-20260810-014` +- Persona cognitive author: `ICE-P-ZY001 / 铸渊` +- Human responsibility subject: `ICE-GL∞ / 冰朔` +- Starting repository head: `e9ee0a8bb15447584a799d23c88653fe3ee3a53a` +- State: `LOCAL_SOURCE_IMPLEMENTED_FULLY_TESTED` + +## Corrected runtime fact + +A successful lifecycle-request inspection is a point-in-time observation, not an enduring authorization to +mutate the session record. Before an interrupted persisted receipt is bound, the kernel now re-derives the +request identity and re-reads the receipt. It verifies the receipt schema, request id, semantic fingerprint, +payload hash, outcome identity, persona and canonical repository again. + +The binding transaction also re-verifies the event chain, final `DORMANT` event, matching dormant state, +released primary lease, recorded Git head and clean worktree. Only after all evidence still agrees are the +request id, request fingerprint and lifecycle receipt hash written into the session record. + +## Verification + +- PNCC focused Rust tests: `33 passed, 0 failed`. +- Full Rust suite: `1173 passed, 2 ignored`; integration test: `1 passed`. +- Routing suite: `29 passed, 0 failed`; `cargo fmt`, strict clippy, diff checks and native authority passed. +- A receipt changed after safe inspection is rejected by its payload hash before session binding. +- A persona Git advanced after safe inspection is rejected before session binding. +- Both rejection paths leave request id, request fingerprint and lifecycle receipt hash unbound. +- GHNQG, publication and fresh-clone readback remain pending. + +## Truth boundary + +- This stage hardens recovery; it does not add a new truth store or a second writer. +- It does not repair a changed receipt or repository and does not activate `EXECUTION_LIMB`. +- UI, human projection aesthetics, desktop integration, artifacts, deployment and runtime health remain out of scope. 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 424d9f5..9e9b8b5 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 @@ -175,6 +175,7 @@ incomplete_idempotent_request_inspection_and_safe_receipt_recovery_source_implem 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 +safe_receipt_binding_terminal_revalidation_source_implemented: 100 general_purpose_persona_runtime_implemented: 0 human_live_projection_implemented: 0 hololake_integrated: 0 diff --git a/product-source/hololake-platform/docs/ARCHITECTURE.md b/product-source/hololake-platform/docs/ARCHITECTURE.md index d81da46..3a60aa9 100644 --- a/product-source/hololake-platform/docs/ARCHITECTURE.md +++ b/product-source/hololake-platform/docs/ARCHITECTURE.md @@ -37,6 +37,12 @@ interruption at the recorded head or a single persona-attributed checkpoint comm checkpoint identity and commit trailers all match the session. Dirty repositories, unknown Git advances, broken journals and attribution mismatches retain the lease and require manual review. +Idempotent lifecycle receipt recovery treats inspection as evidence, not as a lasting write permit. Immediately +before binding an interrupted persisted receipt, the kernel re-reads and verifies its schema, request identity, +semantic fingerprint, payload hash and persona identity, then re-checks the terminal event chain, dormant state, +released lease, canonical repository, recorded Git head and clean work tree. A receipt or repository changed +after inspection fails closed before any request or receipt-hash binding is written into the session record. + 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. The fact 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 e780761..eccce15 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 @@ -2873,30 +2873,83 @@ fn recover_lifecycle_request_at( inspection.status )); } - let receipt_path = - session_directory(runtime_root, &inspection.session_id)?.join("lifecycle-receipt.json"); + bind_persisted_lifecycle_receipt_at(runtime_root, input, &inspection) +} + +fn bind_persisted_lifecycle_receipt_at( + runtime_root: &Path, + input: &PersonaLifecycleRunInput, + inspection: &PersonaLifecycleRequestInspectionReceipt, +) -> Result { + let (request_id, request_fingerprint, session_id, repository) = + lifecycle_request_identity(input)?; + if !inspection.safe_to_bind_receipt + || inspection.request_id != request_id + || inspection.request_fingerprint != request_fingerprint + || inspection.session_id != session_id + { + return Err("PERSONA_LIFECYCLE_RECEIPT_BINDING_CHANGED_REQUIRES_REINSPECTION".into()); + } + let receipt_path = session_directory(runtime_root, &session_id)?.join("lifecycle-receipt.json"); let persisted: PersistedPersonaLifecycleReceipt = serde_json::from_slice( &fs::read(&receipt_path) .map_err(|error| format!("PERSONA_LIFECYCLE_RECEIPT_READ_FAILED: {error}"))?, ) .map_err(|error| format!("PERSONA_LIFECYCLE_RECEIPT_INVALID: {error}"))?; - let mut record = load_session_record(runtime_root, &inspection.session_id)?; + if persisted.schema != "hololake.pncc-persisted-lifecycle-receipt/v1" + || persisted.request_id != request_id + { + return Err("PERSONA_LIFECYCLE_REQUEST_ID_MISMATCH".into()); + } + if persisted.request_fingerprint != request_fingerprint { + return Err("PERSONA_LIFECYCLE_REQUEST_CONFLICT".into()); + } + if hex_digest(&persisted_lifecycle_payload_bytes(&persisted)?) + != persisted.lifecycle_receipt_hash + { + return Err("PERSONA_LIFECYCLE_RECEIPT_HASH_MISMATCH".into()); + } + 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 || record.persona_id != input.wake.expected_persona_id { + return Err("PERSONA_LIFECYCLE_REQUEST_SESSION_IDENTITY_MISMATCH".into()); + } + if !persisted_lifecycle_identity_matches(&persisted, &session_id, &record.persona_id) { + return Err("PERSONA_LIFECYCLE_RECEIPT_IDENTITY_MISMATCH".into()); + } if record.request_id.is_some() || record.request_fingerprint.is_some() || record.lifecycle_receipt_hash.is_some() { return Err("PERSONA_LIFECYCLE_RECEIPT_BINDING_CHANGED_REQUIRES_REINSPECTION".into()); } - record.request_id = Some(inspection.request_id.clone()); - record.request_fingerprint = Some(inspection.request_fingerprint.clone()); + let events = verify_event_journal(runtime_root, &record)?; + let expected_state = if persisted.outcome == "FAILED" { + "DORMANT_AFTER_FAILURE" + } else { + "DORMANT" + }; + let (_, observed_head) = exact_repository(&repository)?; + if record.state != expected_state + || events.last().map(|event| event.kind.as_str()) != Some("DORMANT") + || primary_lease_held_by_session(runtime_root, &record)? + || observed_head != record.git_head + || require_clean_repository(&repository).is_err() + { + return Err("PERSONA_LIFECYCLE_RECEIPT_BINDING_CHANGED_REQUIRES_REINSPECTION".into()); + } + record.request_id = Some(request_id.clone()); + record.request_fingerprint = Some(request_fingerprint.clone()); record.lifecycle_receipt_hash = Some(persisted.lifecycle_receipt_hash); write_session_record(runtime_root, &record)?; verified_lifecycle_replay( runtime_root, - &inspection.request_id, - &inspection.request_fingerprint, - &inspection.session_id, - Path::new(&record.repository_path), + &request_id, + &request_fingerprint, + &session_id, + &repository, )? .ok_or_else(|| "PERSONA_LIFECYCLE_RECEIPT_RECOVERY_LOST_SESSION".into()) } @@ -4000,6 +4053,92 @@ mod tests { assert!(!after.safe_to_bind_receipt); } + #[test] + fn refuses_to_bind_a_receipt_tampered_after_safe_inspection() { + let repo = persona_repo(); + let runtime = tempfile::TempDir::new().unwrap(); + let input = lifecycle_fact_input(repo.path()); + run_idempotent_lifecycle_at( + runtime.path(), + input.clone(), + "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":"Recoverable receipt.","facts":[{"statement":"The brain exists.","evidencePaths":["brain/CORE.hdlp"]}],"limitations":[]}"#.into()) + }) + }, + ) + .unwrap(); + let (_, _, session_id, _) = lifecycle_request_identity(&input).unwrap(); + let mut record = load_session_record(runtime.path(), &session_id).unwrap(); + record.request_id = None; + record.request_fingerprint = None; + record.lifecycle_receipt_hash = None; + write_session_record(runtime.path(), &record).unwrap(); + let inspection = inspect_lifecycle_request_at(runtime.path(), &input).unwrap(); + assert!(inspection.safe_to_bind_receipt); + + let receipt_path = session_directory(runtime.path(), &session_id) + .unwrap() + .join("lifecycle-receipt.json"); + let mut persisted: serde_json::Value = + serde_json::from_slice(&fs::read(&receipt_path).unwrap()).unwrap(); + persisted["lifecycle"]["personaId"] = serde_json::Value::String("TAMPERED".into()); + fs::write( + &receipt_path, + serde_json::to_vec_pretty(&persisted).unwrap(), + ) + .unwrap(); + + let error = + bind_persisted_lifecycle_receipt_at(runtime.path(), &input, &inspection).unwrap_err(); + assert!(error.contains("PERSONA_LIFECYCLE_RECEIPT_HASH_MISMATCH")); + let record = load_session_record(runtime.path(), &session_id).unwrap(); + assert!(record.request_id.is_none()); + assert!(record.request_fingerprint.is_none()); + assert!(record.lifecycle_receipt_hash.is_none()); + } + + #[test] + fn refuses_to_bind_after_the_repository_advances_from_safe_inspection() { + let repo = persona_repo(); + let runtime = tempfile::TempDir::new().unwrap(); + let input = lifecycle_fact_input(repo.path()); + run_idempotent_lifecycle_at( + runtime.path(), + input.clone(), + "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":"Recoverable receipt.","facts":[{"statement":"The brain exists.","evidencePaths":["brain/CORE.hldp"]}],"limitations":[]}"#.into()) + }) + }, + ) + .unwrap(); + let (_, _, session_id, _) = lifecycle_request_identity(&input).unwrap(); + let mut record = load_session_record(runtime.path(), &session_id).unwrap(); + record.request_id = None; + record.request_fingerprint = None; + record.lifecycle_receipt_hash = None; + write_session_record(runtime.path(), &record).unwrap(); + let inspection = inspect_lifecycle_request_at(runtime.path(), &input).unwrap(); + assert!(inspection.safe_to_bind_receipt); + + fs::write(repo.path().join("advanced.txt"), "new head\n").unwrap(); + run_git(repo.path(), &["add", "advanced.txt"]); + run_git(repo.path(), &["commit", "-m", "advance persona repository"]); + + let error = + bind_persisted_lifecycle_receipt_at(runtime.path(), &input, &inspection).unwrap_err(); + assert!(error.contains("PERSONA_LIFECYCLE_RECEIPT_BINDING_CHANGED_REQUIRES_REINSPECTION")); + let record = load_session_record(runtime.path(), &session_id).unwrap(); + assert!(record.request_id.is_none()); + assert!(record.request_fingerprint.is_none()); + assert!(record.lifecycle_receipt_hash.is_none()); + } + #[test] fn refuses_to_fabricate_a_receipt_for_a_session_interrupted_before_receipt_persistence() { let repo = persona_repo(); diff --git a/routing/hololake-current-architecture.json b/routing/hololake-current-architecture.json index b932591..8e68ce2 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_REPLAY_TIME_REPOSITORY_STATE_REVALIDATION_AND_SUCCESS_RECEIPT_TERMINAL_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_SUCCESS_RECEIPT_TERMINAL_REVALIDATION_AND_SAFE_RECEIPT_BINDING_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 0735eea..6d743e5 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.10", - "state": "CURRENT_FIRST_PRODUCT_CORE_SUCCESS_RECEIPT_TERMINAL_REVALIDATION_SOURCE_IMPLEMENTED", + "version": "2026-08-11.11", + "state": "CURRENT_FIRST_PRODUCT_CORE_SAFE_RECEIPT_BINDING_TERMINAL_REVALIDATION_SOURCE_IMPLEMENTED", "development_id": "DEV-20260810-014", "product": { "formal_name_zh": "光湖人格原生代码频道", @@ -112,6 +112,7 @@ "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, + "safe_receipt_binding_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 a06889f..57a1710 100644 --- a/routing/hololake-persona-native-code-channel.test.mjs +++ b/routing/hololake-persona-native-code-channel.test.mjs @@ -110,12 +110,16 @@ test("the first source runtime cycle stays distinct from integration and deploym channel.truth.successful_completion_receipt_terminal_revalidation_source_implemented, 100, ); + assert.equal( + channel.truth.safe_receipt_binding_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_REPLAY_TIME_REPOSITORY_STATE_REVALIDATION_AND_SUCCESS_RECEIPT_TERMINAL_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_SUCCESS_RECEIPT_TERMINAL_REVALIDATION_AND_SAFE_RECEIPT_BINDING_TERMINAL_REVALIDATION_SOURCE_IMPLEMENTED_AND_TESTED", ); assert.equal(channel.truth.hololake_integrated, 0); assert.equal(channel.truth.artifact_built, 0);