feat(hololake): add stage-one read-only PNCC core

This commit is contained in:
冰朔 2026-08-13 13:17:30 +08:00
commit f4c896d15c
11 changed files with 2539 additions and 6 deletions

View file

@ -7,6 +7,17 @@ use crate::dynamic_capability_routing::{
routing_root as dynamic_routing_root, DynamicNodeRegistry, ResolveCapabilityRouteInput,
SignedNodeHealth,
};
use crate::pncc_receipt_projection::{
append_remote_read_at as project_pncc_remote_read_at,
append_repository_binding_at as project_pncc_repository_binding_at,
query_at as query_pncc_projection_at, QueryPnccReceiptProjectionInput,
};
use crate::pncc_remote_git::{
read_mounted_remote_object_at as read_mounted_pncc_remote_at, ReadMountedPnccRemoteObjectInput,
};
use crate::pncc_repository_binding::{
inspect_mounted_at as inspect_mounted_pncc_at, InspectMountedPnccRepositoryInput,
};
use ring::digest::{digest, SHA256};
use serde::{Deserialize, Serialize};
use serde_json::Value;
@ -57,6 +68,9 @@ enum BrokerRequest {
ResolveCapabilityRoute(AuthenticatedRouteInput),
InstallDynamicNodeRegistry(AuthenticatedRegistryInput),
RecordSignedNodeHealth(AuthenticatedHealthInput),
InspectMountedPnccRepository(AuthenticatedPnccMountInput),
ReadMountedPnccRemoteObject(AuthenticatedPnccRemoteReadInput),
QueryPnccReceiptProjection(AuthenticatedPnccProjectionQueryInput),
}
#[derive(Debug, Deserialize)]
@ -80,6 +94,27 @@ struct AuthenticatedHealthInput {
health: SignedNodeHealth,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct AuthenticatedPnccMountInput {
session: AuthenticateSessionInput,
mount: InspectMountedPnccRepositoryInput,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct AuthenticatedPnccRemoteReadInput {
session: AuthenticateSessionInput,
read: ReadMountedPnccRemoteObjectInput,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct AuthenticatedPnccProjectionQueryInput {
session: AuthenticateSessionInput,
query: QueryPnccReceiptProjectionInput,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct BrokerResponse {
@ -205,6 +240,27 @@ fn start_at(
descriptor_path: PathBuf,
socket_path: PathBuf,
) -> Result<DirectLocalBrokerHandle, String> {
let pncc_mount_root = session_root
.parent()
.ok_or("HOLOLAKE_BROKER_PNCC_STORAGE_BOUNDARY_INVALID")?
.join("pncc-stage-one-v1")
.join("repository-mounts");
fs::create_dir_all(&pncc_mount_root)
.map_err(|error| format!("HOLOLAKE_BROKER_PNCC_STORAGE_UNAVAILABLE: {error}"))?;
let pncc_remote_root = session_root
.parent()
.ok_or("HOLOLAKE_BROKER_PNCC_STORAGE_BOUNDARY_INVALID")?
.join("pncc-stage-one-v1")
.join("remote-object-channel");
fs::create_dir_all(&pncc_remote_root)
.map_err(|error| format!("HOLOLAKE_BROKER_PNCC_STORAGE_UNAVAILABLE: {error}"))?;
let pncc_projection_root = session_root
.parent()
.ok_or("HOLOLAKE_BROKER_PNCC_STORAGE_BOUNDARY_INVALID")?
.join("pncc-stage-one-v1")
.join("receipt-projection");
fs::create_dir_all(&pncc_projection_root)
.map_err(|error| format!("HOLOLAKE_BROKER_PNCC_STORAGE_UNAVAILABLE: {error}"))?;
if let Some(parent) = socket_path.parent() {
fs::create_dir_all(parent)
.map_err(|error| format!("HOLOLAKE_BROKER_RUNTIME_DIR_FAILED: {error}"))?;
@ -247,7 +303,15 @@ fn start_at(
.name("hololake-direct-local-broker".into())
.spawn(move || {
let _ = ready_sender.send(());
serve(listener, session_root, routing_root, &worker_shutdown);
serve(
listener,
session_root,
routing_root,
pncc_mount_root,
pncc_remote_root,
pncc_projection_root,
&worker_shutdown,
);
let _ = fs::remove_file(worker_socket);
})
.map_err(|error| format!("HOLOLAKE_BROKER_THREAD_FAILED: {error}"))?;
@ -266,6 +330,9 @@ fn serve(
listener: UnixListener,
session_root: PathBuf,
routing_root: PathBuf,
pncc_mount_root: PathBuf,
pncc_remote_root: PathBuf,
pncc_projection_root: PathBuf,
shutdown: &AtomicBool,
) {
while !shutdown.load(Ordering::Acquire) {
@ -276,9 +343,21 @@ fn serve(
}
let root = session_root.clone();
let routes = routing_root.clone();
let pncc_mounts = pncc_mount_root.clone();
let pncc_remote = pncc_remote_root.clone();
let pncc_projection = pncc_projection_root.clone();
let _ = thread::Builder::new()
.name("hololake-direct-local-client".into())
.spawn(move || serve_connection(stream, &root, &routes));
.spawn(move || {
serve_connection(
stream,
&root,
&routes,
&pncc_mounts,
&pncc_remote,
&pncc_projection,
)
});
}
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
thread::sleep(Duration::from_millis(25));
@ -288,7 +367,14 @@ fn serve(
}
}
fn serve_connection(mut stream: UnixStream, session_root: &Path, routing_root: &Path) {
fn serve_connection(
mut stream: UnixStream,
session_root: &Path,
routing_root: &Path,
pncc_mount_root: &Path,
pncc_remote_root: &Path,
pncc_projection_root: &Path,
) {
let read_stream = match stream.try_clone() {
Ok(stream) => stream,
Err(_) => return,
@ -310,7 +396,14 @@ fn serve_connection(mut stream: UnixStream, session_root: &Path, routing_root: &
let response = if read as u64 > MAX_REQUEST_BYTES || !bytes.ends_with(b"\n") {
BrokerResponse::error("HOLOLAKE_BROKER_REQUEST_TOO_LARGE")
} else {
dispatch(session_root, routing_root, &bytes[..bytes.len() - 1])
dispatch(
session_root,
routing_root,
pncc_mount_root,
pncc_remote_root,
pncc_projection_root,
&bytes[..bytes.len() - 1],
)
};
if serde_json::to_writer(&mut stream, &response).is_err()
|| stream.write_all(b"\n").is_err()
@ -324,7 +417,14 @@ fn serve_connection(mut stream: UnixStream, session_root: &Path, routing_root: &
}
}
fn dispatch(session_root: &Path, routing_root: &Path, bytes: &[u8]) -> BrokerResponse {
fn dispatch(
session_root: &Path,
routing_root: &Path,
pncc_mount_root: &Path,
pncc_remote_root: &Path,
pncc_projection_root: &Path,
bytes: &[u8],
) -> BrokerResponse {
let request: BrokerRequest = match serde_json::from_slice(bytes) {
Ok(request) => request,
Err(error) => {
@ -373,6 +473,32 @@ fn dispatch(session_root: &Path, routing_root: &Path, bytes: &[u8]) -> BrokerRes
serde_json::to_value(receipt).map_err(|error| error.to_string())
})
}
BrokerRequest::InspectMountedPnccRepository(input) => {
if let Err(error) = authenticate_at(session_root, &input.session) {
return BrokerResponse::error(&error);
}
inspect_mounted_pncc_at(pncc_mount_root, input.mount).and_then(|receipt| {
project_pncc_repository_binding_at(pncc_projection_root, &receipt)?;
serde_json::to_value(receipt).map_err(|error| error.to_string())
})
}
BrokerRequest::ReadMountedPnccRemoteObject(input) => {
if let Err(error) = authenticate_at(session_root, &input.session) {
return BrokerResponse::error(&error);
}
read_mounted_pncc_remote_at(pncc_remote_root, input.read).and_then(|receipt| {
project_pncc_remote_read_at(pncc_projection_root, &receipt)?;
serde_json::to_value(receipt).map_err(|error| error.to_string())
})
}
BrokerRequest::QueryPnccReceiptProjection(input) => {
if let Err(error) = authenticate_at(session_root, &input.session) {
return BrokerResponse::error(&error);
}
query_pncc_projection_at(pncc_projection_root, input.query).and_then(|receipt| {
serde_json::to_value(receipt).map_err(|error| error.to_string())
})
}
};
match result {
Ok(value) => BrokerResponse::success(value),
@ -654,4 +780,64 @@ mod tests {
"HOLOLAKE_DYNAMIC_ROUTING_TRUST_UNPROVISIONED"
);
}
#[test]
fn pncc_projection_is_read_only_and_requires_the_active_session_secret() {
let temp = TempDir::new().unwrap();
let socket = temp.path().join("runtime/broker.sock");
let descriptor = temp.path().join("broker.json");
let sessions = temp.path().join("sessions");
let routes = temp.path().join("routes");
fs::create_dir_all(&sessions).unwrap();
fs::create_dir_all(&routes).unwrap();
let _broker = start_at(sessions.clone(), routes, descriptor, socket.clone()).unwrap();
let ticket = issue_ticket_at(
&sessions,
IssueDiscoveryTicketInput {
account_id: "human-1".into(),
lane_id: "DEV-1".into(),
client_instance_id: "codex-1".into(),
},
)
.unwrap();
let opened = request(
&socket,
serde_json::json!({
"operation": "OPEN_SESSION",
"input": {
"accountId": "human-1",
"laneId": "DEV-1",
"clientInstanceId": "codex-1",
"discoveryTicket": ticket.discovery_ticket
}
}),
);
let query = |secret: Value| {
request(
&socket,
serde_json::json!({
"operation": "QUERY_PNCC_RECEIPT_PROJECTION",
"input": {
"session": {
"accountId": "human-1",
"sessionId": opened["result"]["sessionId"],
"resumeSecret": secret
},
"query": {"afterSequence": 0, "limit": 25}
}
}),
)
};
let rejected = query(Value::String("wrong-secret-long-enough".into()));
assert_eq!(rejected["ok"], false);
assert_eq!(rejected["error"], "HOLOLAKE_DIRECT_SESSION_NOT_AUTHORIZED");
let verified = query(opened["result"]["resumeSecret"].clone());
assert_eq!(verified["ok"], true);
assert_eq!(verified["result"]["authorityStore"], false);
assert_eq!(verified["result"]["writeAuthority"], false);
assert_eq!(verified["result"]["modelInstanceFieldsPresent"], false);
assert_eq!(verified["result"]["emptyMeansOffline"], false);
assert_eq!(verified["result"]["returnedEventCount"], 0);
}
}

View file

@ -2,6 +2,9 @@ mod direct_local_broker;
mod direct_local_session;
mod dynamic_capability_routing;
mod local_development_bridge;
mod pncc_receipt_projection;
mod pncc_remote_git;
mod pncc_repository_binding;
mod release_trust;
use tauri::Manager;
@ -21,6 +24,8 @@ pub fn run() {
local_development_bridge::acquire_development_write_lane,
local_development_bridge::inspect_development_write_lane,
local_development_bridge::release_development_write_lane,
pncc_repository_binding::inspect_mounted_pncc_repository,
pncc_receipt_projection::query_pncc_receipt_projection,
])
.setup(|app| {
let broker = direct_local_broker::start(app.handle())?;

View file

@ -0,0 +1,476 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use crate::pncc_remote_git::PnccRemoteObjectReceipt;
use crate::pncc_repository_binding::PnccRepositoryMountReceipt;
use fs2::FileExt;
use ring::digest::{digest, SHA256};
use serde::{Deserialize, Serialize};
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use tauri::{AppHandle, Manager};
use uuid::Uuid;
const JOURNAL_SCHEMA: &str = "hololake.pncc-stage-one-receipt-projection-journal/v1";
const EVENT_SCHEMA: &str = "hololake.pncc-stage-one-receipt-projection-event/v1";
const MAX_EVENTS: usize = 4096;
const MAX_QUERY_LIMIT: usize = 100;
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct QueryPnccReceiptProjectionInput {
pub after_sequence: Option<u64>,
pub limit: Option<usize>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct ProjectionJournal {
schema: String,
events: Vec<PnccReceiptProjectionEvent>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PnccReceiptProjectionEvent {
pub schema: String,
pub sequence: u64,
pub kind: String,
pub mount_id: String,
pub persona_id: Option<String>,
pub human_responsibility_subject: Option<String>,
pub git_head: String,
pub relative_path: Option<String>,
pub object_sha256: String,
pub source_receipt_id: String,
pub observed_at_unix_ms: u128,
pub previous_event_hash: String,
pub event_hash: String,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PnccReceiptProjectionReceipt {
pub schema: &'static str,
pub state: &'static str,
pub authority_store: bool,
pub write_authority: bool,
pub model_instance_fields_present: bool,
pub empty_means_offline: bool,
pub after_sequence: u64,
pub returned_event_count: usize,
pub last_sequence: u64,
pub has_more: bool,
pub events: Vec<PnccReceiptProjectionEvent>,
pub receipt_id: String,
}
#[derive(Debug)]
struct ProjectionInput {
kind: &'static str,
mount_id: String,
persona_id: Option<String>,
human_responsibility_subject: Option<String>,
git_head: String,
relative_path: Option<String>,
object_sha256: String,
source_receipt_id: String,
}
#[tauri::command]
pub async fn query_pncc_receipt_projection(
app: AppHandle,
input: QueryPnccReceiptProjectionInput,
) -> Result<PnccReceiptProjectionReceipt, String> {
let root = pncc_projection_root(&app)?;
tauri::async_runtime::spawn_blocking(move || query_at(&root, input))
.await
.map_err(|error| format!("PNCC_PROJECTION_JOIN_FAILED: {error}"))?
}
pub(crate) fn pncc_projection_root(app: &AppHandle) -> Result<PathBuf, String> {
let root = app
.path()
.app_data_dir()
.map_err(|error| format!("PNCC_APP_DATA_UNAVAILABLE: {error}"))?
.join("pncc-stage-one-v1")
.join("receipt-projection");
fs::create_dir_all(&root)
.map_err(|error| format!("PNCC_PROJECTION_STORAGE_UNAVAILABLE: {error}"))?;
root.canonicalize()
.map_err(|error| format!("PNCC_PROJECTION_STORAGE_UNAVAILABLE: {error}"))
}
pub(crate) fn append_repository_binding_at(
root: &Path,
receipt: &PnccRepositoryMountReceipt,
) -> Result<PnccReceiptProjectionEvent, String> {
append_at(
root,
ProjectionInput {
kind: "REPOSITORY_BINDING_REVALIDATED",
mount_id: receipt.mount_id.clone(),
persona_id: Some(receipt.binding.persona_id.clone()),
human_responsibility_subject: Some(
receipt.binding.human_responsibility_subject.clone(),
),
git_head: receipt.binding.git_head.clone(),
relative_path: Some(receipt.binding.manifest.relative_path.clone()),
object_sha256: receipt.binding.manifest.sha256.clone(),
source_receipt_id: receipt.binding.receipt_id.clone(),
},
)
}
pub(crate) fn append_remote_read_at(
root: &Path,
receipt: &PnccRemoteObjectReceipt,
) -> Result<PnccReceiptProjectionEvent, String> {
append_at(
root,
ProjectionInput {
kind: "REMOTE_OBJECT_READ_VERIFIED",
mount_id: receipt.mount_id.clone(),
persona_id: None,
human_responsibility_subject: None,
git_head: receipt.remote_head.clone(),
relative_path: Some(receipt.relative_path.clone()),
object_sha256: receipt.content_sha256.clone(),
source_receipt_id: receipt.receipt_id.clone(),
},
)
}
fn append_at(root: &Path, input: ProjectionInput) -> Result<PnccReceiptProjectionEvent, String> {
let _lock = lock_projection(root)?;
let mut journal = load_and_verify(root)?;
if let Some(existing) = journal
.events
.iter()
.find(|event| event.source_receipt_id == input.source_receipt_id)
{
return Ok(existing.clone());
}
if journal.events.len() >= MAX_EVENTS {
return Err("PNCC_PROJECTION_CAPACITY_REACHED".into());
}
validate_sha256(&input.object_sha256)?;
validate_sha256(&input.source_receipt_id)?;
validate_head(&input.git_head)?;
let sequence = journal.events.len() as u64 + 1;
let observed_at_unix_ms = now_unix_ms()?;
let previous_event_hash = journal
.events
.last()
.map(|event| event.event_hash.clone())
.unwrap_or_else(|| "0".repeat(64));
let event_hash = hash_event(
sequence,
input.kind,
&input.mount_id,
input.persona_id.as_deref(),
input.human_responsibility_subject.as_deref(),
&input.git_head,
input.relative_path.as_deref(),
&input.object_sha256,
&input.source_receipt_id,
observed_at_unix_ms,
&previous_event_hash,
);
let event = PnccReceiptProjectionEvent {
schema: EVENT_SCHEMA.into(),
sequence,
kind: input.kind.into(),
mount_id: input.mount_id,
persona_id: input.persona_id,
human_responsibility_subject: input.human_responsibility_subject,
git_head: input.git_head,
relative_path: input.relative_path,
object_sha256: input.object_sha256,
source_receipt_id: input.source_receipt_id,
observed_at_unix_ms,
previous_event_hash,
event_hash,
};
journal.events.push(event.clone());
write_journal(root, &journal)?;
Ok(event)
}
pub(crate) fn query_at(
root: &Path,
input: QueryPnccReceiptProjectionInput,
) -> Result<PnccReceiptProjectionReceipt, String> {
let _lock = lock_projection(root)?;
let journal = load_and_verify(root)?;
let after_sequence = input.after_sequence.unwrap_or(0);
let limit = input.limit.unwrap_or(25);
if limit == 0 || limit > MAX_QUERY_LIMIT {
return Err("PNCC_PROJECTION_QUERY_LIMIT_INVALID".into());
}
let last_sequence = journal.events.len() as u64;
if after_sequence > last_sequence {
return Err("PNCC_PROJECTION_CURSOR_INVALID".into());
}
let mut available = journal
.events
.iter()
.filter(|event| event.sequence > after_sequence);
let events = available.by_ref().take(limit).cloned().collect::<Vec<_>>();
let has_more = available.next().is_some();
let receipt_id = sha256_hex(
format!(
"{}\n{}\n{}\n{}",
after_sequence,
events.len(),
last_sequence,
events
.last()
.map(|event| event.event_hash.as_str())
.unwrap_or("EMPTY")
)
.as_bytes(),
);
Ok(PnccReceiptProjectionReceipt {
schema: "hololake.pncc-stage-one-receipt-projection/v1",
state: "VERIFIED_READ_ONLY_PROJECTION",
authority_store: false,
write_authority: false,
model_instance_fields_present: false,
empty_means_offline: false,
after_sequence,
returned_event_count: events.len(),
last_sequence,
has_more,
events,
receipt_id,
})
}
fn load_and_verify(root: &Path) -> Result<ProjectionJournal, String> {
let path = journal_path(root);
if !path.exists() {
return Ok(ProjectionJournal {
schema: JOURNAL_SCHEMA.into(),
events: Vec::new(),
});
}
let metadata = fs::symlink_metadata(&path)
.map_err(|error| format!("PNCC_PROJECTION_READ_FAILED: {error}"))?;
if metadata.file_type().is_symlink() || !metadata.is_file() {
return Err("PNCC_PROJECTION_JOURNAL_INVALID".into());
}
let journal: ProjectionJournal = serde_json::from_slice(
&fs::read(&path).map_err(|error| format!("PNCC_PROJECTION_READ_FAILED: {error}"))?,
)
.map_err(|error| format!("PNCC_PROJECTION_JOURNAL_INVALID: {error}"))?;
if journal.schema != JOURNAL_SCHEMA || journal.events.len() > MAX_EVENTS {
return Err("PNCC_PROJECTION_JOURNAL_INVALID".into());
}
let mut previous = "0".repeat(64);
for (index, event) in journal.events.iter().enumerate() {
if event.schema != EVENT_SCHEMA
|| event.sequence != index as u64 + 1
|| event.previous_event_hash != previous
|| event.event_hash
!= hash_event(
event.sequence,
&event.kind,
&event.mount_id,
event.persona_id.as_deref(),
event.human_responsibility_subject.as_deref(),
&event.git_head,
event.relative_path.as_deref(),
&event.object_sha256,
&event.source_receipt_id,
event.observed_at_unix_ms,
&event.previous_event_hash,
)
{
return Err("PNCC_PROJECTION_EVENT_CHAIN_INVALID".into());
}
previous = event.event_hash.clone();
}
Ok(journal)
}
#[allow(clippy::too_many_arguments)]
fn hash_event(
sequence: u64,
kind: &str,
mount_id: &str,
persona_id: Option<&str>,
human: Option<&str>,
git_head: &str,
relative_path: Option<&str>,
object_sha256: &str,
source_receipt_id: &str,
observed_at_unix_ms: u128,
previous: &str,
) -> String {
sha256_hex(
format!(
"{sequence}\n{kind}\n{mount_id}\n{}\n{}\n{git_head}\n{}\n{object_sha256}\n{source_receipt_id}\n{observed_at_unix_ms}\n{previous}",
persona_id.unwrap_or(""),
human.unwrap_or(""),
relative_path.unwrap_or("")
)
.as_bytes(),
)
}
fn lock_projection(root: &Path) -> Result<std::fs::File, String> {
fs::create_dir_all(root)
.map_err(|error| format!("PNCC_PROJECTION_STORAGE_UNAVAILABLE: {error}"))?;
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(root.join("projection.lock"))
.map_err(|error| format!("PNCC_PROJECTION_LOCK_FAILED: {error}"))?;
file.lock_exclusive()
.map_err(|error| format!("PNCC_PROJECTION_LOCK_FAILED: {error}"))?;
Ok(file)
}
fn write_journal(root: &Path, journal: &ProjectionJournal) -> Result<(), String> {
let temporary = root.join(format!(".journal-{}.tmp", Uuid::new_v4()));
let bytes = serde_json::to_vec_pretty(journal)
.map_err(|error| format!("PNCC_PROJECTION_JOURNAL_INVALID: {error}"))?;
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(&temporary)
.map_err(|error| format!("PNCC_PROJECTION_WRITE_FAILED: {error}"))?;
file.write_all(&bytes)
.and_then(|_| file.sync_all())
.map_err(|error| format!("PNCC_PROJECTION_WRITE_FAILED: {error}"))?;
fs::rename(&temporary, journal_path(root))
.map_err(|error| format!("PNCC_PROJECTION_WRITE_FAILED: {error}"))
}
fn journal_path(root: &Path) -> PathBuf {
root.join("journal.json")
}
fn validate_sha256(value: &str) -> Result<(), String> {
if value.len() == 64
&& value
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
{
Ok(())
} else {
Err("PNCC_PROJECTION_SHA256_INVALID".into())
}
}
fn validate_head(value: &str) -> Result<(), String> {
if value.len() == 40
&& value
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
{
Ok(())
} else {
Err("PNCC_PROJECTION_GIT_HEAD_INVALID".into())
}
}
fn now_unix_ms() -> Result<u128, String> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_millis())
.map_err(|error| format!("PNCC_SYSTEM_CLOCK_INVALID: {error}"))
}
fn sha256_hex(bytes: &[u8]) -> String {
digest(&SHA256, bytes)
.as_ref()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pncc_remote_git::PnccRemoteObjectReceipt;
use tempfile::TempDir;
fn remote_receipt(id: char, head: char) -> PnccRemoteObjectReceipt {
PnccRemoteObjectReceipt {
schema: "hololake.pncc-stage-one-remote-object-read/v1",
mount_id: "persona-primary".into(),
remote_identity_hash: "a".repeat(64),
remote_head: head.to_string().repeat(40),
previous_verified_head: None,
cursor_advanced: true,
continuity_verified: true,
fetched_incremental_objects: true,
cache_rehydrated: false,
relative_path: "brain/CORE.hldp".into(),
git_object_id: "b".repeat(40),
content: "content".into(),
content_sha256: "c".repeat(64),
cache_bytes: 1,
cache_limit_bytes: 1024,
cache_state: "BOUNDED_PARTIAL_OBJECT_CACHE",
worktree_created: false,
full_history_requested: false,
model_inference_started: false,
reality_execution_allowed: false,
receipt_id: id.to_string().repeat(64),
}
}
#[test]
fn projection_is_hash_chained_bounded_and_never_an_authority_store() {
let temp = TempDir::new().unwrap();
append_remote_read_at(temp.path(), &remote_receipt('d', '1')).unwrap();
append_remote_read_at(temp.path(), &remote_receipt('e', '2')).unwrap();
let projection = query_at(
temp.path(),
QueryPnccReceiptProjectionInput {
after_sequence: Some(0),
limit: Some(1),
},
)
.unwrap();
assert!(!projection.authority_store);
assert!(!projection.write_authority);
assert!(!projection.model_instance_fields_present);
assert!(!projection.empty_means_offline);
assert_eq!(projection.returned_event_count, 1);
assert!(projection.has_more);
let source = serde_json::to_string(&projection.events).unwrap();
assert!(!source.to_ascii_lowercase().contains("modelinstance"));
}
#[test]
fn exact_retry_is_idempotent_and_tampering_is_rejected() {
let temp = TempDir::new().unwrap();
let receipt = remote_receipt('d', '1');
let first = append_remote_read_at(temp.path(), &receipt).unwrap();
let duplicate = append_remote_read_at(temp.path(), &receipt).unwrap();
assert_eq!(first.event_hash, duplicate.event_hash);
let path = journal_path(temp.path());
let mut journal: serde_json::Value =
serde_json::from_slice(&fs::read(&path).unwrap()).unwrap();
journal["events"][0]["kind"] = serde_json::json!("FORGED");
fs::write(&path, serde_json::to_vec_pretty(&journal).unwrap()).unwrap();
assert_eq!(
query_at(
temp.path(),
QueryPnccReceiptProjectionInput {
after_sequence: None,
limit: None,
}
)
.unwrap_err(),
"PNCC_PROJECTION_EVENT_CHAIN_INVALID"
);
}
}

View file

@ -0,0 +1,944 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Clean-room stage-one implementation. Contract evidence is recorded in
// audit/pncc-migration-provenance.json; no donor source was copied.
use fs2::FileExt;
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 std::process::{Command, Output};
use uuid::Uuid;
const REMOTE_MOUNT_SCHEMA: &str = "hololake.pncc-stage-one-remote-mount/v1";
const CURSOR_SCHEMA: &str = "hololake.pncc-stage-one-remote-cursor/v1";
#[cfg(test)]
const DEFAULT_BRANCH: &str = "main";
const DEFAULT_CACHE_LIMIT_BYTES: u64 = 128 * 1024 * 1024;
const MIN_CACHE_LIMIT_BYTES: u64 = 1024 * 1024;
const MAX_CACHE_LIMIT_BYTES: u64 = 4 * 1024 * 1024 * 1024;
const MAX_TEXT_OBJECT_BYTES: usize = 2 * 1024 * 1024;
const MAX_CONTINUITY_DEPTH: usize = 4096;
#[cfg(test)]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct RegisterPnccRemoteMountInput {
pub mount_id: String,
pub remote_url: String,
pub branch: Option<String>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ReadMountedPnccRemoteObjectInput {
pub mount_id: String,
pub relative_path: String,
pub expected_head: Option<String>,
pub max_cache_bytes: Option<u64>,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PnccRemoteObjectReceipt {
pub schema: &'static str,
pub mount_id: String,
pub remote_identity_hash: String,
pub remote_head: String,
pub previous_verified_head: Option<String>,
pub cursor_advanced: bool,
pub continuity_verified: bool,
pub fetched_incremental_objects: bool,
pub cache_rehydrated: bool,
pub relative_path: String,
pub git_object_id: String,
pub content: String,
pub content_sha256: String,
pub cache_bytes: u64,
pub cache_limit_bytes: u64,
pub cache_state: &'static str,
pub worktree_created: bool,
pub full_history_requested: bool,
pub model_inference_started: bool,
pub reality_execution_allowed: bool,
pub receipt_id: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct RemoteMountRecord {
schema: String,
mount_id: String,
remote_url: String,
remote_identity_hash: String,
branch: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct CursorRecord {
schema: String,
remote_identity_hash: String,
branch: String,
verified_head: String,
previous_verified_head: Option<String>,
generation: u64,
}
#[cfg(test)]
fn register_remote_mount_at(
root: &Path,
input: RegisterPnccRemoteMountInput,
allow_file_remote: bool,
) -> Result<(), String> {
validate_machine_id(&input.mount_id, "REMOTE_MOUNT_ID")?;
validate_remote_url(&input.remote_url, allow_file_remote)?;
let branch = input.branch.unwrap_or_else(|| DEFAULT_BRANCH.into());
validate_branch(&branch)?;
let identity = remote_identity(&input.remote_url, &branch);
let record = RemoteMountRecord {
schema: REMOTE_MOUNT_SCHEMA.into(),
mount_id: input.mount_id.clone(),
remote_url: input.remote_url,
remote_identity_hash: identity.clone(),
branch: branch.clone(),
};
let mounts = child_root(root, "mounts")?;
write_json_atomic(&mounts.join(format!("{}.json", input.mount_id)), &record)?;
Ok(())
}
pub(crate) fn read_mounted_remote_object_at(
root: &Path,
input: ReadMountedPnccRemoteObjectInput,
) -> Result<PnccRemoteObjectReceipt, String> {
read_mounted_remote_object_with_file_policy(root, input, false)
}
fn read_mounted_remote_object_with_file_policy(
root: &Path,
input: ReadMountedPnccRemoteObjectInput,
allow_file_remote: bool,
) -> Result<PnccRemoteObjectReceipt, String> {
validate_machine_id(&input.mount_id, "REMOTE_MOUNT_ID")?;
validate_relative_path(&input.relative_path)?;
let maximum = input.max_cache_bytes.unwrap_or(DEFAULT_CACHE_LIMIT_BYTES);
if !(MIN_CACHE_LIMIT_BYTES..=MAX_CACHE_LIMIT_BYTES).contains(&maximum) {
return Err("PNCC_REMOTE_CACHE_LIMIT_INVALID".into());
}
let expected_head = input
.expected_head
.map(|head| validate_head(&head))
.transpose()?;
let mounts = child_root(root, "mounts")?;
let record: RemoteMountRecord = read_json(&mounts.join(format!("{}.json", input.mount_id)))?;
if record.schema != REMOTE_MOUNT_SCHEMA || record.mount_id != input.mount_id {
return Err("PNCC_REMOTE_MOUNT_RECORD_INVALID".into());
}
validate_remote_url(&record.remote_url, allow_file_remote)?;
validate_branch(&record.branch)?;
let identity = remote_identity(&record.remote_url, &record.branch);
if identity != record.remote_identity_hash {
return Err("PNCC_REMOTE_MOUNT_IDENTITY_MISMATCH".into());
}
let locks = child_root(root, "locks")?;
let _lock = lock_file(&locks.join(format!("{identity}.lock")))?;
let remote_head = read_remote_head(&record.remote_url, &record.branch)?;
if expected_head
.as_deref()
.is_some_and(|head| head != remote_head)
{
return Err("PNCC_REMOTE_HEAD_MISMATCH".into());
}
let object_root = child_root(root, "objects")?;
let cursor_root = child_root(root, "continuity")?;
let cache_path = object_root.join(format!("{identity}.git"));
let cursor_path = cursor_root.join(format!("{identity}.json"));
let cursor = read_cursor(&cursor_path, &identity, &record.branch)?;
let previous = cursor.as_ref().map(|value| value.verified_head.clone());
ensure_bare_cache(&cache_path, &record.remote_url)?;
let remote_ref = format!("refs/remotes/origin/{}", record.branch);
let cached = optional_ref(&cache_path, &remote_ref)?;
match (previous.as_deref(), cached.as_deref()) {
(None, Some(_)) => return Err("PNCC_REMOTE_CACHE_WITHOUT_CONTINUITY_CURSOR".into()),
(Some(expected), Some(observed)) if expected != observed => {
return Err("PNCC_REMOTE_CACHE_CURSOR_MISMATCH".into())
}
_ => {}
}
let fetched_incremental_objects = cached.as_deref() != Some(remote_head.as_str());
let cache_rehydrated = cached.is_none() && previous.is_some();
if fetched_incremental_objects {
if let Err(error) = fetch_continuity(
&cache_path,
&record.branch,
previous.as_deref(),
cached.is_some(),
) {
if cache_path.exists() {
remove_exact_cache(&object_root, &cache_path)?;
}
return Err(error);
}
}
let fetched_head = required_ref(&cache_path, &remote_ref)?;
if fetched_head != remote_head {
return Err("PNCC_REMOTE_FETCH_HEAD_MISMATCH".into());
}
if let Some(previous_head) = previous.as_deref() {
if previous_head != remote_head && !is_ancestor(&cache_path, previous_head, &remote_head)? {
remove_exact_cache(&object_root, &cache_path)?;
return Err("PNCC_REMOTE_HISTORY_REWRITE_REJECTED".into());
}
}
let (object_id, content_bytes) = read_regular_blob(
&cache_path,
&remote_head,
&input.relative_path,
MAX_TEXT_OBJECT_BYTES,
)?;
let content =
String::from_utf8(content_bytes).map_err(|_| "PNCC_REMOTE_OBJECT_NOT_UTF8".to_string())?;
let content_sha256 = sha256_hex(content.as_bytes());
let cursor_advanced = previous.as_deref() != Some(remote_head.as_str());
let next = CursorRecord {
schema: CURSOR_SCHEMA.into(),
remote_identity_hash: identity.clone(),
branch: record.branch,
verified_head: remote_head.clone(),
previous_verified_head: previous.clone(),
generation: cursor
.as_ref()
.map(|value| value.generation.saturating_add(u64::from(cursor_advanced)))
.unwrap_or(1),
};
write_json_atomic(&cursor_path, &next)?;
let observed_cache_bytes = directory_size(&cache_path)?;
let (cache_bytes, cache_state) = if observed_cache_bytes > maximum {
remove_exact_cache(&object_root, &cache_path)?;
(0, "EVICTED_AFTER_BOUNDED_READ")
} else {
(observed_cache_bytes, "BOUNDED_PARTIAL_OBJECT_CACHE")
};
let receipt_id = sha256_hex(
format!(
"{}\n{}\n{}\n{}\n{}",
input.mount_id, remote_head, input.relative_path, object_id, content_sha256
)
.as_bytes(),
);
Ok(PnccRemoteObjectReceipt {
schema: "hololake.pncc-stage-one-remote-object-read/v1",
mount_id: input.mount_id,
remote_identity_hash: identity,
remote_head,
previous_verified_head: previous,
cursor_advanced,
continuity_verified: true,
fetched_incremental_objects,
cache_rehydrated,
relative_path: input.relative_path,
git_object_id: object_id,
content,
content_sha256,
cache_bytes,
cache_limit_bytes: maximum,
cache_state,
worktree_created: false,
full_history_requested: false,
model_inference_started: false,
reality_execution_allowed: false,
receipt_id,
})
}
fn validate_remote_url(remote_url: &str, allow_file_remote: bool) -> Result<(), String> {
if remote_url.len() > 4096 || remote_url.contains(['\n', '\r', '\0']) {
return Err("PNCC_REMOTE_URL_UNSUPPORTED".into());
}
if allow_file_remote && remote_url.starts_with("file://") {
return Ok(());
}
let url =
tauri::Url::parse(remote_url).map_err(|_| "PNCC_REMOTE_URL_UNSUPPORTED".to_string())?;
if url.scheme() != "https"
|| url.host_str().is_none()
|| !url.username().is_empty()
|| url.password().is_some()
|| url.query().is_some()
|| url.fragment().is_some()
{
return Err("PNCC_REMOTE_URL_MUST_BE_CREDENTIAL_FREE_HTTPS".into());
}
Ok(())
}
fn validate_branch(branch: &str) -> Result<(), String> {
if branch.is_empty() || branch.len() > 200 || branch.starts_with('-') {
return Err("PNCC_REMOTE_BRANCH_INVALID".into());
}
let output = trusted_git()
.args(["check-ref-format", "--branch", branch])
.output()
.map_err(|error| format!("PNCC_GIT_UNAVAILABLE: {error}"))?;
if output.status.success() {
Ok(())
} else {
Err("PNCC_REMOTE_BRANCH_INVALID".into())
}
}
fn validate_relative_path(relative: &str) -> Result<(), String> {
let path = Path::new(relative);
if relative.is_empty()
|| relative.len() > 1024
|| path.is_absolute()
|| path.components().any(|component| {
matches!(
component,
Component::ParentDir | Component::RootDir | Component::Prefix(_)
)
})
|| path
.components()
.any(|component| component.as_os_str() == ".git")
{
return Err("PNCC_REMOTE_PATH_INVALID".into());
}
Ok(())
}
fn validate_head(head: &str) -> Result<String, String> {
if head.len() == 40
&& head
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
{
Ok(head.into())
} else {
Err("PNCC_REMOTE_HEAD_INVALID".into())
}
}
fn validate_machine_id(value: &str, label: &str) -> Result<(), String> {
if value.is_empty()
|| value.len() > 128
|| !value
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.'))
{
return Err(format!("PNCC_{label}_INVALID"));
}
Ok(())
}
fn remote_identity(remote_url: &str, branch: &str) -> String {
sha256_hex(format!("{remote_url}\n{branch}").as_bytes())
}
fn read_remote_head(remote_url: &str, branch: &str) -> Result<String, String> {
let full_ref = format!("refs/heads/{branch}");
let output = trusted_git()
.env("GIT_TRACE_PACKET", "1")
.args([
"-c",
"protocol.version=2",
"ls-remote",
"--exit-code",
"--heads",
remote_url,
&full_ref,
])
.output()
.map_err(|error| format!("PNCC_REMOTE_HEAD_READ_FAILED: {error}"))?;
let output = require_success(output, "PNCC_REMOTE_HEAD_READ")?;
let trace = String::from_utf8_lossy(&output.stderr);
if !trace.lines().any(|line| {
line.split_whitespace().any(|word| word == "filter")
&& line
.split_whitespace()
.any(|word| word.starts_with("fetch="))
}) {
return Err("PNCC_REMOTE_PARTIAL_OBJECT_PROTOCOL_REQUIRED".into());
}
let response = String::from_utf8(output.stdout)
.map_err(|_| "PNCC_REMOTE_HEAD_RESPONSE_NOT_UTF8".to_string())?;
let mut lines = response.lines();
let first = lines
.next()
.ok_or_else(|| "PNCC_REMOTE_BRANCH_NOT_FOUND".to_string())?;
if lines.next().is_some() {
return Err("PNCC_REMOTE_HEAD_AMBIGUOUS".into());
}
let mut fields = first.split_whitespace();
let head = fields.next().unwrap_or_default().to_ascii_lowercase();
if fields.next() != Some(full_ref.as_str()) || fields.next().is_some() {
return Err("PNCC_REMOTE_HEAD_RESPONSE_INVALID".into());
}
validate_head(&head)
}
fn ensure_bare_cache(cache: &Path, remote_url: &str) -> Result<(), String> {
if cache.exists() {
if git_text(
cache,
&["rev-parse", "--is-bare-repository"],
"PNCC_CACHE_PROBE",
)?
.trim()
!= "true"
{
return Err("PNCC_REMOTE_CACHE_NOT_BARE".into());
}
if git_text(
cache,
&["remote", "get-url", "origin"],
"PNCC_CACHE_ORIGIN_READ",
)?
.trim()
!= remote_url
{
return Err("PNCC_REMOTE_CACHE_ORIGIN_MISMATCH".into());
}
return Ok(());
}
fs::create_dir(cache).map_err(|error| format!("PNCC_REMOTE_CACHE_CREATE_FAILED: {error}"))?;
git_status(
trusted_git().args(["init", "--bare", &cache.to_string_lossy()]),
"PNCC_REMOTE_CACHE_INIT",
)?;
git_status(
git_at(cache).args(["remote", "add", "origin", remote_url]),
"PNCC_REMOTE_CACHE_ORIGIN_ADD",
)?;
git_status(
git_at(cache).args(["config", "remote.origin.promisor", "true"]),
"PNCC_REMOTE_CACHE_PROMISOR_CONFIG",
)?;
git_status(
git_at(cache).args(["config", "remote.origin.partialclonefilter", "blob:none"]),
"PNCC_REMOTE_CACHE_FILTER_CONFIG",
)
}
fn fetch_continuity(
cache: &Path,
branch: &str,
previous: Option<&str>,
cache_has_previous: bool,
) -> Result<(), String> {
let refspec = format!("+refs/heads/{branch}:refs/remotes/origin/{branch}");
if previous.is_none() || !cache_has_previous {
fetch_depth(cache, &refspec, 1)?;
} else {
fetch_incremental(cache, &refspec)?;
}
let Some(previous) = previous else {
return Ok(());
};
let current = required_ref(cache, &format!("refs/remotes/origin/{branch}"))?;
if previous == current || is_ancestor(cache, previous, &current)? {
return Ok(());
}
if cache_has_previous {
return Err("PNCC_REMOTE_HISTORY_REWRITE_REJECTED".into());
}
let mut depth = 2;
while depth <= MAX_CONTINUITY_DEPTH {
fetch_depth(cache, &refspec, depth)?;
if is_ancestor(cache, previous, &current)? {
return Ok(());
}
depth = depth.saturating_mul(2);
}
Err("PNCC_REMOTE_CONTINUITY_WINDOW_EXCEEDED".into())
}
fn fetch_depth(cache: &Path, refspec: &str, depth: usize) -> Result<(), String> {
let depth = format!("--depth={depth}");
let output = git_at(cache)
.args([
"fetch",
"--no-tags",
&depth,
"--filter=blob:none",
"origin",
refspec,
])
.output()
.map_err(|error| format!("PNCC_REMOTE_INCREMENTAL_FETCH_FAILED: {error}"))?;
require_partial_fetch(output)
}
fn fetch_incremental(cache: &Path, refspec: &str) -> Result<(), String> {
let output = git_at(cache)
.args([
"fetch",
"--no-tags",
"--update-shallow",
"--filter=blob:none",
"origin",
refspec,
])
.output()
.map_err(|error| format!("PNCC_REMOTE_INCREMENTAL_FETCH_FAILED: {error}"))?;
require_partial_fetch(output)
}
fn require_partial_fetch(output: Output) -> Result<(), String> {
let output = require_success(output, "PNCC_REMOTE_INCREMENTAL_FETCH")?;
let diagnostic = String::from_utf8_lossy(&output.stderr);
if diagnostic.contains("filtering not recognized") || diagnostic.contains("filter-spec") {
Err("PNCC_REMOTE_PARTIAL_OBJECT_PROTOCOL_REQUIRED".into())
} else {
Ok(())
}
}
fn read_regular_blob(
cache: &Path,
head: &str,
relative: &str,
maximum: usize,
) -> Result<(String, Vec<u8>), String> {
validate_head(head)?;
validate_relative_path(relative)?;
let tree = git_text(
cache,
&["ls-tree", head, "--", relative],
"PNCC_REMOTE_OBJECT_MODE_READ",
)?;
let mut fields = tree.split_whitespace();
let mode = fields.next().unwrap_or_default();
let kind = fields.next().unwrap_or_default();
let object_id = fields.next().unwrap_or_default().to_ascii_lowercase();
if !matches!(mode, "100644" | "100755") || kind != "blob" {
return Err("PNCC_REMOTE_OBJECT_NOT_REGULAR_FILE".into());
}
validate_head(&object_id)?;
let bytes = git_bytes(
cache,
&["cat-file", "blob", &object_id],
"PNCC_REMOTE_OBJECT_READ",
)?;
if bytes.is_empty() || bytes.len() > maximum {
return Err("PNCC_REMOTE_OBJECT_SIZE_INVALID".into());
}
Ok((object_id, bytes))
}
fn is_ancestor(cache: &Path, ancestor: &str, descendant: &str) -> Result<bool, String> {
validate_head(ancestor)?;
validate_head(descendant)?;
let output = git_at(cache)
.args(["merge-base", "--is-ancestor", ancestor, descendant])
.output()
.map_err(|error| format!("PNCC_REMOTE_CONTINUITY_CHECK_FAILED: {error}"))?;
match output.status.code() {
Some(0) => Ok(true),
Some(1) => Ok(false),
_ => Err(format!(
"PNCC_REMOTE_CONTINUITY_CHECK_FAILED: {}",
String::from_utf8_lossy(&output.stderr).trim()
)),
}
}
fn optional_ref(cache: &Path, reference: &str) -> Result<Option<String>, String> {
let output = git_at(cache)
.args(["rev-parse", "--verify", reference])
.output()
.map_err(|error| format!("PNCC_REMOTE_CURSOR_READ_FAILED: {error}"))?;
if !output.status.success() {
return Ok(None);
}
let value = String::from_utf8(output.stdout)
.map_err(|_| "PNCC_REMOTE_CURSOR_NOT_UTF8".to_string())?
.trim()
.to_ascii_lowercase();
validate_head(&value).map(Some)
}
fn required_ref(cache: &Path, reference: &str) -> Result<String, String> {
optional_ref(cache, reference)?.ok_or_else(|| "PNCC_REMOTE_FETCH_HEAD_MISSING".into())
}
fn read_cursor(path: &Path, identity: &str, branch: &str) -> Result<Option<CursorRecord>, String> {
if !path.exists() {
return Ok(None);
}
let record: CursorRecord = read_json(path)?;
if record.schema != CURSOR_SCHEMA
|| record.remote_identity_hash != identity
|| record.branch != branch
|| record.generation == 0
{
return Err("PNCC_REMOTE_CURSOR_RECORD_INVALID".into());
}
validate_head(&record.verified_head)?;
if let Some(previous) = record.previous_verified_head.as_deref() {
validate_head(previous)?;
}
Ok(Some(record))
}
fn child_root(root: &Path, child: &str) -> Result<PathBuf, String> {
let canonical_root = root
.canonicalize()
.map_err(|error| format!("PNCC_REMOTE_STORAGE_UNAVAILABLE: {error}"))?;
let path = root.join(child);
fs::create_dir_all(&path)
.map_err(|error| format!("PNCC_REMOTE_STORAGE_UNAVAILABLE: {error}"))?;
let canonical = path
.canonicalize()
.map_err(|error| format!("PNCC_REMOTE_STORAGE_UNAVAILABLE: {error}"))?;
if canonical.parent() != Some(canonical_root.as_path()) || !canonical.is_dir() {
return Err("PNCC_REMOTE_STORAGE_BOUNDARY_INVALID".into());
}
Ok(canonical)
}
fn lock_file(path: &Path) -> Result<std::fs::File, String> {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(path)
.map_err(|error| format!("PNCC_REMOTE_LOCK_FAILED: {error}"))?;
file.lock_exclusive()
.map_err(|error| format!("PNCC_REMOTE_LOCK_FAILED: {error}"))?;
Ok(file)
}
fn write_json_atomic<T: Serialize>(path: &Path, value: &T) -> Result<(), String> {
let parent = path
.parent()
.ok_or("PNCC_REMOTE_STORAGE_BOUNDARY_INVALID")?;
let temporary = parent.join(format!(".pncc-{}.tmp", Uuid::new_v4()));
let bytes = serde_json::to_vec_pretty(value)
.map_err(|error| format!("PNCC_REMOTE_RECORD_INVALID: {error}"))?;
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(&temporary)
.map_err(|error| format!("PNCC_REMOTE_RECORD_WRITE_FAILED: {error}"))?;
file.write_all(&bytes)
.and_then(|_| file.sync_all())
.map_err(|error| format!("PNCC_REMOTE_RECORD_WRITE_FAILED: {error}"))?;
fs::rename(&temporary, path)
.map_err(|error| format!("PNCC_REMOTE_RECORD_WRITE_FAILED: {error}"))
}
fn read_json<T: for<'de> Deserialize<'de>>(path: &Path) -> Result<T, String> {
let metadata = fs::symlink_metadata(path).map_err(|error| {
if error.kind() == std::io::ErrorKind::NotFound {
"PNCC_REMOTE_RECORD_NOT_FOUND".to_string()
} else {
format!("PNCC_REMOTE_RECORD_READ_FAILED: {error}")
}
})?;
if metadata.file_type().is_symlink() || !metadata.is_file() {
return Err("PNCC_REMOTE_RECORD_INVALID".into());
}
serde_json::from_slice(
&fs::read(path).map_err(|error| format!("PNCC_REMOTE_RECORD_READ_FAILED: {error}"))?,
)
.map_err(|error| format!("PNCC_REMOTE_RECORD_INVALID: {error}"))
}
fn directory_size(root: &Path) -> Result<u64, String> {
let mut total = 0_u64;
let mut pending = vec![root.to_path_buf()];
while let Some(path) = pending.pop() {
let metadata = fs::symlink_metadata(&path)
.map_err(|error| format!("PNCC_REMOTE_CACHE_MEASURE_FAILED: {error}"))?;
if metadata.file_type().is_symlink() {
return Err("PNCC_REMOTE_CACHE_SYMLINK_REJECTED".into());
}
if metadata.is_file() {
total = total.saturating_add(metadata.len());
} else if metadata.is_dir() {
for entry in fs::read_dir(&path)
.map_err(|error| format!("PNCC_REMOTE_CACHE_MEASURE_FAILED: {error}"))?
{
pending.push(
entry
.map_err(|error| format!("PNCC_REMOTE_CACHE_MEASURE_FAILED: {error}"))?
.path(),
);
}
}
}
Ok(total)
}
fn remove_exact_cache(root: &Path, cache: &Path) -> Result<(), String> {
if cache.parent() != Some(root) || cache == root || !cache.starts_with(root) {
return Err("PNCC_REMOTE_CACHE_BOUNDARY_INVALID".into());
}
fs::remove_dir_all(cache).map_err(|error| format!("PNCC_REMOTE_CACHE_EVICTION_FAILED: {error}"))
}
fn trusted_git() -> Command {
let mut command = Command::new("/usr/bin/git");
command
.env_clear()
.env("PATH", "/usr/bin:/bin")
.env("GIT_CONFIG_NOSYSTEM", "1")
.env("GIT_CONFIG_GLOBAL", "/dev/null")
.env("GIT_TERMINAL_PROMPT", "0")
.env("GCM_INTERACTIVE", "Never")
.args(["-c", "protocol.file.allow=never"]);
if cfg!(test) {
command.args(["-c", "protocol.file.allow=always"]);
}
command
}
fn git_at(path: &Path) -> Command {
let mut command = trusted_git();
command.arg("-C").arg(path);
command
}
fn git_status(command: &mut Command, label: &str) -> Result<(), String> {
let output = command
.output()
.map_err(|error| format!("{label}_FAILED: {error}"))?;
require_success(output, label).map(|_| ())
}
fn git_text(path: &Path, args: &[&str], label: &str) -> Result<String, String> {
let output = git_at(path)
.args(args)
.output()
.map_err(|error| format!("{label}_FAILED: {error}"))?;
String::from_utf8(require_success(output, label)?.stdout)
.map_err(|_| format!("{label}_NOT_UTF8"))
}
fn git_bytes(path: &Path, args: &[&str], label: &str) -> Result<Vec<u8>, String> {
let output = git_at(path)
.args(args)
.output()
.map_err(|error| format!("{label}_FAILED: {error}"))?;
Ok(require_success(output, label)?.stdout)
}
fn require_success(output: Output, label: &str) -> Result<Output, String> {
if output.status.success() {
Ok(output)
} else {
Err(format!(
"{label}_FAILED: {}",
String::from_utf8_lossy(&output.stderr).trim()
))
}
}
fn sha256_hex(bytes: &[u8]) -> String {
digest(&SHA256, bytes)
.as_ref()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn fixture() -> (TempDir, PathBuf, PathBuf, String) {
let temp = TempDir::new().unwrap();
let source = temp.path().join("source");
let remote = temp.path().join("remote.git");
let storage = temp.path().join("storage");
fs::create_dir_all(source.join("brain")).unwrap();
fs::create_dir(&storage).unwrap();
test_git(&source, &["init", "-b", "main"]);
fs::write(source.join("brain/CORE.hldp"), "first\n").unwrap();
test_git(&source, &["add", "."]);
test_git(
&source,
&[
"-c",
"user.name=PNCC Test",
"-c",
"user.email=pncc@test.invalid",
"commit",
"-m",
"first",
],
);
let output = trusted_git()
.args(["init", "--bare", &remote.to_string_lossy()])
.output()
.unwrap();
assert!(output.status.success());
test_git(&remote, &["config", "uploadpack.allowFilter", "true"]);
test_git(
&remote,
&["config", "uploadpack.allowAnySHA1InWant", "true"],
);
test_git(
&source,
&["remote", "add", "origin", &remote.to_string_lossy()],
);
test_git(&source, &["push", "-u", "origin", "main"]);
let remote_url = format!("file://{}", remote.display());
register_remote_mount_at(
&storage,
RegisterPnccRemoteMountInput {
mount_id: "persona-primary".into(),
remote_url: remote_url.clone(),
branch: None,
},
true,
)
.unwrap();
(temp, source, storage, remote_url)
}
fn test_git(path: &Path, args: &[&str]) -> String {
let mut command = trusted_git();
command
.env("GIT_ALLOW_PROTOCOL", "file")
.arg("-C")
.arg(path)
.args(args);
let output = command.output().unwrap();
assert!(
output.status.success(),
"git {args:?}: {}",
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8_lossy(&output.stdout).trim().into()
}
fn read_input() -> ReadMountedPnccRemoteObjectInput {
ReadMountedPnccRemoteObjectInput {
mount_id: "persona-primary".into(),
relative_path: "brain/CORE.hldp".into(),
expected_head: None,
max_cache_bytes: None,
}
}
#[test]
fn reads_incrementally_without_worktree_or_model_execution() {
let (_temp, _source, storage, _url) = fixture();
let first =
read_mounted_remote_object_with_file_policy(&storage, read_input(), true).unwrap();
assert_eq!(first.content, "first\n");
assert!(first.cursor_advanced);
assert!(first.continuity_verified);
assert!(first.fetched_incremental_objects);
assert!(!first.worktree_created);
assert!(!first.full_history_requested);
assert!(!first.model_inference_started);
assert!(!first.reality_execution_allowed);
let second =
read_mounted_remote_object_with_file_policy(&storage, read_input(), true).unwrap();
assert!(!second.cursor_advanced);
assert!(!second.fetched_incremental_objects);
assert_eq!(second.previous_verified_head, Some(first.remote_head));
}
#[test]
fn appends_only_a_descendant_segment_and_rejects_rewritten_history() {
let (_temp, source, storage, _url) = fixture();
let first =
read_mounted_remote_object_with_file_policy(&storage, read_input(), true).unwrap();
fs::write(source.join("brain/CORE.hldp"), "second\n").unwrap();
test_git(&source, &["add", "."]);
test_git(
&source,
&[
"-c",
"user.name=PNCC Test",
"-c",
"user.email=pncc@test.invalid",
"commit",
"-m",
"second",
],
);
test_git(&source, &["push", "origin", "main"]);
let second =
read_mounted_remote_object_with_file_policy(&storage, read_input(), true).unwrap();
assert_eq!(second.previous_verified_head, Some(first.remote_head));
assert_eq!(second.content, "second\n");
test_git(&source, &["checkout", "--orphan", "rewrite"]);
test_git(&source, &["rm", "-rf", "."]);
fs::create_dir_all(source.join("brain")).unwrap();
fs::write(source.join("brain/CORE.hldp"), "rewrite\n").unwrap();
test_git(&source, &["add", "."]);
test_git(
&source,
&[
"-c",
"user.name=PNCC Test",
"-c",
"user.email=pncc@test.invalid",
"commit",
"-m",
"rewrite",
],
);
test_git(&source, &["branch", "-M", "main"]);
test_git(&source, &["push", "--force", "origin", "main"]);
assert_eq!(
read_mounted_remote_object_with_file_policy(&storage, read_input(), true).unwrap_err(),
"PNCC_REMOTE_HISTORY_REWRITE_REJECTED"
);
}
#[test]
fn public_registration_rejects_credentials_and_non_https_remotes() {
assert_eq!(
validate_remote_url("https://token@example.invalid/repo.git", false).unwrap_err(),
"PNCC_REMOTE_URL_MUST_BE_CREDENTIAL_FREE_HTTPS"
);
assert_eq!(
validate_remote_url("http://example.invalid/repo.git", false).unwrap_err(),
"PNCC_REMOTE_URL_MUST_BE_CREDENTIAL_FREE_HTTPS"
);
assert_eq!(
validate_relative_path("../brain.hldp").unwrap_err(),
"PNCC_REMOTE_PATH_INVALID"
);
}
#[test]
fn cache_eviction_keeps_the_durable_continuity_cursor() {
let (_temp, _source, storage, _url) = fixture();
let mut input = read_input();
input.max_cache_bytes = Some(MIN_CACHE_LIMIT_BYTES);
let objects = child_root(&storage, "objects").unwrap();
let first =
read_mounted_remote_object_with_file_policy(&storage, input.clone(), true).unwrap();
let cache = objects.join(format!("{}.git", first.remote_identity_hash));
fs::write(
cache.join("force-budget-overrun"),
vec![0_u8; MIN_CACHE_LIMIT_BYTES as usize],
)
.unwrap();
let second = read_mounted_remote_object_with_file_policy(&storage, input, true).unwrap();
assert_eq!(second.cache_state, "EVICTED_AFTER_BOUNDED_READ");
assert!(!cache.exists());
assert!(storage
.join("continuity")
.join(format!("{}.json", second.remote_identity_hash))
.is_file());
}
}

View file

@ -0,0 +1,667 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Clean-room stage-one implementation. Contract evidence is recorded in
// audit/pncc-migration-provenance.json; no donor source was copied.
use ring::digest::{digest, SHA256};
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use std::fs;
use std::path::{Component, Path, PathBuf};
use std::process::{Command, Output};
use tauri::{AppHandle, Manager};
const MANIFEST_PATH: &str = ".hololake/persona/manifest.json";
const MANIFEST_SCHEMA: &str = "hololake.persona/v1";
const MAX_MANIFEST_BYTES: usize = 512 * 1024;
const MAX_EVIDENCE_OBJECT_BYTES: usize = 2 * 1024 * 1024;
const MAX_DECLARED_ARTIFACTS: usize = 256;
const MOUNT_SCHEMA: &str = "hololake.pncc-stage-one-repository-mount/v1";
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct InspectMountedPnccRepositoryInput {
pub mount_id: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct PnccRepositoryMountRecord {
schema: String,
mount_id: String,
repository_path: String,
expected_persona_id: String,
expected_human_responsibility_subject: String,
expected_head: String,
approved_receipt_id: String,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PnccRepositoryMountReceipt {
pub schema: &'static str,
pub state: &'static str,
pub mount_id: String,
pub binding: PnccRepositoryInspectionReceipt,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct InspectPnccRepositoryInput {
pub repository_path: String,
pub expected_persona_id: String,
pub expected_human_responsibility_subject: String,
pub expected_head: String,
}
#[tauri::command]
pub async fn inspect_mounted_pncc_repository(
app: AppHandle,
input: InspectMountedPnccRepositoryInput,
) -> Result<PnccRepositoryMountReceipt, String> {
let root = pncc_repository_mount_root(&app)?;
tauri::async_runtime::spawn_blocking(move || inspect_mounted_at(&root, input))
.await
.map_err(|error| format!("PNCC_REPOSITORY_MOUNT_JOIN_FAILED: {error}"))?
}
pub(crate) fn pncc_repository_mount_root(app: &AppHandle) -> Result<PathBuf, String> {
let root = app
.path()
.app_data_dir()
.map_err(|error| format!("PNCC_APP_DATA_UNAVAILABLE: {error}"))?
.join("pncc-stage-one-v1")
.join("repository-mounts");
fs::create_dir_all(&root)
.map_err(|error| format!("PNCC_REPOSITORY_MOUNT_STORAGE_UNAVAILABLE: {error}"))?;
root.canonicalize()
.map_err(|error| format!("PNCC_REPOSITORY_MOUNT_STORAGE_UNAVAILABLE: {error}"))
}
pub(crate) fn inspect_mounted_at(
root: &Path,
input: InspectMountedPnccRepositoryInput,
) -> Result<PnccRepositoryMountReceipt, String> {
validate_machine_id(&input.mount_id, "MOUNT_ID")?;
let record: PnccRepositoryMountRecord = read_json(&mount_path(root, &input.mount_id))?;
if record.schema != MOUNT_SCHEMA || record.mount_id != input.mount_id {
return Err("PNCC_REPOSITORY_MOUNT_RECORD_INVALID".into());
}
let binding = inspect_repository(InspectPnccRepositoryInput {
repository_path: record.repository_path,
expected_persona_id: record.expected_persona_id,
expected_human_responsibility_subject: record.expected_human_responsibility_subject,
expected_head: record.expected_head,
})?;
if binding.receipt_id != record.approved_receipt_id {
return Err("PNCC_REPOSITORY_MOUNT_EVIDENCE_DRIFT".into());
}
Ok(PnccRepositoryMountReceipt {
schema: MOUNT_SCHEMA,
state: "REVALIDATED_READ_ONLY",
mount_id: input.mount_id,
binding,
})
}
fn mount_path(root: &Path, mount_id: &str) -> PathBuf {
root.join(format!("{mount_id}.json"))
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PnccArtifactEvidence {
pub relative_path: String,
pub git_object_id: String,
pub sha256: String,
pub byte_length: usize,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PnccCognitiveGravityEvidence {
pub schema: &'static str,
pub subject_persona_id: String,
pub source: PnccArtifactEvidence,
pub frame_schema: String,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PnccRepositoryInspectionReceipt {
pub schema: &'static str,
pub state: &'static str,
pub persona_id: String,
pub human_responsibility_subject: String,
pub repository_path: String,
pub git_head: String,
pub repository_clean: bool,
pub manifest: PnccArtifactEvidence,
pub brain_entry: PnccArtifactEvidence,
pub current_checkpoint: PnccArtifactEvidence,
pub cognitive_gravity: PnccCognitiveGravityEvidence,
pub declared_artifacts: Vec<PnccArtifactEvidence>,
pub model_fields_exposed: bool,
pub model_inference_started: bool,
pub persona_lease_acquired: bool,
pub reality_execution_allowed: bool,
pub receipt_id: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct PersonaManifestProjection {
schema: String,
persona_id: String,
human_responsibility_subject: String,
brain_entry: String,
cognitive_gravity: CognitiveGravityBinding,
current_checkpoint: String,
#[serde(default)]
organs: Vec<OrganProjection>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CognitiveGravityBinding {
schema: String,
subject_persona_id: String,
source_path: String,
frame_schema: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct OrganProjection {
#[serde(default)]
paths: Vec<String>,
}
pub fn inspect_repository(
input: InspectPnccRepositoryInput,
) -> Result<PnccRepositoryInspectionReceipt, String> {
validate_text_id(&input.expected_persona_id, "PERSONA_ID")?;
validate_text_id(
&input.expected_human_responsibility_subject,
"HUMAN_RESPONSIBILITY_SUBJECT",
)?;
let expected_head = validate_head(&input.expected_head)?;
let repository = exact_repository_root(Path::new(&input.repository_path))?;
let observed_head = git_text(&repository, &["rev-parse", "HEAD"], "PNCC_GIT_HEAD_READ")?
.trim()
.to_ascii_lowercase();
validate_head(&observed_head)?;
if observed_head != expected_head {
return Err(format!(
"PNCC_GIT_HEAD_MISMATCH: expected {expected_head}, observed {observed_head}"
));
}
let repository_clean = git_text(
&repository,
&["status", "--porcelain", "--untracked-files=all"],
"PNCC_GIT_STATUS_READ",
)?
.trim()
.is_empty();
let (manifest_evidence, manifest_bytes) = read_committed_regular_blob(
&repository,
&observed_head,
MANIFEST_PATH,
MAX_MANIFEST_BYTES,
)?;
let manifest: PersonaManifestProjection = serde_json::from_slice(&manifest_bytes)
.map_err(|error| format!("PNCC_PERSONA_MANIFEST_INVALID: {error}"))?;
if manifest.schema != MANIFEST_SCHEMA {
return Err("PNCC_PERSONA_MANIFEST_SCHEMA_UNSUPPORTED".into());
}
if manifest.persona_id != input.expected_persona_id {
return Err("PNCC_PERSONA_ID_MISMATCH".into());
}
if manifest.human_responsibility_subject != input.expected_human_responsibility_subject {
return Err("PNCC_HUMAN_RESPONSIBILITY_SUBJECT_MISMATCH".into());
}
if manifest.cognitive_gravity.schema != "hololake.persona-cognitive-gravity-binding/v1"
|| manifest.cognitive_gravity.subject_persona_id != manifest.persona_id
{
return Err("PNCC_COGNITIVE_GRAVITY_BINDING_INVALID".into());
}
validate_text_id(
&manifest.cognitive_gravity.frame_schema,
"COGNITIVE_GRAVITY_FRAME_SCHEMA",
)?;
let brain_entry = artifact_at_head(
&repository,
&observed_head,
&manifest.brain_entry,
MAX_EVIDENCE_OBJECT_BYTES,
)?;
let current_checkpoint = artifact_at_head(
&repository,
&observed_head,
&manifest.current_checkpoint,
MAX_EVIDENCE_OBJECT_BYTES,
)?;
let gravity_source = artifact_at_head(
&repository,
&observed_head,
&manifest.cognitive_gravity.source_path,
MAX_EVIDENCE_OBJECT_BYTES,
)?;
let mut declared_paths = BTreeSet::new();
for organ in manifest.organs {
for path in organ.paths {
validate_relative_path(&path)?;
declared_paths.insert(path);
if declared_paths.len() > MAX_DECLARED_ARTIFACTS {
return Err("PNCC_DECLARED_ARTIFACT_LIMIT_EXCEEDED".into());
}
}
}
declared_paths.insert(manifest.brain_entry);
declared_paths.insert(manifest.current_checkpoint);
declared_paths.insert(manifest.cognitive_gravity.source_path);
let declared_artifacts = declared_paths
.into_iter()
.map(|relative| {
artifact_at_head(
&repository,
&observed_head,
&relative,
MAX_EVIDENCE_OBJECT_BYTES,
)
})
.collect::<Result<Vec<_>, _>>()?;
let receipt_id = sha256_hex(
format!(
"{}\n{}\n{}\n{}\n{}",
manifest.persona_id,
manifest.human_responsibility_subject,
repository.display(),
observed_head,
manifest_evidence.sha256
)
.as_bytes(),
);
Ok(PnccRepositoryInspectionReceipt {
schema: "hololake.pncc-stage-one-repository-inspection/v1",
state: "BOUND_READ_ONLY_NOT_INFERENCING",
persona_id: manifest.persona_id,
human_responsibility_subject: manifest.human_responsibility_subject,
repository_path: repository.to_string_lossy().into_owned(),
git_head: observed_head,
repository_clean,
manifest: manifest_evidence,
brain_entry,
current_checkpoint,
cognitive_gravity: PnccCognitiveGravityEvidence {
schema: "hololake.persona-cognitive-gravity-evidence/v1",
subject_persona_id: manifest.cognitive_gravity.subject_persona_id,
source: gravity_source,
frame_schema: manifest.cognitive_gravity.frame_schema,
},
declared_artifacts,
model_fields_exposed: false,
model_inference_started: false,
persona_lease_acquired: false,
reality_execution_allowed: false,
receipt_id,
})
}
fn exact_repository_root(candidate: &Path) -> Result<PathBuf, String> {
let canonical = candidate
.canonicalize()
.map_err(|error| format!("PNCC_REPOSITORY_UNAVAILABLE: {error}"))?;
if !canonical.is_dir() {
return Err("PNCC_REPOSITORY_NOT_DIRECTORY".into());
}
let top = PathBuf::from(
git_text(
&canonical,
&["rev-parse", "--show-toplevel"],
"PNCC_GIT_ROOT_READ",
)?
.trim(),
)
.canonicalize()
.map_err(|error| format!("PNCC_GIT_ROOT_UNAVAILABLE: {error}"))?;
if top != canonical {
return Err("PNCC_REPOSITORY_MUST_BE_EXACT_GIT_ROOT".into());
}
Ok(canonical)
}
fn artifact_at_head(
repository: &Path,
head: &str,
relative_path: &str,
maximum_bytes: usize,
) -> Result<PnccArtifactEvidence, String> {
let (evidence, _) =
read_committed_regular_blob(repository, head, relative_path, maximum_bytes)?;
Ok(evidence)
}
fn read_committed_regular_blob(
repository: &Path,
head: &str,
relative_path: &str,
maximum_bytes: usize,
) -> Result<(PnccArtifactEvidence, Vec<u8>), String> {
validate_head(head)?;
validate_relative_path(relative_path)?;
let object = format!("{head}:{relative_path}");
let object_id = git_text(
repository,
&["rev-parse", &object],
"PNCC_GIT_OBJECT_ID_READ",
)?
.trim()
.to_ascii_lowercase();
validate_head(&object_id)?;
let object_type = git_text(
repository,
&["cat-file", "-t", &object_id],
"PNCC_GIT_OBJECT_TYPE_READ",
)?;
if object_type.trim() != "blob" {
return Err("PNCC_COMMITTED_OBJECT_NOT_BLOB".into());
}
let mode_line = git_text(
repository,
&["ls-tree", head, "--", relative_path],
"PNCC_GIT_OBJECT_MODE_READ",
)?;
let mode = mode_line.split_whitespace().next().unwrap_or_default();
if !matches!(mode, "100644" | "100755") {
return Err("PNCC_COMMITTED_OBJECT_NOT_REGULAR_FILE".into());
}
let bytes = git_bytes(
repository,
&["cat-file", "blob", &object_id],
"PNCC_GIT_OBJECT_READ",
)?;
if bytes.is_empty() || bytes.len() > maximum_bytes {
return Err("PNCC_COMMITTED_OBJECT_SIZE_INVALID".into());
}
let evidence = PnccArtifactEvidence {
relative_path: relative_path.into(),
git_object_id: object_id,
sha256: sha256_hex(&bytes),
byte_length: bytes.len(),
};
Ok((evidence, bytes))
}
fn validate_relative_path(relative: &str) -> Result<(), String> {
let path = Path::new(relative);
if relative.is_empty()
|| relative.len() > 1024
|| path.is_absolute()
|| path.components().any(|component| {
matches!(
component,
Component::ParentDir | Component::RootDir | Component::Prefix(_)
)
})
|| path
.components()
.any(|component| component.as_os_str() == ".git")
{
return Err("PNCC_REPOSITORY_RELATIVE_PATH_INVALID".into());
}
Ok(())
}
fn validate_head(head: &str) -> Result<String, String> {
if head.len() == 40
&& head
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
{
Ok(head.into())
} else {
Err("PNCC_FULL_GIT_HEAD_INVALID".into())
}
}
fn validate_text_id(value: &str, label: &str) -> Result<(), String> {
if value.trim() != value
|| value.is_empty()
|| value.len() > 256
|| value.chars().any(char::is_control)
{
return Err(format!("PNCC_{label}_INVALID"));
}
Ok(())
}
fn validate_machine_id(value: &str, label: &str) -> Result<(), String> {
if value.is_empty()
|| value.len() > 128
|| !value
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.'))
{
return Err(format!("PNCC_{label}_INVALID"));
}
Ok(())
}
fn read_json<T: for<'de> Deserialize<'de>>(path: &Path) -> Result<T, String> {
let metadata = fs::symlink_metadata(path).map_err(|error| {
if error.kind() == std::io::ErrorKind::NotFound {
"PNCC_REPOSITORY_MOUNT_NOT_FOUND".to_string()
} else {
format!("PNCC_REPOSITORY_MOUNT_READ_FAILED: {error}")
}
})?;
if metadata.file_type().is_symlink() || !metadata.is_file() {
return Err("PNCC_REPOSITORY_MOUNT_RECORD_INVALID".into());
}
serde_json::from_slice(
&fs::read(path).map_err(|error| format!("PNCC_REPOSITORY_MOUNT_READ_FAILED: {error}"))?,
)
.map_err(|error| format!("PNCC_REPOSITORY_MOUNT_RECORD_INVALID: {error}"))
}
fn trusted_git() -> Command {
let mut command = Command::new("/usr/bin/git");
command
.env_clear()
.env("PATH", "/usr/bin:/bin")
.env("GIT_CONFIG_NOSYSTEM", "1")
.env("GIT_CONFIG_GLOBAL", "/dev/null")
.env("GIT_TERMINAL_PROMPT", "0");
command
}
fn git_command(repository: &Path) -> Command {
let mut command = trusted_git();
command.arg("-C").arg(repository);
command
}
fn git_text(repository: &Path, args: &[&str], label: &str) -> Result<String, String> {
let output = git_command(repository)
.args(args)
.output()
.map_err(|error| format!("{label}_FAILED: {error}"))?;
let output = require_success(output, label)?;
String::from_utf8(output.stdout).map_err(|_| format!("{label}_NOT_UTF8"))
}
fn git_bytes(repository: &Path, args: &[&str], label: &str) -> Result<Vec<u8>, String> {
let output = git_command(repository)
.args(args)
.output()
.map_err(|error| format!("{label}_FAILED: {error}"))?;
Ok(require_success(output, label)?.stdout)
}
fn require_success(output: Output, label: &str) -> Result<Output, String> {
if output.status.success() {
Ok(output)
} else {
Err(format!(
"{label}_FAILED: {}",
String::from_utf8_lossy(&output.stderr).trim()
))
}
}
fn sha256_hex(bytes: &[u8]) -> String {
digest(&SHA256, bytes)
.as_ref()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
fn git(path: &Path, args: &[&str]) -> String {
let output = git_command(path).args(args).output().unwrap();
assert!(
output.status.success(),
"git {args:?}: {}",
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8_lossy(&output.stdout).trim().into()
}
fn fixture() -> (TempDir, PathBuf, String) {
let temp = TempDir::new().unwrap();
let repository = temp.path().join("persona");
fs::create_dir_all(repository.join("brain")).unwrap();
fs::create_dir_all(repository.join(".hololake/persona")).unwrap();
git(&repository, &["init", "-b", "main"]);
fs::write(repository.join("brain/CORE.hldp"), "core\n").unwrap();
fs::write(repository.join("brain/B0.hldp"), "gravity\n").unwrap();
fs::write(
repository.join(".hololake/persona/CURRENT.hldp"),
"checkpoint\n",
)
.unwrap();
let manifest = serde_json::json!({
"schema": MANIFEST_SCHEMA,
"personaId": "ICE-P-ZY001",
"humanResponsibilitySubject": "ICE-GL∞",
"brainEntry": "brain/CORE.hldp",
"cognitiveGravity": {
"schema": "hololake.persona-cognitive-gravity-binding/v1",
"subjectPersonaId": "ICE-P-ZY001",
"sourcePath": "brain/B0.hldp",
"frameSchema": "guanghu.zhuyuan-cognitive-gravity-frame/v1"
},
"currentCheckpoint": ".hololake/persona/CURRENT.hldp",
"modelBinding": {"providerId": "must-not-project", "apiKey": "must-not-project"},
"organs": [{"paths": ["brain/CORE.hldp", "brain/B0.hldp"]}]
});
fs::write(
repository.join(MANIFEST_PATH),
serde_json::to_vec_pretty(&manifest).unwrap(),
)
.unwrap();
git(&repository, &["add", "."]);
git(
&repository,
&[
"-c",
"user.name=PNCC Test",
"-c",
"user.email=pncc@test.invalid",
"commit",
"-m",
"persona",
],
);
let head = git(&repository, &["rev-parse", "HEAD"]);
(temp, repository, head)
}
fn input(repository: &Path, head: &str) -> InspectPnccRepositoryInput {
InspectPnccRepositoryInput {
repository_path: repository.to_string_lossy().into_owned(),
expected_persona_id: "ICE-P-ZY001".into(),
expected_human_responsibility_subject: "ICE-GL∞".into(),
expected_head: head.into(),
}
}
#[test]
fn binds_only_committed_read_only_evidence_without_model_or_execution_fields() {
let (_temp, repository, head) = fixture();
fs::write(repository.join("brain/CORE.hldp"), "uncommitted\n").unwrap();
let receipt = inspect_repository(input(&repository, &head)).unwrap();
assert_eq!(receipt.state, "BOUND_READ_ONLY_NOT_INFERENCING");
assert!(!receipt.repository_clean);
assert!(!receipt.model_fields_exposed);
assert!(!receipt.model_inference_started);
assert!(!receipt.persona_lease_acquired);
assert!(!receipt.reality_execution_allowed);
assert_eq!(receipt.brain_entry.sha256, sha256_hex(b"core\n"));
let serialized = serde_json::to_string(&receipt).unwrap();
assert!(!serialized.contains("must-not-project"));
assert!(!serialized.to_ascii_lowercase().contains("modelbinding"));
}
#[test]
fn rejects_subdirectories_wrong_heads_and_wrong_human_bindings() {
let (_temp, repository, head) = fixture();
assert_eq!(
inspect_repository(input(&repository.join("brain"), &head)).unwrap_err(),
"PNCC_REPOSITORY_MUST_BE_EXACT_GIT_ROOT"
);
let mut wrong_head = input(&repository, &"0".repeat(40));
assert!(inspect_repository(wrong_head.clone())
.unwrap_err()
.starts_with("PNCC_GIT_HEAD_MISMATCH"));
wrong_head.expected_head = head;
wrong_head.expected_human_responsibility_subject = "ANOTHER-HUMAN".into();
assert_eq!(
inspect_repository(wrong_head).unwrap_err(),
"PNCC_HUMAN_RESPONSIBILITY_SUBJECT_MISMATCH"
);
}
#[test]
fn rejects_manifest_path_escape_even_when_the_worktree_has_a_target() {
let (_temp, repository, _head) = fixture();
let manifest_path = repository.join(MANIFEST_PATH);
let mut manifest: serde_json::Value =
serde_json::from_slice(&fs::read(&manifest_path).unwrap()).unwrap();
manifest["brainEntry"] = serde_json::json!("../outside.hldp");
fs::write(
&manifest_path,
serde_json::to_vec_pretty(&manifest).unwrap(),
)
.unwrap();
git(&repository, &["add", "."]);
git(
&repository,
&[
"-c",
"user.name=PNCC Test",
"-c",
"user.email=pncc@test.invalid",
"commit",
"-m",
"escape",
],
);
let head = git(&repository, &["rev-parse", "HEAD"]);
assert_eq!(
inspect_repository(input(&repository, &head)).unwrap_err(),
"PNCC_REPOSITORY_RELATIVE_PATH_INVALID"
);
}
}