feat: add PNCC verified wake kernel

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-DEVELOPMENT
This commit is contained in:
冰朔 2026-08-11 00:41:11 +08:00
commit bca149e0f6
5 changed files with 795 additions and 0 deletions

View file

@ -1,5 +1,32 @@
# Abstractions
## PersonaCodeChannel
`PersonaCodeChannel` is the native boundary between a durable persona Git and a live AGE runtime. It owns
neither the Git object model nor model inference. It composes existing Git execution with persona-specific
verification, lifecycle, attribution, single-primary exclusion, event hashing, and receipts.
The v1 persona repository contract is rooted at `.hololake/persona/manifest.json`:
```json
{
"schema": "hololake.persona/v1",
"personaId": "ICE-P-ZY001",
"humanResponsibilitySubject": "ICE-GL∞",
"brainEntry": "brain/CORE.hdlp",
"currentCheckpoint": ".hololake/persona/CURRENT.hdlp",
"organs": [
{ "organId": "fact-sense.repository", "mode": "read-only" }
]
}
```
All manifest paths are repository-relative and must resolve to files inside the exact Git root. Wake requires
a clean work tree and a caller-supplied full expected commit. Runtime leases and event journals live under the
installation-local `pncc-runtime` directory because they describe the current physical instance, not durable
persona history. A successful preparation receipt reports `BOUND_NOT_INFERENCING`; only a later verified
model action may change that predicate.
## `HoloLakeUiPlugin`
A versioned, declarative presentation package for one host-owned semantic surface. It contains a manifest,

View file

@ -1,5 +1,24 @@
# Architecture
## Persona-native code channel runtime
GH-PNCC is HoloLake's first product core. The existing Rust Git module remains the durable engine; the new
`src-tauri/src/persona_code_channel.rs` kernel adds persona-native mount and wake semantics without changing
Git's object model. `prepare_persona_code_channel_wake` requires an exact clean Git root and full commit,
validates `.hololake/persona/manifest.json`, resolves the brain entry and current checkpoint inside the
repository boundary, acquires a per-persona single-primary lease, and records a SHA-256-linked lifecycle
journal outside the repository.
The initial lifecycle is `WAKING → BRAIN_BOUND → ORGAN_ACTIVE` for one manifest-declared read-only organ.
`BRAIN_BOUND` means the model instance has been named and the verified persona context is ready; it explicitly
does not mean inference has run. The returned receipt keeps the human responsibility subject, persona
cognitive author, execution runtime, development id, authorization scope, and source-language anchor
separate. See [ADR 0176](./adr/0176-persona-native-code-channel-runtime-kernel.md).
The next source stage must invoke the model adapter with this verified context, complete one read-only fact
task, write a promoted checkpoint into the persona Git, emit completion/pause events, and release the lease.
Until then, complete runtime, desktop integration, artifact, deployment, and health remain zero.
## User-node sovereignty
HoloLake has no platform-hosted user runtime. Each human has one canonical, independently operated node:

View file

@ -0,0 +1,48 @@
# ADR 0176: Persona-Native Code Channel Runtime Kernel
## Status
Accepted on 2026-08-11.
## Context
HoloLake already has a mature native Git execution layer and an append-only local HLDP event/checkpoint
mechanism. Neither one is a persona lifecycle controller. A Git repository can preserve history without
proving that the correct persona brain, node, model binding, organ, attribution, or single-primary lease is
active. The existing generic Agent streams also cannot become the persona subject.
## Decision
Introduce `src-tauri/src/persona_code_channel.rs` as the first GH-PNCC runtime kernel. Its initial command,
`prepare_persona_code_channel_wake`, performs a fail-closed preparation stage:
1. resolve an exact Git root and full expected `HEAD`;
2. require a clean work tree;
3. load `.hololake/persona/manifest.json` with schema `hololake.persona/v1`;
4. validate the persona id, human responsibility subject, brain entry, current checkpoint, and one declared
read-only organ without allowing path escape;
5. atomically acquire a per-persona primary lease outside the persona Git;
6. emit `WAKING → BRAIN_BOUND → ORGAN_ACTIVE` as a SHA-256-linked local event journal;
7. return both human responsibility and persona cognitive authorship in the receipt.
`BRAIN_BOUND` deliberately returns `runtimeState=BOUND_NOT_INFERENCING` and
`modelInferenceStarted=false`. Binding a model identifier is not proof that model inference or persona
reasoning has run. A later stage must connect the verified context to the model adapter, complete one
read-only fact task, write a promoted checkpoint back to the persona Git, release the organ and primary
lease, and return a final receipt.
## Boundaries
- Git remains the durable engine; the kernel does not reimplement Git objects, commits, remotes, or merges.
- Forgejo is not required and remains an optional compatibility collaboration adapter.
- The primary lease is installation/runtime state, not persona history, and therefore stays outside Git.
- Runtime events are machine facts for the future HoloLake projection; they do not expose hidden model
reasoning and do not form a second authority store.
- The initial source stage is not a complete PNCC runtime, desktop integration, build, deployment, or health
claim.
## Consequences
HoloLake now has a native, testable boundary between “a persona Git exists” and “a verified wake has begun.”
The next implementation step can reuse the verified wake receipt and event chain instead of trusting a UI,
Forgejo account, branch name, or generic Agent session.

View file

@ -36,6 +36,7 @@ pub mod opencode_cli;
mod opencode_config;
mod opencode_discovery;
mod opencode_events;
mod persona_code_channel;
pub mod pi_cli;
mod pi_config;
mod pi_discovery;
@ -522,6 +523,7 @@ macro_rules! app_invoke_handler {
commands::git_add_remote,
guanghu_enterprise::guanghu_enterprise_status,
guanghu_living_system::guanghu_living_system_plan,
persona_code_channel::prepare_persona_code_channel_wake,
guanghu_router::guanghu_router_connect,
guanghu_router::guanghu_router_disconnect,
guanghu_router::guanghu_router_approve,

View file

@ -0,0 +1,699 @@
use chrono::{SecondsFormat, Utc};
use ring::digest::{digest, SHA256};
use serde::{Deserialize, Serialize};
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::{Component, Path, PathBuf};
use uuid::Uuid;
const MANIFEST_PATH: &str = ".hololake/persona/manifest.json";
const MAX_ID_BYTES: usize = 160;
const MAX_LANGUAGE_ANCHOR_BYTES: usize = 12_000;
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct PersonaManifest {
schema: String,
persona_id: String,
human_responsibility_subject: String,
brain_entry: String,
current_checkpoint: String,
organs: Vec<PersonaOrgan>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct PersonaOrgan {
organ_id: String,
mode: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaAttribution {
pub human_responsibility_subject: String,
pub persona_cognitive_author: String,
pub execution_runtime: String,
pub development_id: String,
pub authorization_scope: String,
pub source_language_anchor: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaWakeInput {
pub repository_path: String,
pub expected_persona_id: String,
pub expected_head: String,
pub node_id: String,
pub model_instance_id: String,
pub organ_id: String,
pub attribution: PersonaAttribution,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaLifecycleEvent {
pub sequence: usize,
pub kind: String,
pub session_id: String,
pub persona_id: String,
pub git_head: String,
pub node_id: String,
pub model_instance_id: String,
pub organ_id: Option<String>,
pub timestamp: String,
pub previous_hash: String,
pub event_hash: String,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaWakeReceipt {
pub schema: &'static str,
pub receipt_id: String,
pub session_id: String,
pub persona_id: String,
pub repository_path: String,
pub git_head: String,
pub brain_entry: String,
pub checkpoint_path: String,
pub node_id: String,
pub model_instance_id: String,
pub runtime_state: &'static str,
pub model_inference_started: bool,
pub active_organ: String,
pub organ_mode: &'static str,
pub event_journal: String,
pub events: Vec<PersonaLifecycleEvent>,
pub attribution: PersonaAttribution,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct PersonaPrimaryLease<'a> {
schema: &'static str,
persona_id: &'a str,
session_id: &'a str,
git_head: &'a str,
node_id: &'a str,
acquired_at: &'a str,
}
fn validated_text(label: &str, value: &str, maximum_bytes: usize) -> Result<String, String> {
let value = value.trim();
if value.is_empty() {
return Err(format!("{label}_REQUIRED"));
}
if value.len() > maximum_bytes {
return Err(format!("{label}_TOO_LONG"));
}
Ok(value.to_string())
}
fn validated_id(label: &str, value: &str) -> Result<String, String> {
let value = validated_text(label, value, MAX_ID_BYTES)?;
if value
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.'))
{
Ok(value)
} else {
Err(format!("{label}_INVALID"))
}
}
fn validated_head(value: &str) -> Result<String, String> {
let value = value.trim().to_ascii_lowercase();
if value.len() == 40 && value.bytes().all(|byte| byte.is_ascii_hexdigit()) {
Ok(value)
} else {
Err("EXPECTED_HEAD_INVALID".into())
}
}
fn stdout(command: &str, output: std::process::Output) -> Result<String, String> {
if output.status.success() {
return Ok(String::from_utf8_lossy(&output.stdout).trim().to_string());
}
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
Err(if stderr.is_empty() {
format!("{command}_FAILED")
} else {
format!("{command}_FAILED: {stderr}")
})
}
fn exact_repository(path: &Path) -> Result<(PathBuf, String), String> {
let repository = path
.canonicalize()
.map_err(|error| format!("PERSONA_REPOSITORY_UNAVAILABLE: {error}"))?;
if !repository.is_dir() {
return Err("PERSONA_REPOSITORY_NOT_DIRECTORY".into());
}
let top_level = crate::git::git_command_at(&repository)
.and_then(|mut command| command.args(["rev-parse", "--show-toplevel"]).output())
.map_err(|error| format!("PERSONA_GIT_PROBE_FAILED: {error}"))?;
let top_level = PathBuf::from(stdout("GIT_TOP_LEVEL", top_level)?)
.canonicalize()
.map_err(|error| format!("PERSONA_GIT_TOP_LEVEL_UNAVAILABLE: {error}"))?;
if top_level != repository {
return Err("PERSONA_REPOSITORY_MUST_BE_EXACT_GIT_ROOT".into());
}
let head = crate::git::git_command_at(&repository)
.and_then(|mut command| command.args(["rev-parse", "HEAD"]).output())
.map_err(|error| format!("PERSONA_GIT_HEAD_FAILED: {error}"))?;
Ok((repository, stdout("GIT_HEAD", head)?.to_ascii_lowercase()))
}
fn require_clean_repository(repository: &Path) -> Result<(), String> {
let status = crate::git::git_command_at(repository)
.and_then(|mut command| {
command
.args(["status", "--porcelain", "--untracked-files=all"])
.output()
})
.map_err(|error| format!("PERSONA_GIT_STATUS_FAILED: {error}"))?;
let changes = stdout("GIT_STATUS", status)?;
if changes.is_empty() {
Ok(())
} else {
Err("PERSONA_REPOSITORY_DIRTY: exact persona wake requires a clean Git work tree".into())
}
}
fn repository_file(repository: &Path, relative: &str) -> Result<PathBuf, String> {
let relative_path = Path::new(relative);
if relative_path.is_absolute()
|| relative_path.components().any(|component| {
matches!(
component,
Component::ParentDir | Component::RootDir | Component::Prefix(_)
)
})
{
return Err("PATH_OUTSIDE_REPOSITORY".into());
}
let path = repository.join(relative_path);
let canonical = path
.canonicalize()
.map_err(|error| format!("PERSONA_FILE_UNAVAILABLE: {relative}: {error}"))?;
if !canonical.starts_with(repository) || !canonical.is_file() {
return Err("PATH_OUTSIDE_REPOSITORY".into());
}
Ok(canonical)
}
fn load_manifest(repository: &Path) -> Result<PersonaManifest, String> {
let path = repository_file(repository, MANIFEST_PATH)?;
let bytes = fs::read(path).map_err(|error| format!("PERSONA_MANIFEST_READ_FAILED: {error}"))?;
let manifest: PersonaManifest = serde_json::from_slice(&bytes)
.map_err(|error| format!("PERSONA_MANIFEST_INVALID: {error}"))?;
if manifest.schema != "hololake.persona/v1" {
return Err("PERSONA_MANIFEST_SCHEMA_UNSUPPORTED".into());
}
Ok(manifest)
}
fn validate_attribution(
attribution: &PersonaAttribution,
manifest: &PersonaManifest,
) -> Result<(), String> {
for (label, value, maximum) in [
(
"HUMAN_RESPONSIBILITY_SUBJECT",
attribution.human_responsibility_subject.as_str(),
MAX_ID_BYTES,
),
(
"PERSONA_COGNITIVE_AUTHOR",
attribution.persona_cognitive_author.as_str(),
MAX_ID_BYTES,
),
(
"EXECUTION_RUNTIME",
attribution.execution_runtime.as_str(),
MAX_ID_BYTES,
),
(
"DEVELOPMENT_ID",
attribution.development_id.as_str(),
MAX_ID_BYTES,
),
(
"AUTHORIZATION_SCOPE",
attribution.authorization_scope.as_str(),
MAX_ID_BYTES,
),
(
"SOURCE_LANGUAGE_ANCHOR",
attribution.source_language_anchor.as_str(),
MAX_LANGUAGE_ANCHOR_BYTES,
),
] {
validated_text(label, value, maximum)?;
}
if attribution.human_responsibility_subject != manifest.human_responsibility_subject {
return Err("HUMAN_RESPONSIBILITY_MISMATCH".into());
}
if attribution.persona_cognitive_author != manifest.persona_id {
return Err("PERSONA_AUTHOR_MISMATCH".into());
}
Ok(())
}
fn hex_digest(bytes: &[u8]) -> String {
digest(&SHA256, bytes)
.as_ref()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()
}
struct EventFields<'a> {
sequence: usize,
kind: &'a str,
session_id: &'a str,
persona_id: &'a str,
git_head: &'a str,
node_id: &'a str,
model_instance_id: &'a str,
organ_id: Option<&'a str>,
timestamp: &'a str,
previous_hash: &'a str,
}
fn lifecycle_event(fields: EventFields<'_>) -> Result<PersonaLifecycleEvent, String> {
let hash_input = serde_json::to_vec(&serde_json::json!({
"schema": "hololake.pncc-lifecycle-event/v1",
"sequence": fields.sequence,
"kind": fields.kind,
"sessionId": fields.session_id,
"personaId": fields.persona_id,
"gitHead": fields.git_head,
"nodeId": fields.node_id,
"modelInstanceId": fields.model_instance_id,
"organId": fields.organ_id,
"timestamp": fields.timestamp,
"previousHash": fields.previous_hash,
}))
.map_err(|error| format!("PERSONA_EVENT_HASH_INPUT_FAILED: {error}"))?;
Ok(PersonaLifecycleEvent {
sequence: fields.sequence,
kind: fields.kind.to_string(),
session_id: fields.session_id.to_string(),
persona_id: fields.persona_id.to_string(),
git_head: fields.git_head.to_string(),
node_id: fields.node_id.to_string(),
model_instance_id: fields.model_instance_id.to_string(),
organ_id: fields.organ_id.map(str::to_string),
timestamp: fields.timestamp.to_string(),
previous_hash: fields.previous_hash.to_string(),
event_hash: hex_digest(&hash_input),
})
}
fn acquire_primary_lease(
runtime_root: &Path,
persona_id: &str,
session_id: &str,
git_head: &str,
node_id: &str,
timestamp: &str,
) -> Result<PathBuf, String> {
let leases = runtime_root.join("leases");
fs::create_dir_all(&leases)
.map_err(|error| format!("PERSONA_LEASE_DIRECTORY_FAILED: {error}"))?;
let path = leases.join(format!("{persona_id}.json"));
let lease = PersonaPrimaryLease {
schema: "hololake.pncc-primary-lease/v1",
persona_id,
session_id,
git_head,
node_id,
acquired_at: timestamp,
};
let bytes = serde_json::to_vec_pretty(&lease)
.map_err(|error| format!("PERSONA_LEASE_SERIALIZATION_FAILED: {error}"))?;
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(&path)
.map_err(|error| {
if error.kind() == std::io::ErrorKind::AlreadyExists {
"PRIMARY_ALREADY_ACTIVE".to_string()
} else {
format!("PERSONA_LEASE_ACQUIRE_FAILED: {error}")
}
})?;
file.write_all(&bytes)
.map_err(|error| format!("PERSONA_LEASE_WRITE_FAILED: {error}"))?;
Ok(path)
}
fn write_event_journal(
runtime_root: &Path,
session_id: &str,
events: &[PersonaLifecycleEvent],
) -> Result<PathBuf, String> {
let session_dir = runtime_root.join("sessions").join(session_id);
fs::create_dir_all(&session_dir)
.map_err(|error| format!("PERSONA_SESSION_DIRECTORY_FAILED: {error}"))?;
let path = session_dir.join("events.jsonl");
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(&path)
.map_err(|error| format!("PERSONA_EVENT_JOURNAL_CREATE_FAILED: {error}"))?;
for event in events {
serde_json::to_writer(&mut file, event)
.map_err(|error| format!("PERSONA_EVENT_WRITE_FAILED: {error}"))?;
file.write_all(b"\n")
.map_err(|error| format!("PERSONA_EVENT_WRITE_FAILED: {error}"))?;
}
Ok(path)
}
fn prepare_wake_at(
runtime_root: &Path,
input: PersonaWakeInput,
raw_session_id: &str,
raw_timestamp: &str,
) -> Result<PersonaWakeReceipt, String> {
let session_id = validated_id("SESSION_ID", raw_session_id)?;
let timestamp = validated_text("TIMESTAMP", raw_timestamp, MAX_ID_BYTES)?;
let expected_persona_id = validated_id("EXPECTED_PERSONA_ID", &input.expected_persona_id)?;
let expected_head = validated_head(&input.expected_head)?;
let node_id = validated_id("NODE_ID", &input.node_id)?;
let model_instance_id = validated_id("MODEL_INSTANCE_ID", &input.model_instance_id)?;
let organ_id = validated_id("ORGAN_ID", &input.organ_id)?;
let (repository, git_head) = exact_repository(Path::new(&input.repository_path))?;
if git_head != expected_head {
return Err(format!(
"HEAD_MISMATCH: expected {expected_head}, observed {git_head}"
));
}
require_clean_repository(&repository)?;
let manifest = load_manifest(&repository)?;
if manifest.persona_id != expected_persona_id {
return Err("PERSONA_ID_MISMATCH".into());
}
validate_attribution(&input.attribution, &manifest)?;
let brain_entry = repository_file(&repository, &manifest.brain_entry)?;
let checkpoint = repository_file(&repository, &manifest.current_checkpoint)?;
let organ = manifest
.organs
.iter()
.find(|candidate| candidate.organ_id == organ_id && candidate.mode == "read-only")
.ok_or_else(|| {
"ORGAN_NOT_ALLOWED: first PNCC wake requires a declared read-only organ".to_string()
})?;
let lease_path = acquire_primary_lease(
runtime_root,
&manifest.persona_id,
&session_id,
&git_head,
&node_id,
&timestamp,
)?;
let result = (|| {
let waking = lifecycle_event(EventFields {
sequence: 1,
kind: "WAKING",
session_id: &session_id,
persona_id: &manifest.persona_id,
git_head: &git_head,
node_id: &node_id,
model_instance_id: &model_instance_id,
organ_id: None,
timestamp: &timestamp,
previous_hash: "GENESIS",
})?;
let brain_bound = lifecycle_event(EventFields {
sequence: 2,
kind: "BRAIN_BOUND",
session_id: &session_id,
persona_id: &manifest.persona_id,
git_head: &git_head,
node_id: &node_id,
model_instance_id: &model_instance_id,
organ_id: None,
timestamp: &timestamp,
previous_hash: &waking.event_hash,
})?;
let organ_active = lifecycle_event(EventFields {
sequence: 3,
kind: "ORGAN_ACTIVE",
session_id: &session_id,
persona_id: &manifest.persona_id,
git_head: &git_head,
node_id: &node_id,
model_instance_id: &model_instance_id,
organ_id: Some(&organ.organ_id),
timestamp: &timestamp,
previous_hash: &brain_bound.event_hash,
})?;
let events = vec![waking, brain_bound, organ_active];
let journal = write_event_journal(runtime_root, &session_id, &events)?;
let last_hash = &events.last().expect("three lifecycle events").event_hash;
Ok(PersonaWakeReceipt {
schema: "hololake.pncc-wake-receipt/v1",
receipt_id: format!("PNCC-WAKE-{}", &last_hash[..20]),
session_id,
persona_id: manifest.persona_id,
repository_path: repository.to_string_lossy().into_owned(),
git_head,
brain_entry: brain_entry.to_string_lossy().into_owned(),
checkpoint_path: checkpoint.to_string_lossy().into_owned(),
node_id,
model_instance_id,
runtime_state: "BOUND_NOT_INFERENCING",
model_inference_started: false,
active_organ: organ.organ_id.clone(),
organ_mode: "read-only",
event_journal: journal.to_string_lossy().into_owned(),
events,
attribution: input.attribution,
})
})();
if result.is_err() {
let _ = fs::remove_file(lease_path);
}
result
}
#[tauri::command]
pub fn prepare_persona_code_channel_wake(
input: PersonaWakeInput,
) -> Result<PersonaWakeReceipt, String> {
let runtime_root = crate::app_config::preferred_app_config_path("pncc-runtime")?;
let session_id = format!("PNCC-{}", Uuid::new_v4());
let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true);
prepare_wake_at(&runtime_root, input, &session_id, &timestamp)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::path::Path;
fn run_git(repo: &Path, args: &[&str]) {
let output = crate::git::git_command_at(repo)
.unwrap()
.args(args)
.output()
.unwrap();
assert!(
output.status.success(),
"git {args:?} failed: {}",
String::from_utf8_lossy(&output.stderr),
);
}
fn persona_repo() -> tempfile::TempDir {
let repo = tempfile::TempDir::new().unwrap();
fs::create_dir_all(repo.path().join(".hololake/persona")).unwrap();
fs::create_dir_all(repo.path().join("brain")).unwrap();
fs::write(
repo.path().join(".hololake/persona/manifest.json"),
r#"{
"schema": "hololake.persona/v1",
"personaId": "ICE-P-ZY001",
"humanResponsibilitySubject": "ICE-GL∞",
"brainEntry": "brain/CORE.hdlp",
"currentCheckpoint": ".hololake/persona/CURRENT.hdlp",
"organs": [{"organId":"fact-sense.repository","mode":"read-only"}]
}"#,
)
.unwrap();
fs::write(repo.path().join("brain/CORE.hdlp"), "# Persona brain\n").unwrap();
fs::write(
repo.path().join(".hololake/persona/CURRENT.hdlp"),
"# Current self\n",
)
.unwrap();
run_git(repo.path(), &["init", "-b", "main"]);
run_git(repo.path(), &["config", "user.name", "Test Persona"]);
run_git(
repo.path(),
&["config", "user.email", "persona@example.invalid"],
);
run_git(repo.path(), &["add", "."]);
run_git(repo.path(), &["commit", "-m", "persona fixture"]);
repo
}
fn head(repo: &Path) -> String {
let output = crate::git::git_command_at(repo)
.unwrap()
.args(["rev-parse", "HEAD"])
.output()
.unwrap();
String::from_utf8(output.stdout).unwrap().trim().to_string()
}
fn wake_input(repo: &Path) -> PersonaWakeInput {
PersonaWakeInput {
repository_path: repo.to_string_lossy().into_owned(),
expected_persona_id: "ICE-P-ZY001".into(),
expected_head: head(repo),
node_id: "LOCAL-NODE-001".into(),
model_instance_id: "MODEL-INSTANCE-001".into(),
organ_id: "fact-sense.repository".into(),
attribution: PersonaAttribution {
human_responsibility_subject: "ICE-GL∞".into(),
persona_cognitive_author: "ICE-P-ZY001".into(),
execution_runtime: "test-runtime".into(),
development_id: "DEV-20260810-014".into(),
authorization_scope: "READ_ONLY_PERSONA_WAKE".into(),
source_language_anchor: "开发PNCC人格运行层".into(),
},
}
}
#[test]
fn prepares_exact_persona_git_wake_with_hash_chained_events() {
let repo = persona_repo();
let runtime = tempfile::TempDir::new().unwrap();
let receipt = prepare_wake_at(
runtime.path(),
wake_input(repo.path()),
"PNCC-SESSION-001",
"2026-08-11T00:00:00.000Z",
)
.unwrap();
assert_eq!(receipt.persona_id, "ICE-P-ZY001");
assert_eq!(receipt.git_head, head(repo.path()));
assert_eq!(receipt.runtime_state, "BOUND_NOT_INFERENCING");
assert!(!receipt.model_inference_started);
assert_eq!(receipt.active_organ, "fact-sense.repository");
assert_eq!(receipt.events.len(), 3);
assert_eq!(receipt.events[0].kind, "WAKING");
assert_eq!(receipt.events[1].kind, "BRAIN_BOUND");
assert_eq!(receipt.events[2].kind, "ORGAN_ACTIVE");
assert_eq!(
receipt.events[1].previous_hash,
receipt.events[0].event_hash
);
assert_eq!(
receipt.events[2].previous_hash,
receipt.events[1].event_hash
);
assert_eq!(receipt.attribution.human_responsibility_subject, "ICE-GL∞");
assert_eq!(receipt.attribution.persona_cognitive_author, "ICE-P-ZY001");
}
#[test]
fn rejects_wrong_commit_undeclared_organ_and_second_primary() {
let repo = persona_repo();
let runtime = tempfile::TempDir::new().unwrap();
let mut wrong_head = wake_input(repo.path());
wrong_head.expected_head = "0".repeat(40);
assert!(prepare_wake_at(
runtime.path(),
wrong_head,
"PNCC-SESSION-WRONG",
"2026-08-11T00:00:00.000Z",
)
.unwrap_err()
.contains("HEAD_MISMATCH"));
let mut wrong_organ = wake_input(repo.path());
wrong_organ.organ_id = "execution-limb.shell".into();
assert!(prepare_wake_at(
runtime.path(),
wrong_organ,
"PNCC-SESSION-ORGAN",
"2026-08-11T00:00:00.000Z",
)
.unwrap_err()
.contains("ORGAN_NOT_ALLOWED"));
prepare_wake_at(
runtime.path(),
wake_input(repo.path()),
"PNCC-SESSION-PRIMARY",
"2026-08-11T00:00:00.000Z",
)
.unwrap();
assert!(prepare_wake_at(
runtime.path(),
wake_input(repo.path()),
"PNCC-SESSION-SECOND",
"2026-08-11T00:00:01.000Z",
)
.unwrap_err()
.contains("PRIMARY_ALREADY_ACTIVE"));
}
#[test]
fn rejects_manifest_paths_that_escape_the_persona_repository() {
let repo = persona_repo();
let runtime = tempfile::TempDir::new().unwrap();
let manifest_path = repo.path().join(".hololake/persona/manifest.json");
let mut manifest: serde_json::Value =
serde_json::from_slice(&fs::read(&manifest_path).unwrap()).unwrap();
manifest["brainEntry"] = serde_json::Value::String("../outside.hdlp".into());
fs::write(
&manifest_path,
serde_json::to_vec_pretty(&manifest).unwrap(),
)
.unwrap();
run_git(repo.path(), &["add", "."]);
run_git(repo.path(), &["commit", "-m", "unsafe manifest"]);
assert!(prepare_wake_at(
runtime.path(),
wake_input(repo.path()),
"PNCC-SESSION-ESCAPE",
"2026-08-11T00:00:00.000Z",
)
.unwrap_err()
.contains("PATH_OUTSIDE_REPOSITORY"));
}
#[test]
fn rejects_a_dirty_persona_git_before_acquiring_the_primary_lease() {
let repo = persona_repo();
let runtime = tempfile::TempDir::new().unwrap();
fs::write(
repo.path().join("uncommitted.txt"),
"not part of exact self\n",
)
.unwrap();
assert!(prepare_wake_at(
runtime.path(),
wake_input(repo.path()),
"PNCC-SESSION-DIRTY",
"2026-08-11T00:00:00.000Z",
)
.unwrap_err()
.contains("PERSONA_REPOSITORY_DIRTY"));
assert!(!runtime.path().join("leases/ICE-P-ZY001.json").exists());
}
}