fix(pncc): revalidate Git state before replay
Human-Responsibility: ICE-GL∞ / 冰朔 Persona-Author: ICE-P-ZY001 / 铸渊 Execution-Runtime: Codex macOS Development-ID: DEV-20260810-014 Authorization-Scope: GH-PNCC local runtime and REPO-014 publication Source-Anchor: UI and execution limb deferred
This commit is contained in:
parent
abc7dbd625
commit
b067c7bcaa
10 changed files with 143 additions and 8 deletions
|
|
@ -32,7 +32,8 @@ Windows / macOS / Linux 构建机与安装包
|
|||
|
||||
| 时间 | 版本 | 记录 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| 2026-08-11 | GH-PNCC 终态失败重放 | [终态失败结构化回执与幂等重放](operations/2026-08-11-hololake-pncc-idempotent-terminal-failure-replay.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、全新克隆与严格读回通过 |
|
||||
| 2026-08-11 | GH-PNCC 幂等中断恢复 | [不完整幂等请求检查与安全回执恢复](operations/2026-08-11-hololake-pncc-incomplete-idempotent-request-recovery.md) | 已发布至 REPO-014 main b8c3fcf;GHNQG、全新克隆与严格 fsck 通过 |
|
||||
| 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 通过 |
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
- Persona cognitive author: `ICE-P-ZY001 / 铸渊`
|
||||
- Human responsibility subject: `ICE-GL∞ / 冰朔`
|
||||
- Starting repository head: `b8c3fcf3d8c2928be724291fdf9c1e5dc3512d71`
|
||||
- State: `LOCAL_SOURCE_IMPLEMENTED_FULLY_TESTED`
|
||||
- State: `PUBLISHED_VERIFIED`
|
||||
|
||||
## Implemented facts
|
||||
|
||||
|
|
@ -31,7 +31,11 @@ commands accept the same proven terminal outcome and still reject partial bindin
|
|||
- The terminal-failure test proves exact retry does not call the model again and that raw provider detail is
|
||||
absent from the persisted receipt.
|
||||
- The crash-window test proves an unbound failure receipt is safely inspected, bound, and replayed.
|
||||
- GHNQG, commit, publication, and fresh-clone readback remain pending for this stage.
|
||||
- Published commit: `abc7dbd625610042873868d1f2f36ffc3c88bf85`; tree:
|
||||
`8b11d8e015c2c2f72a0de88c121c182bc17bb012`.
|
||||
- Publication queue receipt: `PUB-20260810201406299-bea423d6`; GHNQG SHA-256:
|
||||
`5315e0bbae64e5b1971c0e1a40a2b27117c8c37646762eab49c334533cb04e3c`.
|
||||
- Fresh-clone readback, strict repository verification, and the full test suites passed from the published tree.
|
||||
|
||||
## Truth boundary
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
# GH-PNCC replay-time repository state revalidation
|
||||
|
||||
- Development ID: `DEV-20260810-014`
|
||||
- Persona cognitive author: `ICE-P-ZY001 / 铸渊`
|
||||
- Human responsibility subject: `ICE-GL∞ / 冰朔`
|
||||
- Starting repository head: `abc7dbd625610042873868d1f2f36ffc3c88bf85`
|
||||
- State: `LOCAL_SOURCE_IMPLEMENTED_FULLY_TESTED`
|
||||
|
||||
## Corrected runtime fact
|
||||
|
||||
The persisted lifecycle receipt is historical evidence, not authority to ignore later changes to the
|
||||
persona-owned Git repository. Before replaying either a completed lifecycle or a structured terminal
|
||||
failure, the kernel now resolves the canonical repository again, requires its current head to equal the
|
||||
session's recorded head, and requires a clean worktree.
|
||||
|
||||
If the repository has advanced or become dirty, replay fails closed before the organ callback can run.
|
||||
Lifecycle request inspection reports `MANUAL_REVIEW_REQUIRED`, so safe receipt binding cannot promote stale
|
||||
evidence after repository drift.
|
||||
|
||||
## Verification
|
||||
|
||||
- PNCC focused Rust tests: `29 passed, 0 failed`.
|
||||
- Full Rust suite: `1169 passed, 2 ignored`; integration test: `1 passed`.
|
||||
- Routing suite: `29 passed, 0 failed`; `cargo fmt`, strict clippy, and diff checks passed.
|
||||
- A completed lifecycle followed by an untracked change cannot replay and is classified for manual review.
|
||||
- A structured terminal failure followed by a new Git commit cannot replay and is classified for manual
|
||||
review.
|
||||
- GHNQG, publication, and fresh-clone readback remain pending for this stage.
|
||||
|
||||
## Truth boundary
|
||||
|
||||
- This stage does not delete, reset, or repair the changed repository. It only prevents stale replay and
|
||||
exposes the condition through the existing inspection status.
|
||||
- UI, human projection aesthetics, background scheduling, and `EXECUTION_LIMB` remain outside this stage.
|
||||
|
|
@ -173,6 +173,7 @@ 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
|
||||
idempotent_terminal_failure_receipt_and_replay_source_implemented: 100
|
||||
idempotent_replay_repository_state_revalidation_source_implemented: 100
|
||||
general_purpose_persona_runtime_implemented: 0
|
||||
human_live_projection_implemented: 0
|
||||
hololake_integrated: 0
|
||||
|
|
@ -195,6 +196,10 @@ runtime_health: 0
|
|||
终态、Git、租约、事件链和回执哈希全部复核后只重放原失败,不再次启动模型或器官。未完成
|
||||
闭合、脏仓库或仍持有租约的失败不能被伪装成终态回执。
|
||||
|
||||
成功回执与失败回执的每次重放都会重新读取规范仓库当前提交并检查工作树,而不是只信任回执
|
||||
生成时的状态。当前提交偏离会话记录或工作树变脏时,检查结果降级为人工复核,重放失败关闭,
|
||||
且不会重新启动器官。
|
||||
|
||||
人格器官现已拥有机器可读类型合同。系统能够在不唤醒人格、不取得主锁、不运行模型的情况下,
|
||||
检查 `FACT_SENSE`、`MEMORY_METABOLISM` 与 `EXECUTION_LIMB` 的固定模式、输入输出 schema、派生权限、
|
||||
模型推理边界、现实动作边界和真实可激活状态。只读事实感官与独立记忆代谢器官可激活;后者不
|
||||
|
|
|
|||
|
|
@ -110,6 +110,10 @@ raw provider diagnostic. Replay and safe binding recovery require `DORMANT_AFTER
|
|||
event, a released lease, and an unchanged clean Git head. A failure that cannot prove those conditions is not
|
||||
terminal and remains recovery-required.
|
||||
|
||||
Repository state is revalidated at replay time for both successful and failed receipts. The current canonical
|
||||
repository must still be clean and its head must equal the session record; otherwise inspection returns
|
||||
`MANUAL_REVIEW_REQUIRED` and ordinary replay fails without invoking the organ.
|
||||
|
||||
`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
|
||||
|
|
|
|||
|
|
@ -81,6 +81,11 @@ clean Git head, semantic fingerprint, receipt hash, and session bindings before
|
|||
with `replayed: true`. Active, dirty, partially closed, or otherwise unproven failures retain the original
|
||||
recovery path and cannot be converted into a terminal receipt.
|
||||
|
||||
The unchanged-clean-repository condition is enforced again on every successful or failed receipt replay,
|
||||
not only when the receipt is first persisted. If the current head differs from the session's recorded head,
|
||||
or the worktree has become dirty, replay fails closed and request inspection reports manual review instead
|
||||
of restarting the organ or treating stale evidence as current.
|
||||
|
||||
`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
|
||||
|
|
|
|||
|
|
@ -2720,6 +2720,11 @@ fn verified_lifecycle_replay(
|
|||
{
|
||||
return Err("PERSONA_LIFECYCLE_RECEIPT_SESSION_BINDING_MISMATCH".into());
|
||||
}
|
||||
let (_, observed_head) = exact_repository(repository)?;
|
||||
if observed_head != record.git_head {
|
||||
return Err("PERSONA_LIFECYCLE_REPLAY_GIT_HEAD_MISMATCH".into());
|
||||
}
|
||||
require_clean_repository(repository)?;
|
||||
let events = verify_event_journal(runtime_root, &record)?;
|
||||
let expected_state = if persisted.outcome == "FAILED" {
|
||||
"DORMANT_AFTER_FAILURE"
|
||||
|
|
@ -2829,9 +2834,13 @@ fn inspect_lifecycle_request_at(
|
|||
} else {
|
||||
"DORMANT"
|
||||
};
|
||||
let (_, observed_head) = exact_repository(&repository)?;
|
||||
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")
|
||||
&& !lease_held;
|
||||
&& !lease_held
|
||||
&& repository_matches_record;
|
||||
let (status, safe_to_bind_receipt) = if complete && safely_dormant {
|
||||
("COMPLETE_REPLAYABLE", false)
|
||||
} else if unbound && safely_dormant {
|
||||
|
|
@ -3708,6 +3717,39 @@ mod tests {
|
|||
assert!(!runtime.path().join("leases/ICE-P-ZY001.json").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refuses_to_replay_a_completed_lifecycle_when_the_repository_is_dirty() {
|
||||
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":"Fact before repository drift.","facts":[{"statement":"The brain exists.","evidencePaths":["brain/CORE.hdlp"]}],"limitations":[]}"#.into())
|
||||
})
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(repo.path().join("untracked-after-lifecycle.txt"), "dirty\n").unwrap();
|
||||
|
||||
let inspection = inspect_lifecycle_request_at(runtime.path(), &input).unwrap();
|
||||
assert_eq!(inspection.status, "MANUAL_REVIEW_REQUIRED");
|
||||
assert!(!inspection.safe_to_bind_receipt);
|
||||
let error = run_idempotent_lifecycle_at(
|
||||
runtime.path(),
|
||||
input,
|
||||
"2026-08-11T00:00:02.000Z",
|
||||
"2026-08-11T00:00:03.000Z",
|
||||
|_, _, _| panic!("a dirty repository must not rerun the organ"),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(error.contains("PERSONA_REPOSITORY_DIRTY"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_reusing_a_lifecycle_request_id_for_a_different_operation() {
|
||||
let repo = persona_repo();
|
||||
|
|
@ -3908,6 +3950,41 @@ mod tests {
|
|||
assert!(!runtime.path().join("leases/ICE-P-ZY001.json").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refuses_to_replay_a_terminal_failure_after_the_repository_head_advances() {
|
||||
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, |_, _| {
|
||||
Err("bounded provider failure".into())
|
||||
})
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
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 inspection = inspect_lifecycle_request_at(runtime.path(), &input).unwrap();
|
||||
assert_eq!(inspection.status, "MANUAL_REVIEW_REQUIRED");
|
||||
assert!(!inspection.safe_to_bind_receipt);
|
||||
let error = run_idempotent_lifecycle_at(
|
||||
runtime.path(),
|
||||
input,
|
||||
"2026-08-11T00:00:02.000Z",
|
||||
"2026-08-11T00:00:03.000Z",
|
||||
|_, _, _| panic!("an advanced repository must not rerun the organ"),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(error.contains("PERSONA_LIFECYCLE_REPLAY_GIT_HEAD_MISMATCH"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn safely_recovers_an_unbound_terminal_failure_receipt() {
|
||||
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_AND_SAFE_RECEIPT_BINDING_RECOVERY_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_AND_REPLAY_TIME_REPOSITORY_STATE_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.8",
|
||||
"state": "CURRENT_FIRST_PRODUCT_CORE_IDEMPOTENT_TERMINAL_FAILURE_REPLAY_SOURCE_IMPLEMENTED",
|
||||
"version": "2026-08-11.9",
|
||||
"state": "CURRENT_FIRST_PRODUCT_CORE_IDEMPOTENT_REPLAY_REPOSITORY_STATE_REVALIDATION_SOURCE_IMPLEMENTED",
|
||||
"development_id": "DEV-20260810-014",
|
||||
"product": {
|
||||
"formal_name_zh": "光湖人格原生代码频道",
|
||||
|
|
@ -110,6 +110,7 @@
|
|||
"idempotent_lifecycle_request_and_receipt_replay_source_implemented": 100,
|
||||
"incomplete_idempotent_request_inspection_and_safe_receipt_recovery_source_implemented": 100,
|
||||
"idempotent_terminal_failure_receipt_and_replay_source_implemented": 100,
|
||||
"idempotent_replay_repository_state_revalidation_source_implemented": 100,
|
||||
"general_purpose_persona_runtime_implemented": 0,
|
||||
"human_live_projection_implemented": 0,
|
||||
"hololake_integrated": 0,
|
||||
|
|
|
|||
|
|
@ -102,12 +102,16 @@ test("the first source runtime cycle stays distinct from integration and deploym
|
|||
channel.truth.idempotent_terminal_failure_receipt_and_replay_source_implemented,
|
||||
100,
|
||||
);
|
||||
assert.equal(
|
||||
channel.truth.idempotent_replay_repository_state_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_AND_SAFE_RECEIPT_BINDING_RECOVERY_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_AND_REPLAY_TIME_REPOSITORY_STATE_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