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:
parent
e9ee0a8bb1
commit
21d120d7b5
8 changed files with 199 additions and 13 deletions
|
|
@ -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、全新克隆与严格读回通过 |
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue