fix(pncc): bind failure receipts to terminal 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:46:12 +08:00
commit 85fc90bd52
7 changed files with 278 additions and 10 deletions

View file

@ -0,0 +1,36 @@
# GH-PNCC failure receipt terminal-evidence binding
- Development ID: `DEV-20260810-014`
- Persona cognitive author: `ICE-P-ZY001 / 铸渊`
- Human responsibility subject: `ICE-GL∞ / 冰朔`
- Starting repository head: `21d120d7b5b929ad639f8035c15dd393b4be4d08`
- State: `LOCAL_SOURCE_IMPLEMENTED_FULLY_TESTED`
## Corrected runtime fact
A digest proves that a persisted failure payload has not changed relative to the digest stored beside it. It
does not prove that the payload still describes the terminal session evidence. The previous inspection path
accepted a failure receipt whose terminal event hash or attribution had been changed when the payload digest
was recomputed.
Inspection, interrupted binding recovery and replay now bind a failed lifecycle receipt back to the verified
session record and final event. The canonical repository path, terminal event hash, complete human/persona
attribution and stable machine error code must all agree before the receipt can be considered replayable or
safe to bind.
## Verification
- Two regression tests first reproduced the false acceptance with a recomputed payload digest.
- The forged terminal event and forged persona attribution are now rejected with
`PERSONA_LIFECYCLE_FAILURE_TERMINAL_EVIDENCE_MISMATCH`.
- Both rejection paths leave request id, request fingerprint and lifecycle receipt hash unbound.
- PNCC focused Rust tests: `37 passed, 0 failed`.
- Full Rust suite: `1177 passed, 2 ignored`; integration test: `1 passed`.
- Routing suite: `29 passed, 0 failed`; `cargo fmt`, strict clippy, diff checks and native authority passed.
- GHNQG, publication and fresh-clone readback remain pending.
## Truth boundary
- This stage authenticates the meaning of an existing failure receipt against its existing event and session
truth; it does not add a second truth store or repair forged evidence.
- It does not activate `EXECUTION_LIMB`, add UI, build an artifact or claim deployment/runtime health.

View file

@ -4,7 +4,7 @@
- Persona cognitive author: `ICE-P-ZY001 / 铸渊`
- Human responsibility subject: `ICE-GL∞ / 冰朔`
- Starting repository head: `e9ee0a8bb15447584a799d23c88653fe3ee3a53a`
- State: `LOCAL_SOURCE_IMPLEMENTED_FULLY_TESTED`
- State: `PUBLISHED_AND_READ_BACK`
## Corrected runtime fact
@ -25,7 +25,8 @@ request id, request fingerprint and lifecycle receipt hash written into the sess
- 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.
- GHNQG passed twice for commit `21d120d7b5b929ad639f8035c15dd393b4be4d08`; the persistent receipt SHA-256 is `6847bd4d48ac0bf8d5d8c5dadb7194f96905af33d2d178fc74669c55a51a85f7`.
- Publication queue `PUB-20260810212655790-a9f1874c` completed, and a fresh clone read back the same commit and tree with strict `git fsck` and a clean worktree.
## Truth boundary

View file

@ -176,6 +176,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
failure_receipt_terminal_evidence_binding_source_implemented: 100
general_purpose_persona_runtime_implemented: 0
human_live_projection_implemented: 0
hololake_integrated: 0
@ -198,6 +199,11 @@ runtime_health: 0
终态、Git、租约、事件链和回执哈希全部复核后只重放原失败不再次启动模型或器官。未完成
闭合、脏仓库或仍持有租约的失败不能被伪装成终态回执。
失败回执不能只靠“载荷哈希能对上”证明语义真实。检查、恢复绑定和重放现在还会把失败回执
中的规范仓库、终态事件哈希、完整双层归因与稳定机器错误码重新绑定到当前会话记录及已验证
事件链。即使有人同步重算回执哈希,伪造终态事件或人格作者也会失败关闭,且不会把请求字段
写入会话记录。
成功回执与失败回执的每次重放都会重新读取规范仓库当前提交并检查工作树,而不是只信任回执
生成时的状态。当前提交偏离会话记录或工作树变脏时,检查结果降级为人工复核,重放失败关闭,
且不会重新启动器官。

View file

@ -82,7 +82,7 @@ struct PersonaModelBinding {
base_url: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaAttribution {
pub human_responsibility_subject: String,
@ -2664,6 +2664,28 @@ fn persisted_lifecycle_identity_matches(
}
}
fn validate_persisted_lifecycle_terminal_evidence(
persisted: &PersistedPersonaLifecycleReceipt,
record: &PersonaSessionRecord,
terminal_event: &PersonaLifecycleEvent,
) -> Result<(), String> {
if persisted.outcome != "FAILED" {
return Ok(());
}
let failure = persisted
.failure
.as_ref()
.ok_or("PERSONA_LIFECYCLE_FAILURE_RECEIPT_MISSING")?;
if failure.repository_path != record.repository_path
|| failure.terminal_event_hash != terminal_event.event_hash
|| failure.attribution != record.attribution
|| lifecycle_failure_code(&failure.error_code) != failure.error_code
{
return Err("PERSONA_LIFECYCLE_FAILURE_TERMINAL_EVIDENCE_MISMATCH".into());
}
Ok(())
}
fn lifecycle_failure_code(error: &str) -> String {
let candidate = error.split([':', ';']).next().unwrap_or_default().trim();
if !candidate.is_empty()
@ -2731,12 +2753,14 @@ fn verified_lifecycle_replay(
} else {
"DORMANT"
};
let terminal_event = events.last().ok_or("PERSONA_EVENT_CHAIN_EMPTY")?;
if record.state != expected_state
|| events.last().map(|event| event.kind.as_str()) != Some("DORMANT")
|| terminal_event.kind != "DORMANT"
|| primary_lease_held_by_session(runtime_root, &record)?
{
return Err("PERSONA_LIFECYCLE_REQUEST_INCOMPLETE_REQUIRES_RECOVERY".into());
}
validate_persisted_lifecycle_terminal_evidence(&persisted, &record, terminal_event)?;
if !persisted_lifecycle_identity_matches(&persisted, session_id, &record.persona_id) {
return Err("PERSONA_LIFECYCLE_RECEIPT_IDENTITY_MISMATCH".into());
}
@ -2822,6 +2846,8 @@ fn inspect_lifecycle_request_at(
if !persisted_lifecycle_identity_matches(&persisted, &session_id, &record.persona_id) {
return Err("PERSONA_LIFECYCLE_RECEIPT_IDENTITY_MISMATCH".into());
}
let terminal_event = events.last().ok_or("PERSONA_EVENT_CHAIN_EMPTY")?;
validate_persisted_lifecycle_terminal_evidence(&persisted, &record, terminal_event)?;
let complete = record.request_id.as_deref() == Some(request_id.as_str())
&& record.request_fingerprint.as_deref() == Some(request_fingerprint.as_str())
&& record.lifecycle_receipt_hash.as_deref()
@ -2838,7 +2864,7 @@ fn inspect_lifecycle_request_at(
let repository_matches_record =
observed_head == record.git_head && require_clean_repository(&repository).is_ok();
let safely_dormant = record.state == expected_state
&& events.last().map(|event| event.kind.as_str()) == Some("DORMANT")
&& terminal_event.kind == "DORMANT"
&& !lease_held
&& repository_matches_record;
let (status, safe_to_bind_receipt) = if complete && safely_dormant {
@ -2926,6 +2952,8 @@ fn bind_persisted_lifecycle_receipt_at(
return Err("PERSONA_LIFECYCLE_RECEIPT_BINDING_CHANGED_REQUIRES_REINSPECTION".into());
}
let events = verify_event_journal(runtime_root, &record)?;
let terminal_event = events.last().ok_or("PERSONA_EVENT_CHAIN_EMPTY")?;
validate_persisted_lifecycle_terminal_evidence(&persisted, &record, terminal_event)?;
let expected_state = if persisted.outcome == "FAILED" {
"DORMANT_AFTER_FAILURE"
} else {
@ -2933,7 +2961,7 @@ fn bind_persisted_lifecycle_receipt_at(
};
let (_, observed_head) = exact_repository(&repository)?;
if record.state != expected_state
|| events.last().map(|event| event.kind.as_str()) != Some("DORMANT")
|| terminal_event.kind != "DORMANT"
|| primary_lease_held_by_session(runtime_root, &record)?
|| observed_head != record.git_head
|| require_clean_repository(&repository).is_err()
@ -4288,6 +4316,198 @@ mod tests {
);
}
#[test]
fn rejects_a_rehashed_failure_receipt_with_the_wrong_terminal_event() {
let repo = persona_repo();
let runtime = tempfile::TempDir::new().unwrap();
let input = lifecycle_fact_input(repo.path());
let failed = 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, |_, _| {
Err("bounded provider failure".into())
})
},
)
.unwrap();
let session_id = failed.failure.as_ref().unwrap().session_id.clone();
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 receipt_path = session_directory(runtime.path(), &session_id)
.unwrap()
.join("lifecycle-receipt.json");
let mut persisted: PersistedPersonaLifecycleReceipt =
serde_json::from_slice(&fs::read(&receipt_path).unwrap()).unwrap();
persisted.failure.as_mut().unwrap().terminal_event_hash = "0".repeat(64);
persisted.lifecycle_receipt_hash =
hex_digest(&persisted_lifecycle_payload_bytes(&persisted).unwrap());
fs::write(
&receipt_path,
serde_json::to_vec_pretty(&persisted).unwrap(),
)
.unwrap();
let error = inspect_lifecycle_request_at(runtime.path(), &input).unwrap_err();
assert!(error.contains("PERSONA_LIFECYCLE_FAILURE_TERMINAL_EVIDENCE_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 rejects_a_rehashed_failure_receipt_with_forged_attribution() {
let repo = persona_repo();
let runtime = tempfile::TempDir::new().unwrap();
let input = lifecycle_fact_input(repo.path());
let failed = 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, |_, _| {
Err("bounded provider failure".into())
})
},
)
.unwrap();
let session_id = failed.failure.as_ref().unwrap().session_id.clone();
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 receipt_path = session_directory(runtime.path(), &session_id)
.unwrap()
.join("lifecycle-receipt.json");
let mut persisted: PersistedPersonaLifecycleReceipt =
serde_json::from_slice(&fs::read(&receipt_path).unwrap()).unwrap();
persisted
.failure
.as_mut()
.unwrap()
.attribution
.persona_cognitive_author = "FORGED-PERSONA".into();
persisted.lifecycle_receipt_hash =
hex_digest(&persisted_lifecycle_payload_bytes(&persisted).unwrap());
fs::write(
&receipt_path,
serde_json::to_vec_pretty(&persisted).unwrap(),
)
.unwrap();
let error = inspect_lifecycle_request_at(runtime.path(), &input).unwrap_err();
assert!(error.contains("PERSONA_LIFECYCLE_FAILURE_TERMINAL_EVIDENCE_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_rehashed_failure_evidence_changed_after_inspection() {
let repo = persona_repo();
let runtime = tempfile::TempDir::new().unwrap();
let input = lifecycle_fact_input(repo.path());
let failed = 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, |_, _| {
Err("bounded provider failure".into())
})
},
)
.unwrap();
let session_id = failed.failure.as_ref().unwrap().session_id.clone();
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: PersistedPersonaLifecycleReceipt =
serde_json::from_slice(&fs::read(&receipt_path).unwrap()).unwrap();
persisted.failure.as_mut().unwrap().terminal_event_hash = "0".repeat(64);
persisted.lifecycle_receipt_hash =
hex_digest(&persisted_lifecycle_payload_bytes(&persisted).unwrap());
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_FAILURE_TERMINAL_EVIDENCE_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_replay_rehashed_failure_evidence_bound_into_the_session() {
let repo = persona_repo();
let runtime = tempfile::TempDir::new().unwrap();
let input = lifecycle_fact_input(repo.path());
let failed = 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, |_, _| {
Err("bounded provider failure".into())
})
},
)
.unwrap();
let session_id = failed.failure.as_ref().unwrap().session_id.clone();
let receipt_path = session_directory(runtime.path(), &session_id)
.unwrap()
.join("lifecycle-receipt.json");
let mut persisted: PersistedPersonaLifecycleReceipt =
serde_json::from_slice(&fs::read(&receipt_path).unwrap()).unwrap();
persisted.failure.as_mut().unwrap().repository_path = "/forged/repository".into();
persisted.lifecycle_receipt_hash =
hex_digest(&persisted_lifecycle_payload_bytes(&persisted).unwrap());
fs::write(
&receipt_path,
serde_json::to_vec_pretty(&persisted).unwrap(),
)
.unwrap();
let mut record = load_session_record(runtime.path(), &session_id).unwrap();
record.lifecycle_receipt_hash = Some(persisted.lifecycle_receipt_hash);
write_session_record(runtime.path(), &record).unwrap();
let error = run_idempotent_lifecycle_at(
runtime.path(),
input,
"2026-08-11T00:00:02.000Z",
"2026-08-11T00:00:03.000Z",
|_, _, _| panic!("forged terminal evidence must not rerun the organ"),
)
.unwrap_err();
assert!(error.contains("PERSONA_LIFECYCLE_FAILURE_TERMINAL_EVIDENCE_MISMATCH"));
}
#[test]
fn independently_promotes_only_the_current_verified_structured_checkpoint() {
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_SUCCESS_RECEIPT_TERMINAL_REVALIDATION_AND_SAFE_RECEIPT_BINDING_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_SAFE_RECEIPT_BINDING_TERMINAL_REVALIDATION_AND_FAILURE_RECEIPT_TERMINAL_EVIDENCE_BINDING_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.11",
"state": "CURRENT_FIRST_PRODUCT_CORE_SAFE_RECEIPT_BINDING_TERMINAL_REVALIDATION_SOURCE_IMPLEMENTED",
"version": "2026-08-11.12",
"state": "CURRENT_FIRST_PRODUCT_CORE_FAILURE_RECEIPT_TERMINAL_EVIDENCE_BINDING_SOURCE_IMPLEMENTED",
"development_id": "DEV-20260810-014",
"product": {
"formal_name_zh": "光湖人格原生代码频道",
@ -113,6 +113,7 @@
"idempotent_replay_repository_state_revalidation_source_implemented": 100,
"successful_completion_receipt_terminal_revalidation_source_implemented": 100,
"safe_receipt_binding_terminal_revalidation_source_implemented": 100,
"failure_receipt_terminal_evidence_binding_source_implemented": 100,
"general_purpose_persona_runtime_implemented": 0,
"human_live_projection_implemented": 0,
"hololake_integrated": 0,

View file

@ -114,12 +114,16 @@ test("the first source runtime cycle stays distinct from integration and deploym
channel.truth.safe_receipt_binding_terminal_revalidation_source_implemented,
100,
);
assert.equal(
channel.truth.failure_receipt_terminal_evidence_binding_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_SUCCESS_RECEIPT_TERMINAL_REVALIDATION_AND_SAFE_RECEIPT_BINDING_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_SAFE_RECEIPT_BINDING_TERMINAL_REVALIDATION_AND_FAILURE_RECEIPT_TERMINAL_EVIDENCE_BINDING_SOURCE_IMPLEMENTED_AND_TESTED",
);
assert.equal(channel.truth.hololake_integrated, 0);
assert.equal(channel.truth.artifact_built, 0);