feat(pncc): recover persisted lifecycle receipt binding
GuangHu-Human-Responsibility: ICE-GL∞ / 冰朔 GuangHu-Persona-Cognitive-Author: ICE-P-ZY001 / 铸渊 GuangHu-Execution-Runtime: Codex macOS / DEV-20260810-014 GuangHu-Development-ID: DEV-20260810-014 GuangHu-Authorization-Scope: GH-PNCC local runtime development and registered REPO-014 publication GuangHu-Source-Language-Anchor: continue PNCC persona runtime; UI and execution limb remain deferred
This commit is contained in:
parent
38ef9d0e11
commit
b8c3fcf3d8
11 changed files with 331 additions and 15 deletions
|
|
@ -32,7 +32,8 @@ Windows / macOS / Linux 构建机与安装包
|
|||
|
||||
| 时间 | 版本 | 记录 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| 2026-08-11 | GH-PNCC 幂等生命周期 | [生命周期请求身份与同一回执重放](operations/2026-08-11-hololake-pncc-idempotent-lifecycle-replay.md) | 本地完整 Rust/路由测试、格式与 clippy 已通过;GHNQG、提交与发布待验收 |
|
||||
| 2026-08-11 | GH-PNCC 幂等中断恢复 | [不完整幂等请求检查与安全回执恢复](operations/2026-08-11-hololake-pncc-incomplete-idempotent-request-recovery.md) | 本地源码与聚焦测试已通过;完整门禁、发布与独立回读待验收 |
|
||||
| 2026-08-11 | GH-PNCC 幂等生命周期 | [生命周期请求身份与同一回执重放](operations/2026-08-11-hololake-pncc-idempotent-lifecycle-replay.md) | 已发布至 REPO-014 main 38ef9d0;GHNQG、全新克隆与严格 fsck 通过 |
|
||||
| 2026-08-11 | GH-PNCC 生命周期协调器 | [已登记安全器官的非 UI 生命周期协调](operations/2026-08-11-hololake-pncc-safe-organ-lifecycle-coordinator.md) | 已发布至 REPO-014 main 8f35834;GHNQG、全新克隆与严格 fsck 通过 |
|
||||
| 2026-08-11 | GH-PNCC 记忆失败闭环 | [记忆代谢失败闭环与运行时命令接入](operations/2026-08-11-hololake-pncc-memory-failure-closure-runtime-command.md) | 已发布至 REPO-014 main 18944f5;GHNQG、全新克隆与严格 fsck 通过 |
|
||||
| 2026-08-11 | GH-PNCC 运行查询 | [人格持久事件与回执有界查询](operations/2026-08-11-hololake-pncc-durable-runtime-query.md) | 本地源码、完整 Rust/路由测试与 clippy 已通过;GHNQG 和发布待验收 |
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
- Persona cognitive author: `ICE-P-ZY001 / 铸渊`
|
||||
- Human responsibility subject: `ICE-GL∞ / 冰朔`
|
||||
- Starting repository head: `8f35834a7abc9546b717da0b513b6e8ac1684815`
|
||||
- State: `LOCAL_SOURCE_IMPLEMENTED_FOCUSED_TESTED`
|
||||
- Published repository head: `38ef9d0e113e25737b1be456a705da5ddce0f27c`
|
||||
- Published tree: `11116f03a5db3e3a27a832959da7dae56eec8921`
|
||||
- State: `PUBLISHED_AND_INDEPENDENTLY_READ_BACK`
|
||||
|
||||
## Implemented facts
|
||||
|
||||
|
|
@ -33,12 +35,14 @@ persisted lifecycle fails its receipt hash check.
|
|||
- Conflict retry proves changed semantics never invoke the organ.
|
||||
- Tamper test proves a modified persisted lifecycle is rejected.
|
||||
- Rust formatting and clippy for all targets: `PASS` with `-D warnings`.
|
||||
- GHNQG, commit, publication, and fresh-clone gates remain pending for this stage.
|
||||
- GHNQG: `PASS_100`; manual receipt SHA-256
|
||||
`23e42677da026fbace23edd41cd4818ebe8f7ec8dcc75d53b51adb9c1ac8ed7e`.
|
||||
- Publish queue `PUB-20260810192548933-930c46b1` completed; fresh shallow clone, strict `git fsck`,
|
||||
clean worktree, exact tree, persona author, and human responsibility trailer all read back.
|
||||
|
||||
## Truth boundary and next minimum
|
||||
|
||||
- The existing runtime session directory remains the only lifecycle evidence plane; no database or second
|
||||
request authority was introduced.
|
||||
- UI, human projection aesthetics, background scheduling, and `EXECUTION_LIMB` remain outside this stage.
|
||||
- The next minimum is evidence-bound inspection and safe receipt recovery for an idempotent request that was
|
||||
interrupted after lifecycle progress but before its replay receipt became complete.
|
||||
- The next stage implements that evidence-bound inspection and safe receipt binding recovery.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
# GH-PNCC incomplete idempotent request inspection and safe receipt recovery
|
||||
|
||||
- Development ID: `DEV-20260810-014`
|
||||
- Persona cognitive author: `ICE-P-ZY001 / 铸渊`
|
||||
- Human responsibility subject: `ICE-GL∞ / 冰朔`
|
||||
- Starting repository head: `38ef9d0e113e25737b1be456a705da5ddce0f27c`
|
||||
- State: `LOCAL_SOURCE_IMPLEMENTED_FOCUSED_TESTED`
|
||||
|
||||
## Implemented facts
|
||||
|
||||
The lifecycle coordinator now persists the immutable full lifecycle receipt before atomically binding its
|
||||
request id, semantic fingerprint, and receipt hash into the mutable session record. A crash between those
|
||||
two writes therefore leaves an exact, inspectable recovery state instead of an ambiguous duplicate request.
|
||||
|
||||
`inspect_persona_code_channel_lifecycle_request` derives the deterministic session from the original semantic
|
||||
request and reports `NOT_STARTED`, `COMPLETE_REPLAYABLE`, `SAFE_BIND_PERSISTED_RECEIPT`,
|
||||
`INCOMPLETE_REQUIRES_SESSION_RECOVERY`, or `MANUAL_REVIEW_REQUIRED`. It verifies the canonical repository,
|
||||
persona, receipt schema and hash, request fingerprint, lifecycle identity, event chain, dormancy, and lease.
|
||||
|
||||
`recover_persona_code_channel_lifecycle_request` may write only when a complete persisted receipt exists, the
|
||||
session is fully dormant with no lease, and all three session binding fields are absent. It rechecks the state,
|
||||
binds the receipt, and returns through the ordinary verified replay path. It refuses missing receipts, partial
|
||||
bindings, changed semantics, damaged evidence, active leases, or non-dormant sessions. Missing receipts are
|
||||
never reconstructed from guesses and remain owned by the established session recovery path.
|
||||
|
||||
## Current verification
|
||||
|
||||
- PNCC focused Rust tests: `25 passed, 0 failed`.
|
||||
- A simulated crash after receipt persistence proves inspection and binding recovery return the exact original
|
||||
lifecycle without another model call, session, organ activation, or Git commit.
|
||||
- A session interrupted before receipt persistence proves recovery refuses to fabricate a success receipt.
|
||||
- Full repository gates, GHNQG, commit, publication, and fresh-clone readback remain pending for this stage.
|
||||
|
||||
## Truth boundary and next minimum
|
||||
|
||||
- Runtime files in the existing session directory remain the only evidence plane; no database or second
|
||||
request authority was introduced.
|
||||
- UI, projection aesthetics, background scheduling, and `EXECUTION_LIMB` remain outside this stage.
|
||||
- Terminal failed lifecycle calls still do not have an idempotently replayable command-level failure receipt;
|
||||
that is the next minimum runtime boundary.
|
||||
|
|
@ -171,6 +171,7 @@ independent_memory_metabolism_source_implemented: 100
|
|||
memory_failure_closure_and_nonblocking_runtime_command_implemented: 100
|
||||
non_ui_safe_organ_lifecycle_coordinator_source_implemented: 100
|
||||
idempotent_lifecycle_request_and_receipt_replay_source_implemented: 100
|
||||
incomplete_idempotent_request_inspection_and_safe_receipt_recovery_source_implemented: 100
|
||||
general_purpose_persona_runtime_implemented: 0
|
||||
human_live_projection_implemented: 0
|
||||
hololake_integrated: 0
|
||||
|
|
@ -205,6 +206,11 @@ SHA-256 与来源事件哈希后生成新检查点并以人格 Git 身份提交
|
|||
完成后的类型化生命周期回执保存在同一会话目录并与会话记录中的哈希交叉校验。完全相同的重试
|
||||
只回放同一回执,不再运行器官或提交 Git;同编号不同语义、回执篡改或不完整会话均失败关闭。
|
||||
|
||||
最终生命周期值现在先以原子文件落入同一会话目录,再把请求编号、语义指纹和回执哈希绑定到
|
||||
会话记录。只读检查命令区分尚未开始、完整可重放、回执已落盘但尚未绑定、以及必须交给会话
|
||||
恢复器的中断。恢复命令只允许在完整回执哈希、人格/会话身份、事件链、休眠状态、租约释放和
|
||||
三个绑定字段全空同时成立时补齐绑定;没有回执、部分绑定、活动租约或损坏证据都不会被伪造成成功。
|
||||
|
||||
记忆代谢的 Tauri 命令现已进入阻塞任务池,不占用桌面命令线程。来源拒绝、检查点冲突和提交前
|
||||
失败必须依次持久化失败事件、器官释放与休眠,写回 `DORMANT_AFTER_FAILURE` 后才释放精确主锁;
|
||||
闭环任一步失败时返回 `MEMORY_FAILURE_CLOSURE_INCOMPLETE_REQUIRES_RECOVERY`,不能吞掉二次失败。
|
||||
|
|
|
|||
|
|
@ -96,6 +96,13 @@ return that verified lifecycle with `replayed: true`. A changed request under th
|
|||
`PERSONA_LIFECYCLE_REQUEST_CONFLICT`; a partial session without a complete receipt fails recovery-required
|
||||
instead of launching a duplicate.
|
||||
|
||||
The persisted receipt is promoted before its three binding fields are written to `PersonaSessionRecord`.
|
||||
`PersonaLifecycleRequestInspectionReceipt` makes that two-write boundary explicit. Only a complete, hash-valid
|
||||
receipt beside a verified dormant session with no lease and all three binding fields absent is
|
||||
`SAFE_BIND_PERSISTED_RECEIPT`; the recovery command fills those bindings and then uses the ordinary replay
|
||||
verifier. Missing receipts, partial bindings, active leases, damaged chains, or changed semantics remain
|
||||
recovery/manual-review states and cannot be converted into a successful receipt.
|
||||
|
||||
`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
|
||||
|
|
|
|||
|
|
@ -67,6 +67,12 @@ replaying the same lifecycle value without invoking an organ or committing Git a
|
|||
for different semantics is a conflict. A session without a complete bound receipt is recovery-required and
|
||||
is never treated as permission to start a second lifecycle.
|
||||
|
||||
The final lifecycle value is now atomically persisted before its hash is bound into the mutable session
|
||||
record. `inspect_persona_code_channel_lifecycle_request` distinguishes not-started, replayable, receipt-present
|
||||
but unbound, and session-recovery states from the same session evidence. The recovery command may complete
|
||||
only the receipt-present/unbound window after rechecking the receipt hash, identity, event chain, dormancy,
|
||||
and released lease. It never fabricates a receipt for an earlier interruption or repairs partial bindings.
|
||||
|
||||
`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
|
||||
|
|
|
|||
|
|
@ -529,6 +529,8 @@ macro_rules! app_invoke_handler {
|
|||
persona_code_channel::run_persona_code_channel_fact_task,
|
||||
persona_code_channel::run_persona_code_channel_memory_metabolism,
|
||||
persona_code_channel::run_persona_code_channel_lifecycle,
|
||||
persona_code_channel::inspect_persona_code_channel_lifecycle_request,
|
||||
persona_code_channel::recover_persona_code_channel_lifecycle_request,
|
||||
persona_code_channel::inspect_persona_code_channel_session,
|
||||
persona_code_channel::recover_persona_code_channel_session,
|
||||
guanghu_router::guanghu_router_connect,
|
||||
|
|
|
|||
|
|
@ -331,6 +331,21 @@ pub struct PersonaLifecycleCommandReceipt {
|
|||
pub lifecycle: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PersonaLifecycleRequestInspectionReceipt {
|
||||
pub schema: &'static str,
|
||||
pub request_id: String,
|
||||
pub request_fingerprint: String,
|
||||
pub session_id: String,
|
||||
pub status: &'static str,
|
||||
pub receipt_present: bool,
|
||||
pub session_state: Option<String>,
|
||||
pub event_chain_valid: bool,
|
||||
pub primary_lease_held: bool,
|
||||
pub safe_to_bind_receipt: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PersistedPersonaLifecycleReceipt {
|
||||
|
|
@ -2652,6 +2667,153 @@ fn verified_lifecycle_replay(
|
|||
}))
|
||||
}
|
||||
|
||||
fn inspect_lifecycle_request_at(
|
||||
runtime_root: &Path,
|
||||
input: &PersonaLifecycleRunInput,
|
||||
) -> Result<PersonaLifecycleRequestInspectionReceipt, String> {
|
||||
let (request_id, request_fingerprint, session_id, repository) =
|
||||
lifecycle_request_identity(input)?;
|
||||
let session_dir = session_directory(runtime_root, &session_id)?;
|
||||
if !session_dir.exists() {
|
||||
return Ok(PersonaLifecycleRequestInspectionReceipt {
|
||||
schema: "hololake.pncc-lifecycle-request-inspection/v1",
|
||||
request_id,
|
||||
request_fingerprint,
|
||||
session_id,
|
||||
status: "NOT_STARTED",
|
||||
receipt_present: false,
|
||||
session_state: None,
|
||||
event_chain_valid: false,
|
||||
primary_lease_held: false,
|
||||
safe_to_bind_receipt: false,
|
||||
});
|
||||
}
|
||||
|
||||
let 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());
|
||||
}
|
||||
let events = verify_event_journal(runtime_root, &record)?;
|
||||
let lease_held = primary_lease_held_by_session(runtime_root, &record)?;
|
||||
let receipt_path = session_dir.join("lifecycle-receipt.json");
|
||||
if !receipt_path.exists() {
|
||||
return Ok(PersonaLifecycleRequestInspectionReceipt {
|
||||
schema: "hololake.pncc-lifecycle-request-inspection/v1",
|
||||
request_id,
|
||||
request_fingerprint,
|
||||
session_id,
|
||||
status: "INCOMPLETE_REQUIRES_SESSION_RECOVERY",
|
||||
receipt_present: false,
|
||||
session_state: Some(record.state),
|
||||
event_chain_valid: true,
|
||||
primary_lease_held: lease_held,
|
||||
safe_to_bind_receipt: false,
|
||||
});
|
||||
}
|
||||
|
||||
let bytes = fs::read(&receipt_path)
|
||||
.map_err(|error| format!("PERSONA_LIFECYCLE_RECEIPT_READ_FAILED: {error}"))?;
|
||||
let persisted: PersistedPersonaLifecycleReceipt = serde_json::from_slice(&bytes)
|
||||
.map_err(|error| format!("PERSONA_LIFECYCLE_RECEIPT_INVALID: {error}"))?;
|
||||
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());
|
||||
}
|
||||
let lifecycle_bytes = serde_json::to_vec(&persisted.lifecycle)
|
||||
.map_err(|error| format!("PERSONA_LIFECYCLE_RECEIPT_HASH_INPUT_FAILED: {error}"))?;
|
||||
if hex_digest(&lifecycle_bytes) != persisted.lifecycle_receipt_hash {
|
||||
return Err("PERSONA_LIFECYCLE_RECEIPT_HASH_MISMATCH".into());
|
||||
}
|
||||
if persisted
|
||||
.lifecycle
|
||||
.get("sessionId")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
!= Some(session_id.as_str())
|
||||
|| persisted
|
||||
.lifecycle
|
||||
.get("personaId")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
!= Some(record.persona_id.as_str())
|
||||
{
|
||||
return Err("PERSONA_LIFECYCLE_RECEIPT_IDENTITY_MISMATCH".into());
|
||||
}
|
||||
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()
|
||||
== Some(persisted.lifecycle_receipt_hash.as_str());
|
||||
let unbound = record.request_id.is_none()
|
||||
&& record.request_fingerprint.is_none()
|
||||
&& record.lifecycle_receipt_hash.is_none();
|
||||
let safely_dormant = record.state == "DORMANT"
|
||||
&& events.last().map(|event| event.kind.as_str()) == Some("DORMANT")
|
||||
&& !lease_held;
|
||||
let (status, safe_to_bind_receipt) = if complete && safely_dormant {
|
||||
("COMPLETE_REPLAYABLE", false)
|
||||
} else if unbound && safely_dormant {
|
||||
("SAFE_BIND_PERSISTED_RECEIPT", true)
|
||||
} else {
|
||||
("MANUAL_REVIEW_REQUIRED", false)
|
||||
};
|
||||
Ok(PersonaLifecycleRequestInspectionReceipt {
|
||||
schema: "hololake.pncc-lifecycle-request-inspection/v1",
|
||||
request_id,
|
||||
request_fingerprint,
|
||||
session_id,
|
||||
status,
|
||||
receipt_present: true,
|
||||
session_state: Some(record.state),
|
||||
event_chain_valid: true,
|
||||
primary_lease_held: lease_held,
|
||||
safe_to_bind_receipt,
|
||||
})
|
||||
}
|
||||
|
||||
fn recover_lifecycle_request_at(
|
||||
runtime_root: &Path,
|
||||
input: &PersonaLifecycleRunInput,
|
||||
) -> Result<PersonaLifecycleCommandReceipt, String> {
|
||||
let inspection = inspect_lifecycle_request_at(runtime_root, input)?;
|
||||
if !inspection.safe_to_bind_receipt {
|
||||
return Err(format!(
|
||||
"PERSONA_LIFECYCLE_RECEIPT_BINDING_NOT_SAFE: {}",
|
||||
inspection.status
|
||||
));
|
||||
}
|
||||
let receipt_path =
|
||||
session_directory(runtime_root, &inspection.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 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());
|
||||
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),
|
||||
)?
|
||||
.ok_or_else(|| "PERSONA_LIFECYCLE_RECEIPT_RECOVERY_LOST_SESSION".into())
|
||||
}
|
||||
|
||||
fn run_idempotent_lifecycle_at<F>(
|
||||
runtime_root: &Path,
|
||||
input: PersonaLifecycleRunInput,
|
||||
|
|
@ -2690,21 +2852,23 @@ where
|
|||
if record.state != "DORMANT" || primary_lease_held_by_session(runtime_root, &record)? {
|
||||
return Err("PERSONA_LIFECYCLE_REQUEST_INCOMPLETE_REQUIRES_RECOVERY".into());
|
||||
}
|
||||
record.request_id = Some(request_id.clone());
|
||||
record.request_fingerprint = Some(request_fingerprint.clone());
|
||||
record.lifecycle_receipt_hash = Some(lifecycle_receipt_hash.clone());
|
||||
write_session_record(runtime_root, &record)?;
|
||||
// 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: lifecycle_receipt_hash.clone(),
|
||||
lifecycle: lifecycle.clone(),
|
||||
},
|
||||
"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,
|
||||
|
|
@ -2785,6 +2949,22 @@ pub async fn run_persona_code_channel_lifecycle(
|
|||
.map_err(|error| format!("PERSONA_LIFECYCLE_JOIN_FAILED: {error}"))?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn inspect_persona_code_channel_lifecycle_request(
|
||||
input: PersonaLifecycleRunInput,
|
||||
) -> Result<PersonaLifecycleRequestInspectionReceipt, String> {
|
||||
let runtime_root = crate::app_config::preferred_app_config_path("pncc-runtime")?;
|
||||
inspect_lifecycle_request_at(&runtime_root, &input)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn recover_persona_code_channel_lifecycle_request(
|
||||
input: PersonaLifecycleRunInput,
|
||||
) -> Result<PersonaLifecycleCommandReceipt, String> {
|
||||
let runtime_root = crate::app_config::preferred_app_config_path("pncc-runtime")?;
|
||||
recover_lifecycle_request_at(&runtime_root, &input)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn inspect_persona_code_channel_session(
|
||||
input: PersonaSessionControlInput,
|
||||
|
|
@ -3435,6 +3615,71 @@ mod tests {
|
|||
assert!(error.contains("PERSONA_LIFECYCLE_RECEIPT_HASH_MISMATCH"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inspects_and_safely_binds_a_persisted_receipt_after_an_interrupted_binding() {
|
||||
let repo = persona_repo();
|
||||
let runtime = tempfile::TempDir::new().unwrap();
|
||||
let input = lifecycle_fact_input(repo.path());
|
||||
let first = 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 completed_head = head(repo.path());
|
||||
let session_id = first.lifecycle["sessionId"].as_str().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_eq!(inspection.status, "SAFE_BIND_PERSISTED_RECEIPT");
|
||||
assert!(inspection.receipt_present);
|
||||
assert!(inspection.event_chain_valid);
|
||||
assert!(!inspection.primary_lease_held);
|
||||
assert!(inspection.safe_to_bind_receipt);
|
||||
|
||||
let recovered = recover_lifecycle_request_at(runtime.path(), &input).unwrap();
|
||||
assert!(recovered.replayed);
|
||||
assert_eq!(recovered.lifecycle, first.lifecycle);
|
||||
assert_eq!(head(repo.path()), completed_head);
|
||||
let after = inspect_lifecycle_request_at(runtime.path(), &input).unwrap();
|
||||
assert_eq!(after.status, "COMPLETE_REPLAYABLE");
|
||||
assert!(!after.safe_to_bind_receipt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refuses_to_fabricate_a_receipt_for_a_session_interrupted_before_receipt_persistence() {
|
||||
let repo = persona_repo();
|
||||
let runtime = tempfile::TempDir::new().unwrap();
|
||||
let input = lifecycle_fact_input(repo.path());
|
||||
let (_, _, session_id, _) = lifecycle_request_identity(&input).unwrap();
|
||||
prepare_wake_at(
|
||||
runtime.path(),
|
||||
input.wake.clone(),
|
||||
&session_id,
|
||||
"2026-08-11T00:00:00.000Z",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let inspection = inspect_lifecycle_request_at(runtime.path(), &input).unwrap();
|
||||
assert_eq!(inspection.status, "INCOMPLETE_REQUIRES_SESSION_RECOVERY");
|
||||
assert!(!inspection.receipt_present);
|
||||
assert!(inspection.event_chain_valid);
|
||||
assert!(inspection.primary_lease_held);
|
||||
assert!(!inspection.safe_to_bind_receipt);
|
||||
let error = recover_lifecycle_request_at(runtime.path(), &input).unwrap_err();
|
||||
assert!(error.contains("PERSONA_LIFECYCLE_RECEIPT_BINDING_NOT_SAFE"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn independently_promotes_only_the_current_verified_structured_checkpoint() {
|
||||
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_AND_IDEMPOTENT_RECEIPT_REPLAY_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_RECEIPT_REPLAY_AND_SAFE_RECEIPT_BINDING_RECOVERY_SOURCE_IMPLEMENTED_AND_TESTED",
|
||||
"desktop_integrated": false,
|
||||
"development_id": "DEV-20260810-014"
|
||||
},
|
||||
|
|
@ -343,7 +343,7 @@
|
|||
"DEV-20260810-013"
|
||||
],
|
||||
"closeout_record": "HLP-DEV-20260809-007-CLOSEOUT-001",
|
||||
"next_minimum_stage": "GH_PNCC_INCOMPLETE_IDEMPOTENT_REQUEST_INSPECTION_AND_SAFE_RECEIPT_RECOVERY",
|
||||
"next_minimum_stage": "GH_PNCC_IDEMPOTENT_TERMINAL_FAILURE_RECEIPT_AND_REPLAY",
|
||||
"next_stage_started": true,
|
||||
"heartbeat_automation": "pncc",
|
||||
"heartbeat_state": "ACTIVE_EVERY_10_MINUTES_UNTIL_TASK_TERMINAL"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"schema": "hololake.persona-native-code-channel/v1",
|
||||
"record_id": "HLP-PERSONA-NATIVE-CODE-CHANNEL-001",
|
||||
"version": "2026-08-11.6",
|
||||
"state": "CURRENT_FIRST_PRODUCT_CORE_IDEMPOTENT_SAFE_ORGAN_LIFECYCLE_SOURCE_IMPLEMENTED",
|
||||
"version": "2026-08-11.7",
|
||||
"state": "CURRENT_FIRST_PRODUCT_CORE_RECOVERABLE_IDEMPOTENT_SAFE_ORGAN_LIFECYCLE_SOURCE_IMPLEMENTED",
|
||||
"development_id": "DEV-20260810-014",
|
||||
"product": {
|
||||
"formal_name_zh": "光湖人格原生代码频道",
|
||||
|
|
@ -108,6 +108,7 @@
|
|||
"memory_failure_closure_and_nonblocking_runtime_command_implemented": 100,
|
||||
"non_ui_safe_organ_lifecycle_coordinator_source_implemented": 100,
|
||||
"idempotent_lifecycle_request_and_receipt_replay_source_implemented": 100,
|
||||
"incomplete_idempotent_request_inspection_and_safe_receipt_recovery_source_implemented": 100,
|
||||
"general_purpose_persona_runtime_implemented": 0,
|
||||
"human_live_projection_implemented": 0,
|
||||
"hololake_integrated": 0,
|
||||
|
|
|
|||
|
|
@ -94,12 +94,16 @@ test("the first source runtime cycle stays distinct from integration and deploym
|
|||
channel.truth.idempotent_lifecycle_request_and_receipt_replay_source_implemented,
|
||||
100,
|
||||
);
|
||||
assert.equal(
|
||||
channel.truth.incomplete_idempotent_request_inspection_and_safe_receipt_recovery_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_AND_IDEMPOTENT_RECEIPT_REPLAY_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_RECEIPT_REPLAY_AND_SAFE_RECEIPT_BINDING_RECOVERY_SOURCE_IMPLEMENTED_AND_TESTED",
|
||||
);
|
||||
assert.equal(channel.truth.hololake_integrated, 0);
|
||||
assert.equal(channel.truth.artifact_built, 0);
|
||||
|
|
|
|||
Loading…
Reference in a new issue