feat: type PNCC organ contracts

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:39:09 +08:00
commit c3e7dbff10
10 changed files with 375 additions and 10 deletions

View file

@ -524,6 +524,7 @@ macro_rules! app_invoke_handler {
guanghu_enterprise::guanghu_enterprise_status,
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::run_persona_code_channel_fact_task,
persona_code_channel::inspect_persona_code_channel_session,
persona_code_channel::recover_persona_code_channel_session,

View file

@ -31,9 +31,39 @@ struct PersonaManifest {
#[serde(rename_all = "camelCase")]
struct PersonaOrgan {
organ_id: String,
#[serde(default)]
kind: Option<PersonaOrganKind>,
mode: String,
#[serde(default)]
paths: Vec<String>,
#[serde(default)]
input_schema: Option<String>,
#[serde(default)]
output_schema: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PersonaOrganKind {
FactSense,
MemoryMetabolism,
ExecutionLimb,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaOrganContract {
pub organ_id: String,
pub kind: PersonaOrganKind,
pub mode: String,
pub paths: Vec<String>,
pub input_schema: String,
pub output_schema: String,
pub permissions: Vec<&'static str>,
pub model_inference_allowed: bool,
pub reality_actions_allowed: bool,
pub activatable: bool,
pub implementation_state: &'static str,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
@ -109,11 +139,36 @@ pub struct PersonaWakeReceipt {
pub model_inference_started: bool,
pub active_organ: String,
pub organ_mode: &'static str,
pub organ_contract: PersonaOrganContract,
pub event_journal: String,
pub events: Vec<PersonaLifecycleEvent>,
pub attribution: PersonaAttribution,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaManifestInspectionInput {
pub repository_path: String,
pub expected_persona_id: String,
pub expected_head: String,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaManifestInspectionReceipt {
pub schema: &'static str,
pub persona_id: String,
pub repository_path: String,
pub git_head: String,
pub repository_clean: bool,
pub brain_entry: String,
pub current_checkpoint: String,
pub human_responsibility_subject: String,
pub git_author_name: String,
pub git_author_email: String,
pub organ_contracts: Vec<PersonaOrganContract>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaFactTaskInput {
@ -267,6 +322,18 @@ fn validated_head(value: &str) -> Result<String, String> {
}
}
fn validated_schema(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'.' | b'/'))
{
Ok(value)
} else {
Err(format!("{label}_INVALID"))
}
}
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());
@ -430,6 +497,141 @@ fn validate_model_binding(binding: &PersonaModelBinding) -> Result<(), String> {
Ok(())
}
fn organ_contract(organ: &PersonaOrgan) -> Result<PersonaOrganContract, String> {
let organ_id = validated_id("ORGAN_ID", &organ.organ_id)?;
let kind = match &organ.kind {
Some(kind) => kind.clone(),
None if organ_id == "fact-sense.repository" && organ.mode == "read-only" => {
PersonaOrganKind::FactSense
}
None => return Err("PERSONA_ORGAN_KIND_REQUIRED".into()),
};
let (
expected_mode,
default_input,
default_output,
permissions,
inference,
actions,
activatable,
state,
) = match kind {
PersonaOrganKind::FactSense => (
"read-only",
"hololake.pncc-fact-question/v1",
"hololake.pncc-fact-result/v1",
vec![
"READ_DECLARED_PATHS",
"RUN_MANIFEST_PINNED_MODEL",
"EMIT_STRUCTURED_FACTS",
],
true,
false,
true,
"IMPLEMENTED",
),
PersonaOrganKind::MemoryMetabolism => (
"checkpoint-write",
"hololake.pncc-checkpoint-candidate/v1",
"hololake.persona-checkpoint/v1",
vec![
"READ_CURRENT_CHECKPOINT",
"WRITE_NEW_CHECKPOINT",
"PROMOTE_MANIFEST_POINTER",
"COMMIT_PERSONA_GIT",
],
false,
false,
false,
"DECLARED_NOT_INDEPENDENTLY_ACTIVATABLE",
),
PersonaOrganKind::ExecutionLimb => (
"bounded-execution",
"hololake.pncc-action-request/v1",
"hololake.pncc-action-receipt/v1",
vec![
"REQUIRE_EXPLICIT_EXECUTOR",
"REQUIRE_ACTION_SCOPE",
"REQUIRE_REALITY_RECEIPT",
],
false,
true,
false,
"DECLARED_EXECUTOR_NOT_IMPLEMENTED",
),
};
if organ.mode != expected_mode {
return Err(format!(
"PERSONA_ORGAN_MODE_MISMATCH: {organ_id} requires {expected_mode}"
));
}
let input_schema = organ.input_schema.as_deref().unwrap_or(default_input);
let output_schema = organ.output_schema.as_deref().unwrap_or(default_output);
validated_schema("ORGAN_INPUT_SCHEMA", input_schema)?;
validated_schema("ORGAN_OUTPUT_SCHEMA", output_schema)?;
if organ.paths.is_empty() {
return Err("PERSONA_ORGAN_PATHS_REQUIRED".into());
}
Ok(PersonaOrganContract {
organ_id,
kind,
mode: organ.mode.clone(),
paths: organ.paths.clone(),
input_schema: input_schema.to_string(),
output_schema: output_schema.to_string(),
permissions,
model_inference_allowed: inference,
reality_actions_allowed: actions,
activatable,
implementation_state: state,
})
}
fn inspect_manifest_at(
input: PersonaManifestInspectionInput,
) -> Result<PersonaManifestInspectionReceipt, String> {
let expected_persona_id = validated_id("EXPECTED_PERSONA_ID", &input.expected_persona_id)?;
let expected_head = validated_head(&input.expected_head)?;
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}"
));
}
let clean = repository_is_clean(&repository)?;
let manifest = load_manifest(&repository)?;
if manifest.persona_id != expected_persona_id {
return Err("PERSONA_ID_MISMATCH".into());
}
validate_git_identity(&manifest.git_identity)?;
validate_model_binding(&manifest.model_binding)?;
repository_file(&repository, &manifest.brain_entry)?;
repository_file(&repository, &manifest.current_checkpoint)?;
let mut contracts = Vec::with_capacity(manifest.organs.len());
for organ in &manifest.organs {
for path in &organ.paths {
repository_file(&repository, path)?;
}
contracts.push(organ_contract(organ)?);
}
if contracts.is_empty() {
return Err("PERSONA_ORGANS_REQUIRED".into());
}
Ok(PersonaManifestInspectionReceipt {
schema: "hololake.pncc-manifest-inspection/v1",
persona_id: manifest.persona_id,
repository_path: repository.to_string_lossy().into_owned(),
git_head,
repository_clean: clean,
brain_entry: manifest.brain_entry,
current_checkpoint: manifest.current_checkpoint,
human_responsibility_subject: manifest.human_responsibility_subject,
git_author_name: manifest.git_identity.author_name,
git_author_email: manifest.git_identity.author_email,
organ_contracts: contracts,
})
}
fn hex_digest(bytes: &[u8]) -> String {
digest(&SHA256, bytes)
.as_ref()
@ -843,10 +1045,12 @@ fn prepare_wake_at(
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()
})?;
.find(|candidate| candidate.organ_id == organ_id)
.ok_or_else(|| "ORGAN_NOT_DECLARED".to_string())?;
let contract = organ_contract(organ)?;
if contract.kind != PersonaOrganKind::FactSense || !contract.activatable {
return Err("ORGAN_NOT_ACTIVATABLE: only the implemented fact sense may wake".into());
}
for path in &organ.paths {
repository_file(&repository, path)?;
}
@ -943,6 +1147,7 @@ fn prepare_wake_at(
model_inference_started: false,
active_organ: organ.organ_id.clone(),
organ_mode: "read-only",
organ_contract: contract,
event_journal: journal.to_string_lossy().into_owned(),
events,
attribution: input.attribution,
@ -1590,6 +1795,13 @@ pub fn prepare_persona_code_channel_wake(
prepare_wake_at(&runtime_root, input, &session_id, &timestamp)
}
#[tauri::command]
pub fn inspect_persona_code_channel_manifest(
input: PersonaManifestInspectionInput,
) -> Result<PersonaManifestInspectionReceipt, String> {
inspect_manifest_at(input)
}
#[tauri::command]
pub async fn run_persona_code_channel_fact_task(
input: PersonaFactTaskInput,
@ -1692,8 +1904,11 @@ mod tests {
},
"organs": [{
"organId":"fact-sense.repository",
"kind":"FACT_SENSE",
"mode":"read-only",
"paths":["brain/CORE.hdlp", ".hololake/persona/CURRENT.hdlp"]
"paths":["brain/CORE.hdlp", ".hololake/persona/CURRENT.hdlp"],
"inputSchema":"hololake.pncc-fact-question/v1",
"outputSchema":"hololake.pncc-fact-result/v1"
}]
}"#,
)
@ -1784,6 +1999,14 @@ mod tests {
}
}
fn manifest_inspection(repo: &Path) -> PersonaManifestInspectionInput {
PersonaManifestInspectionInput {
repository_path: repo.to_string_lossy().into_owned(),
expected_persona_id: "ICE-P-ZY001".into(),
expected_head: head(repo),
}
}
#[test]
fn prepares_exact_persona_git_wake_with_hash_chained_events() {
let repo = persona_repo();
@ -1801,6 +2024,10 @@ mod tests {
assert_eq!(receipt.runtime_state, "BOUND_NOT_INFERENCING");
assert!(!receipt.model_inference_started);
assert_eq!(receipt.active_organ, "fact-sense.repository");
assert_eq!(receipt.organ_contract.kind, PersonaOrganKind::FactSense);
assert!(receipt.organ_contract.model_inference_allowed);
assert!(!receipt.organ_contract.reality_actions_allowed);
assert!(receipt.organ_contract.activatable);
assert_eq!(receipt.events.len(), 3);
assert_eq!(receipt.events[0].kind, "WAKING");
assert_eq!(receipt.events[1].kind, "BRAIN_BOUND");
@ -1842,7 +2069,7 @@ mod tests {
"2026-08-11T00:00:00.000Z",
)
.unwrap_err()
.contains("ORGAN_NOT_ALLOWED"));
.contains("ORGAN_NOT_DECLARED"));
prepare_wake_at(
runtime.path(),
@ -2177,4 +2404,71 @@ mod tests {
assert_eq!(receipt.recovery_action, "RELEASED_DORMANT_LEASE");
assert!(!runtime.path().join("leases/ICE-P-ZY001.json").exists());
}
#[test]
fn inspects_normalized_organ_contracts_without_starting_a_runtime() {
let repo = persona_repo();
let receipt = inspect_manifest_at(manifest_inspection(repo.path())).unwrap();
assert_eq!(receipt.persona_id, "ICE-P-ZY001");
assert!(receipt.repository_clean);
assert_eq!(receipt.organ_contracts.len(), 1);
let contract = &receipt.organ_contracts[0];
assert_eq!(contract.kind, PersonaOrganKind::FactSense);
assert_eq!(contract.mode, "read-only");
assert!(contract.activatable);
assert!(contract.model_inference_allowed);
assert!(!contract.reality_actions_allowed);
assert_eq!(contract.implementation_state, "IMPLEMENTED");
}
#[test]
fn declares_but_refuses_an_execution_limb_without_an_executor() {
let repo = persona_repo();
let repository = repo.path().canonicalize().unwrap();
let manifest_path = repository.join(MANIFEST_PATH);
let mut manifest: serde_json::Value =
serde_json::from_slice(&fs::read(&manifest_path).unwrap()).unwrap();
manifest["organs"]
.as_array_mut()
.unwrap()
.push(serde_json::json!({
"organId": "execution-limb.shell",
"kind": "EXECUTION_LIMB",
"mode": "bounded-execution",
"paths": ["brain/CORE.hdlp"],
"inputSchema": "hololake.pncc-action-request/v1",
"outputSchema": "hololake.pncc-action-receipt/v1"
}));
fs::write(
&manifest_path,
serde_json::to_vec_pretty(&manifest).unwrap(),
)
.unwrap();
run_git(&repository, &["add", "."]);
run_git(&repository, &["commit", "-m", "declare execution limb"]);
let inspection = inspect_manifest_at(manifest_inspection(&repository)).unwrap();
let execution = inspection
.organ_contracts
.iter()
.find(|contract| contract.kind == PersonaOrganKind::ExecutionLimb)
.unwrap();
assert!(execution.reality_actions_allowed);
assert!(!execution.activatable);
assert_eq!(
execution.implementation_state,
"DECLARED_EXECUTOR_NOT_IMPLEMENTED"
);
let mut input = wake_input(&repository);
input.organ_id = "execution-limb.shell".into();
assert!(prepare_wake_at(
tempfile::TempDir::new().unwrap().path(),
input,
"PNCC-SESSION-EXECUTION-REFUSED",
"2026-08-11T00:00:00.000Z",
)
.unwrap_err()
.contains("ORGAN_NOT_ACTIVATABLE"));
}
}