fix(pncc): revalidate receipt binding evidence

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
This commit is contained in:
铸渊 / ICE-P-ZY001 2026-08-11 05:19:28 +08:00
commit 21d120d7b5
8 changed files with 199 additions and 13 deletions

View file

@ -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 abc7dbdGHNQG、全新克隆与严格读回通过 |

View file

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

View file

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

View file

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

View file

@ -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<PersonaLifecycleCommandReceipt, String> {
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();

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_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"
},

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

View file

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