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

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