feat: query verified PNCC runtime history

GuangHu-Human-Responsibility: ICE-GL∞ / 冰朔
GuangHu-Persona-Cognitive-Author: ICE-P-ZY001 / 铸渊
GuangHu-Execution-Runtime: Codex thread 019febf6-3902-70f3-9d82-74c9680b0c7e
GuangHu-Development-ID: DEV-20260810-014
GuangHu-Authorization-Scope: LOCAL_PNCC_RUNTIME_DEVELOPMENT_AND_PUBLICATION
This commit is contained in:
铸渊 / ICE-P-ZY001 2026-08-11 01:51:02 +08:00
commit 33b3b67ce7
10 changed files with 289 additions and 3 deletions

View file

@ -166,6 +166,7 @@ persona_git_identity_commit_implemented: 100
semantic_attribution_first_cycle_implemented: 100
interrupted_session_inspection_and_recovery_source_implemented: 100
typed_organ_contract_and_manifest_inspection_source_implemented: 100
durable_event_and_receipt_query_source_implemented: 100
general_purpose_persona_runtime_implemented: 0
human_live_projection_implemented: 0
hololake_integrated: 0
@ -187,3 +188,7 @@ runtime_health: 0
检查 `FACT_SENSE``MEMORY_METABOLISM``EXECUTION_LIMB` 的固定模式、输入输出 schema、派生权限、
模型推理边界、现实动作边界和真实可激活状态。当前只有只读事实感官可激活;清单里写出记忆器官
或执行手脚不会自动产生执行器,更不会自动获得 shell 或现实动作权限。
运行层现在还提供同源有界查询:调用者必须给出精确人格与精确仓库,最多返回 100 条经过事件
哈希链验证的会话摘要包括生命周期状态、Git 头、节点、模型实例、活跃器官、事件数量、链头和
双层归因。该查询不复制第二套数据库、不启动模型、不取得主锁;匹配会话的事件链损坏时失败关闭。

View file

@ -64,6 +64,12 @@ inspection receipt reports them as not independently activatable until their exe
an execution-limb declaration never grants a shell or reality action by itself. The inspection command reads
and validates all contracts without acquiring a runtime lease or starting model inference.
`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
the event-chain head and human/persona attribution so a future renderer can link a summary back to the same
machine evidence rather than trusting display state.
## `HoloLakeUiPlugin`
A versioned, declarative presentation package for one host-owned semantic surface. It contains a manifest,

View file

@ -43,6 +43,13 @@ schemas, derived permissions, inference/reality-action boundaries, and the actua
implemented read-only fact sense can wake. Merely declaring a memory organ or execution limb does not create
an executor or grant a shell; those contracts remain visible but fail closed as not activatable.
`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
event, and returns lifecycle state, Git head, node/model instance, active organ, event count, chain head and
structured attribution. It does not copy facts into a second database, expose hidden reasoning, acquire a
lease, or start inference. A corrupted matching journal fails the query closed.
## User-node sovereignty
HoloLake has no platform-hosted user runtime. Each human has one canonical, independently operated node:

View file

@ -525,6 +525,7 @@ macro_rules! app_invoke_handler {
guanghu_living_system::guanghu_living_system_plan,
persona_code_channel::prepare_persona_code_channel_wake,
persona_code_channel::inspect_persona_code_channel_manifest,
persona_code_channel::query_persona_code_channel_runtime,
persona_code_channel::run_persona_code_channel_fact_task,
persona_code_channel::inspect_persona_code_channel_session,
persona_code_channel::recover_persona_code_channel_session,

View file

@ -13,6 +13,7 @@ const MAX_FACT_QUESTION_BYTES: usize = 8_000;
const MAX_FACT_SOURCE_BYTES: usize = 128_000;
const MAX_FACT_CONTEXT_BYTES: usize = 512_000;
const MAX_FACT_RESPONSE_BYTES: usize = 128_000;
const MAX_SESSION_QUERY_LIMIT: usize = 100;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
@ -169,6 +170,44 @@ pub struct PersonaManifestInspectionReceipt {
pub organ_contracts: Vec<PersonaOrganContract>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaRuntimeQueryInput {
pub expected_persona_id: String,
pub expected_repository_path: String,
pub limit: Option<usize>,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaRuntimeSessionProjection {
pub session_id: String,
pub state: String,
pub git_head: String,
pub node_id: String,
pub model_instance_id: String,
pub active_organ: Option<String>,
pub event_count: usize,
pub first_event_at: String,
pub last_event_at: String,
pub last_event: String,
pub event_chain_head: String,
pub attribution: PersonaAttribution,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaRuntimeQueryReceipt {
pub schema: &'static str,
pub persona_id: String,
pub repository_path: String,
pub observed_git_head: String,
pub repository_clean: bool,
pub matching_session_count: usize,
pub returned_session_count: usize,
pub sessions: Vec<PersonaRuntimeSessionProjection>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaFactTaskInput {
@ -1439,6 +1478,99 @@ fn inspect_session_at(
})
}
fn query_runtime_sessions_at(
runtime_root: &Path,
input: PersonaRuntimeQueryInput,
) -> Result<PersonaRuntimeQueryReceipt, String> {
let expected_persona_id = validated_id("EXPECTED_PERSONA_ID", &input.expected_persona_id)?;
let limit = input.limit.unwrap_or(25);
if limit == 0 || limit > MAX_SESSION_QUERY_LIMIT {
return Err("PERSONA_SESSION_QUERY_LIMIT_INVALID".into());
}
let expected_repository = Path::new(&input.expected_repository_path)
.canonicalize()
.map_err(|error| format!("PERSONA_REPOSITORY_UNAVAILABLE: {error}"))?;
let (repository, observed_head) = exact_repository(&expected_repository)?;
let repository_clean = repository_is_clean(&repository)?;
let sessions_root = runtime_root.join("sessions");
let entries = match fs::read_dir(&sessions_root) {
Ok(entries) => entries,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
return Ok(PersonaRuntimeQueryReceipt {
schema: "hololake.pncc-runtime-query/v1",
persona_id: expected_persona_id,
repository_path: repository.to_string_lossy().into_owned(),
observed_git_head: observed_head,
repository_clean,
matching_session_count: 0,
returned_session_count: 0,
sessions: Vec::new(),
});
}
Err(error) => return Err(format!("PERSONA_SESSIONS_DIRECTORY_READ_FAILED: {error}")),
};
let mut projections = Vec::new();
for entry in entries {
let entry = entry.map_err(|error| format!("PERSONA_SESSION_ENTRY_FAILED: {error}"))?;
if !entry
.file_type()
.map_err(|error| format!("PERSONA_SESSION_ENTRY_TYPE_FAILED: {error}"))?
.is_dir()
{
continue;
}
let session_id = match entry.file_name().to_str() {
Some(value) => validated_id("SESSION_ID", value)?,
None => return Err("PERSONA_SESSION_ID_NOT_UTF8".into()),
};
let record = load_session_record(runtime_root, &session_id)?;
if record.persona_id != expected_persona_id
|| Path::new(&record.repository_path) != repository
{
continue;
}
let events = verify_event_journal(runtime_root, &record)?;
let first = events.first().expect("verified non-empty event chain");
let last = events.last().expect("verified non-empty event chain");
projections.push(PersonaRuntimeSessionProjection {
session_id: record.session_id,
state: record.state.clone(),
git_head: record.git_head,
node_id: record.node_id,
model_instance_id: record.model_instance_id,
active_organ: if record.state.starts_with("DORMANT") {
None
} else {
Some(record.active_organ)
},
event_count: events.len(),
first_event_at: first.timestamp.clone(),
last_event_at: last.timestamp.clone(),
last_event: last.kind.clone(),
event_chain_head: last.event_hash.clone(),
attribution: record.attribution,
});
}
projections.sort_by(|left, right| {
right
.last_event_at
.cmp(&left.last_event_at)
.then_with(|| right.session_id.cmp(&left.session_id))
});
let matching_session_count = projections.len();
projections.truncate(limit);
Ok(PersonaRuntimeQueryReceipt {
schema: "hololake.pncc-runtime-query/v1",
persona_id: expected_persona_id,
repository_path: repository.to_string_lossy().into_owned(),
observed_git_head: observed_head,
repository_clean,
matching_session_count,
returned_session_count: projections.len(),
sessions: projections,
})
}
fn recover_session_at(
runtime_root: &Path,
input: PersonaSessionControlInput,
@ -1802,6 +1934,14 @@ pub fn inspect_persona_code_channel_manifest(
inspect_manifest_at(input)
}
#[tauri::command]
pub fn query_persona_code_channel_runtime(
input: PersonaRuntimeQueryInput,
) -> Result<PersonaRuntimeQueryReceipt, String> {
let runtime_root = crate::app_config::preferred_app_config_path("pncc-runtime")?;
query_runtime_sessions_at(&runtime_root, input)
}
#[tauri::command]
pub async fn run_persona_code_channel_fact_task(
input: PersonaFactTaskInput,
@ -2007,6 +2147,14 @@ mod tests {
}
}
fn runtime_query(repo: &Path, limit: Option<usize>) -> PersonaRuntimeQueryInput {
PersonaRuntimeQueryInput {
expected_persona_id: "ICE-P-ZY001".into(),
expected_repository_path: repo.to_string_lossy().into_owned(),
limit,
}
}
#[test]
fn prepares_exact_persona_git_wake_with_hash_chained_events() {
let repo = persona_repo();
@ -2471,4 +2619,79 @@ mod tests {
.unwrap_err()
.contains("ORGAN_NOT_ACTIVATABLE"));
}
#[test]
fn queries_bounded_verified_runtime_projections_without_a_second_store() {
let repo = persona_repo();
let runtime = tempfile::TempDir::new().unwrap();
prepare_wake_at(
runtime.path(),
wake_input(repo.path()),
"PNCC-SESSION-HISTORY-001",
"2026-08-11T00:00:00.000Z",
)
.unwrap();
recover_session_at(
runtime.path(),
session_control(repo.path(), "PNCC-SESSION-HISTORY-001"),
"2026-08-11T00:01:00.000Z",
)
.unwrap();
prepare_wake_at(
runtime.path(),
wake_input(repo.path()),
"PNCC-SESSION-HISTORY-002",
"2026-08-11T00:02:00.000Z",
)
.unwrap();
let receipt =
query_runtime_sessions_at(runtime.path(), runtime_query(repo.path(), Some(1))).unwrap();
assert_eq!(receipt.matching_session_count, 2);
assert_eq!(receipt.returned_session_count, 1);
assert_eq!(receipt.sessions[0].session_id, "PNCC-SESSION-HISTORY-002");
assert_eq!(receipt.sessions[0].last_event, "ORGAN_ACTIVE");
assert_eq!(
receipt.sessions[0].active_organ.as_deref(),
Some("fact-sense.repository")
);
assert_eq!(receipt.sessions[0].event_count, 3);
assert!(!receipt.sessions[0].event_chain_head.is_empty());
assert_eq!(
receipt.sessions[0].attribution.persona_cognitive_author,
"ICE-P-ZY001"
);
}
#[test]
fn runtime_query_rejects_invalid_limits_and_corrupted_event_chains() {
let repo = persona_repo();
let runtime = tempfile::TempDir::new().unwrap();
assert!(
query_runtime_sessions_at(runtime.path(), runtime_query(repo.path(), Some(0)))
.unwrap_err()
.contains("LIMIT_INVALID")
);
prepare_wake_at(
runtime.path(),
wake_input(repo.path()),
"PNCC-SESSION-CORRUPTED",
"2026-08-11T00:00:00.000Z",
)
.unwrap();
let journal = runtime
.path()
.join("sessions/PNCC-SESSION-CORRUPTED/events.jsonl");
let content = fs::read_to_string(&journal).unwrap();
fs::write(
&journal,
content.replacen("\"eventHash\":\"", "\"eventHash\":\"00", 1),
)
.unwrap();
assert!(
query_runtime_sessions_at(runtime.path(), runtime_query(repo.path(), Some(10)))
.unwrap_err()
.contains("EVENT_HASH_INVALID")
);
}
}