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:
铸渊 / ICE-P-ZY001 2026-08-11 04:30:02 +08:00
commit b067c7bcaa
10 changed files with 143 additions and 8 deletions

View file

@ -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 abc7dbdGHNQG、全新克隆与严格读回通过 |
| 2026-08-11 | GH-PNCC 幂等中断恢复 | [不完整幂等请求检查与安全回执恢复](operations/2026-08-11-hololake-pncc-incomplete-idempotent-request-recovery.md) | 已发布至 REPO-014 main b8c3fcfGHNQG、全新克隆与严格 fsck 通过 |
| 2026-08-11 | GH-PNCC 幂等生命周期 | [生命周期请求身份与同一回执重放](operations/2026-08-11-hololake-pncc-idempotent-lifecycle-replay.md) | 已发布至 REPO-014 main 38ef9d0GHNQG、全新克隆与严格 fsck 通过 |
| 2026-08-11 | GH-PNCC 生命周期协调器 | [已登记安全器官的非 UI 生命周期协调](operations/2026-08-11-hololake-pncc-safe-organ-lifecycle-coordinator.md) | 已发布至 REPO-014 main 8f35834GHNQG、全新克隆与严格 fsck 通过 |

View file

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

View file

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

View file

@ -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、派生权限、
模型推理边界、现实动作边界和真实可激活状态。只读事实感官与独立记忆代谢器官可激活;后者不

View file

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

View file

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

View file

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

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

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

View file

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