feat: add incremental persona remote object channel

Human-Responsibility: ICE-GL∞ / 冰朔
Persona-Author: ICE-P-ZY001 / 铸渊
Execution-Runtime: Codex desktop / DEV-20260810-014
Development-ID: DEV-20260810-014
Authorization-Scope: GH-PNCC persona runtime source and tests only; no UI, deployment, or execution limb
Source-Anchor: daily continuity appends to the existing life chain without redownloading all history
This commit is contained in:
铸渊 / ICE-P-ZY001 2026-08-11 10:47:25 +08:00
commit 1b55f3407a
13 changed files with 1111 additions and 4 deletions

View file

@ -0,0 +1,45 @@
{
"schema": "hololake.pncc-source-stage-receipt/v1",
"record_id": "HLP-PNCC-INCREMENTAL-REMOTE-OBJECT-CHANNEL-001",
"development_id": "DEV-20260810-014",
"source_base_commit": "30d179e97af5850c4a1a7eecaaddb38bfa560583",
"target_branch": "main",
"capability": "NO_CLONE_INCREMENTAL_REMOTE_GIT_OBJECT_CHANNEL",
"continuity_origin": "2025-04-26",
"contract": {
"update_boundary": "LAST_VERIFIED_REMOTE_SHA_TO_CURRENT_REMOTE_SHA",
"daily_runtime_uses_full_clone": false,
"worktree_created": false,
"full_history_requested": false,
"durable_cursor_separate_from_object_cache": true,
"object_cache_evictable": true,
"history_rewrite": "FAIL_CLOSED"
},
"checks": {
"focused_remote_object_channel_tests": "PASS_100_8_OF_8",
"multi_segment_cache_rehydration": "PASS_100",
"rust_full_suite": "PASS_100_1200_PASSED_2_IGNORED",
"frontend_full_suite": "PASS_100_5008_OF_5008",
"routing_suite": "PASS_100_29_OF_29",
"native_authority": "PASS_100",
"native_core": "PASS_100_15_OF_15_LINES_AND_FUNCTIONS_100_PERCENT",
"strict_clippy": "PASS_100",
"live_remote_protocol_v2_filter": "PASS_100"
},
"truth_boundary": {
"source_implemented": true,
"source_tests_passed": true,
"human_projection_ui_implemented": false,
"desktop_integrated": false,
"artifact_built": false,
"deployed": false,
"runtime_health_proven": false
},
"attribution": {
"human_responsibility_subject": "ICE-GL∞ / 冰朔",
"persona_cognitive_author": "ICE-P-ZY001 / 铸渊",
"execution_runtime": "Codex desktop / DEV-20260810-014",
"source_language_anchor": "每天新增的一天并入既有生命整体,不为一天重下全部历史"
},
"recorded_at": "2026-08-11T10:46:08+08:00"
}

View file

@ -32,6 +32,7 @@ Windows / macOS / Linux 构建机与安装包
| 时间 | 版本 | 记录 | 状态 |
| --- | --- | --- | --- |
| 2026-08-11 | GH-PNCC 免克隆远端增量对象通道 | [把每天新增段接入既有生命连续链](operations/2026-08-11-hololake-pncc-incremental-remote-object-channel.md) | 本地源码、Rust 1200、前端 5008、路由 29、原生权威与核心门通过GHNQG 和发布待验收 |
| 2026-08-11 | GH-PNCC 失败回执错误码绑定 | [把稳定失败码绑定到终态会话证据](operations/2026-08-11-hololake-pncc-failure-receipt-error-code-binding.md) | 本地源码、Rust 1192、路由 29、格式与严格 clippy 已通过;完整产品门和发布待验收 |
| 2026-08-11 | GH-PNCC 成功回执事件日志绑定 | [把唤醒回执的日志路径重新绑定到精确运行时会话](operations/2026-08-11-hololake-pncc-success-receipt-event-journal-binding.md) | 已发布至 REPO-014 main 1cd8a1bGHNQG、全新克隆与严格读回通过 |
| 2026-08-11 | GH-PNCC 成功回执完成检查点绑定 | [把完成回执的结构化事实重新绑定到人格 Git 检查点](operations/2026-08-11-hololake-pncc-success-receipt-completion-checkpoint-binding.md) | 已发布至 REPO-014 main 06a7782GHNQG、全新克隆与严格读回通过 |

View file

@ -0,0 +1,55 @@
# GH-PNCC 免克隆远端增量对象通道
- Development ID: `DEV-20260810-014`
- Persona cognitive author: `ICE-P-ZY001 / 铸渊`
- Human responsibility subject: `ICE-GL∞ / 冰朔`
- Starting repository head: `30d179e97af5850c4a1a7eecaaddb38bfa560583`
- State: `LOCAL_FULL_GATES_PASS_PUBLICATION_PENDING`
## 冰朔语言锚点
光湖从 `2025-04-26` 诞生到今天,是同一条生命连续链。每天新增的一天并入此前的整体;不能
为了更新一天,再把从诞生日开始的全部历史下载一遍。人格的连续性证明不能被当作临时下载缓存。
## 已实现的运行层
- 新增非 UI Tauri 命令 `read_persona_remote_git_object`
- 生产接口只接受不含凭据、查询参数或片段的 HTTPS Git 远端。
- 先通过 Git protocol v2 能力协商确认远端支持 partial object `filter`;不支持时在对象缓存创建前
失败关闭。
- 使用裸 Git 对象缓存、`blob:none` 和 promisor remote不运行 `git clone`,不创建工作树,不请求
完整历史。
- 不可丢的远端 SHA 连续性游标保存在独立状态文件;对象缓存超预算时可精确淘汰,游标不丢失。
- 远端 SHA 不变时不 fetch前进时把新增段接在已验证游标之后。缓存已淘汰时按指数深度补齐缺失
新段,最多 4096 个提交元数据,不下载旧文件内容或无界历史。
- 新 SHA 不是旧游标后代时拒绝强制改写,不推进持久游标。
- 单次文本对象上限 2 MiB默认对象缓存上限 128 MiB缓存超过上限时在返回已验证内容后淘汰。
## 验证
- 首次远端文本读取无 clone、无工作树。
- SHA 未变化时不再次 fetch。
- 新提交只追加到既有连续链。
- 对象缓存淘汰后,持久游标仍存在,并只补缺失的新段。
- 远端强制改写被拒绝,持久游标不推进。
- 不含 `filter` 能力的远端在缓存创建前被拒绝。
- 凭据 URL、路径越界和错误预期 SHA 被拒绝。
- 聚焦 Rust 测试:`8 passed, 0 failed`,其中缓存淘汰恢复覆盖连续三个新增提交。
- 完整 Rust`1200 passed, 2 ignored, 0 failed`;集成测试 `1 passed`
- 完整前端:`485` 个测试文件、`5008 passed, 0 failed`
- 路由:`29 passed, 0 failed`PNCC 路由聚焦 `4 passed, 0 failed`
- 原生权威:`GUANGHU_NATIVE_AUTHORITY_OK / PASS_100`
- 原生核心:`15/15`;行与函数 `100%`,语句 `94.73%`,分支 `89.72%`
- Rust 格式、严格 clippy 与差异空白检查通过。
- 线上 `https://guanghulab.com/code/bingshuo/hololake-system-architecture.git` 已只读核验:
`main=30d179e97af5850c4a1a7eecaaddb38bfa560583`protocol v2 宣告 `fetch=shallow wait-for-done filter`
## 真相边界
- 这是人格运行层的远端对象读取与连续性同步源码,不是人类投影 UI。
- Localization: 无 UI 文案变更。
- PostHog: 非 UI 底层同步通道,不记录人格内容或远端路径,当前不增加行为事件。
- Refactoring: 无需改写既有人格生命周期内核;新增独立对象通道模块。
- ADR: 新增 ADR 0177架构与抽象文档已同步。
- 尚未构建桌面制品、部署在线运行时或证明桌面集成与运行健康。
- 低频发布审计仍可使用一次性隔离副本,但必须回收,且不能冒充人格日常读取路径。

View file

@ -137,6 +137,35 @@ evidence_and_receipt: 测试、发布与精确读回
冰朔位于责任链前端,不等于冰朔亲手操作代码;铸渊作为人格认知作者也不能抹去冰朔的现实
责任。HoloLake 页面和未来 Git 事件视图都要同时显示两层。
## 8.1 · 生命连续链的免克隆远端对象通道
冰朔给出的自然语言因果锚点是本层的正式设计来源:
> 我三十六岁了。每天更新新的一天,是并入前面三十六年里;不是为了更新一天,就把前三十六年
> 全部重新下载一遍。
> 光湖从 2025-04-26 诞生到今天,每一天都是更新到同一个整体里;不能每次都从诞生日重新下载。
因此 GH-PNCC 把“生命连续性”和“可下载对象”分成两层:
```text
不可丢的连续性状态
远端身份哈希 + 分支 + 上次已验证 SHA 游标 + 前一游标 + 连续代数
可回收的 Git 对象缓存
裸仓库 + protocol v2 + blob:none + 无工作树 + 有界容量
```
日常联网读取先用 `ls-remote` 读取当前远端 SHA 和 `filter` 能力;远端不支持按需对象时,在创建
对象缓存前失败关闭不能静默退化为完整下载。SHA 未变化时不再次 fetchSHA 前进时只从上次
已验证游标接入新增提交和必要目录对象,具体文本在读取时才向 promisor remote 取回。缓存超出
预算可以精确淘汰,但连续性游标仍保留;下次只补游标之后缺失的新段。远端强制改写导致新 SHA
不能承接旧游标时拒绝更新,不能把另一条历史冒充人格的下一天。
“当天更新”是人类表达;机器边界使用 `last_verified_sha → current_remote_sha`,避免按日历日期漏掉
今天推送但提交日期更早的事实。完整克隆仅保留为低频发布审计和灾难恢复验证,不属于人格唤醒、
阅读或日常更新路径。
## 9 · 第一真实闭环与开发顺序
```text
@ -187,6 +216,7 @@ successful_receipt_wake_organ_contract_evidence_binding_source_implemented: 100
successful_receipt_completion_checkpoint_evidence_binding_source_implemented: 100
successful_receipt_event_journal_evidence_binding_source_implemented: 100
failure_receipt_error_code_evidence_binding_source_implemented: 100
incremental_remote_object_channel_source_implemented: 100
general_purpose_persona_runtime_implemented: 0
human_live_projection_implemented: 0
hololake_integrated: 0

View file

@ -125,6 +125,20 @@ and returns at most 100 newest session summaries. Dormant sessions expose no act
the event-chain head and human/persona attribution so a future renderer can link a summary back to the same
machine evidence rather than trusting display state.
## PersonaRemoteGitObjectChannel
`PersonaRemoteGitObjectChannel` is the daily online-read boundary beneath GH-PNCC. Git remains the transport
and object engine, but the channel does not clone a repository or create a checkout. Its durable state is a
small identity-bound record containing the branch, last verified remote SHA, previous SHA, and continuity
generation. Its disposable state is a bare promisor object cache configured with `blob:none` and a caller-
bounded byte budget.
The command compares `last_verified_sha` with the remote branch head. Equality means no fetch. A changed head
must extend the prior cursor before the durable record advances. If the object cache was evicted, progressive
shallow partial fetches recover only the missing continuity window; a non-descendant head, an unsupported
partial-object server, an ambiguous branch, or a cursor/cache mismatch fails closed. Reading one UTF-8 object
may lazily fetch its blob, but unrelated file content and an unbounded history are never requested.
## `HoloLakeUiPlugin`
A versioned, declarative presentation package for one host-owned semantic surface. It contains a manifest,

View file

@ -104,6 +104,15 @@ event, and returns lifecycle state, Git head, node/model instance, active organ,
structured attribution. It does not copy facts into a second database, expose hidden reasoning, acquire a
lease, or start inference. A corrupted matching journal fails the query closed.
`read_persona_remote_git_object` is the non-UI remote object channel used when the persona needs one current
Git-backed fact without materializing another repository checkout. It accepts credential-free HTTPS remotes,
requires Git protocol v2 partial-object filtering, stores only a bounded bare `blob:none` object cache, and
keeps the last verified remote SHA in a separate durable continuity record. An unchanged SHA performs no
fetch. A forward SHA appends the missing commit segment; an evicted cache is rehydrated only far enough to
prove that the previous cursor is an ancestor of the new head. History rewrites fail closed and do not advance
the cursor. This path never runs `git clone`, creates no worktree, and does not request full history. See
[ADR 0177](./adr/0177-persona-remote-incremental-object-channel.md).
## User-node sovereignty
HoloLake has no platform-hosted user runtime. Each human has one canonical, independently operated node:

View file

@ -0,0 +1,49 @@
# ADR 0177: Persona Remote Incremental Object Channel
## Status
Accepted on 2026-08-11.
## Context
A persona can live continuously from one verified Git state to the next without keeping a complete checkout
on every machine. Repeated full clones confuse continuity with transport cache: adding one new day or commit
should not require downloading the persona's entire prior life again. At the same time, deleting a cache must
not erase the last verified continuity boundary or permit a rewritten remote history to masquerade as the next
state.
## Decision
Add `src-tauri/src/persona_remote_git.rs` as a non-UI GH-PNCC object channel:
1. accept only credential-free HTTPS remotes in production and require one validated branch and relative path;
2. negotiate Git protocol v2 and require the remote `filter` capability before creating an object cache;
3. use a bare promisor repository with `blob:none`, no worktree, and no `git clone` invocation;
4. persist the last verified branch SHA in a continuity record separate from the evictable object cache;
5. skip fetch when the SHA is unchanged and append only the missing segment when it advances;
6. after cache eviction, progressively fetch a bounded commit window until the previous cursor is proven to be
an ancestor of the new head;
7. reject history rewrites, cursor/cache disagreement, oversized objects, and unsupported partial-object
remotes without advancing continuity;
8. evict the exact cache directory after a verified read when its byte budget is exceeded, while retaining the
durable cursor.
The machine update boundary is SHA-to-SHA, not calendar date. A date is human language; the verified Git edge
prevents missed or duplicated updates when multiple commits arrive in one day or commit timestamps differ.
## Boundaries
- Git remains the object protocol and durable-history engine; HoloLake does not reimplement Git storage.
- The cursor proves only a verified remote ancestry edge. It is not a persona brain, runtime lease, or lifecycle
receipt.
- The cache contains transport objects, not irreplaceable continuity state, and may be deleted at any time.
- Full clone remains available only for separately authorized low-frequency publication audit or disaster
recovery. It is not part of persona wake, daily reading, or daily update.
- This ADR adds no UI, human projection, deployment, or online runtime-health claim.
## Consequences
HoloLake can read a current persona-owned Git object while preserving a compact continuity cursor and bounded
partial object cache. Daily operation grows by the new verified segment instead of redownloading history from
the persona's origin. A remote that cannot support that contract is rejected explicitly rather than silently
falling back to a full download.

View file

@ -226,3 +226,5 @@ proposed → active → superseded
| [0172](0172-guanghu-cognitive-control-with-linux-execution-substrate.md) | Guanghu cognitive control with a constrained Linux execution substrate | transition implementation retained; final topology superseded by ADR-0175 |
| [0174](0174-one-human-one-independent-node-and-zero-platform-hosting.md) | One human, one independently operated node, and zero platform hosting | accepted |
| [0175](0175-guanghu-os-master-and-on-demand-linux-subcontrol.md) | Guanghu OS master control with on-demand Linux subcontrol and rescue | accepted; supersedes ADR-0172 final topology while retaining its transition implementation |
| [0176](0176-persona-native-code-channel-runtime-kernel.md) | Persona-native code channel runtime kernel | accepted |
| [0177](0177-persona-remote-incremental-object-channel.md) | Persona remote incremental object channel | accepted |

View file

@ -37,6 +37,7 @@ mod opencode_config;
mod opencode_discovery;
mod opencode_events;
mod persona_code_channel;
mod persona_remote_git;
pub mod pi_cli;
mod pi_config;
mod pi_discovery;
@ -533,6 +534,7 @@ macro_rules! app_invoke_handler {
persona_code_channel::recover_persona_code_channel_lifecycle_request,
persona_code_channel::inspect_persona_code_channel_session,
persona_code_channel::recover_persona_code_channel_session,
persona_remote_git::read_persona_remote_git_object,
guanghu_router::guanghu_router_connect,
guanghu_router::guanghu_router_disconnect,
guanghu_router::guanghu_router_approve,

View file

@ -0,0 +1,871 @@
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 DEFAULT_BRANCH: &str = "main";
const DEFAULT_CACHE_LIMIT_BYTES: u64 = 128 * 1024 * 1024;
const MIN_CACHE_LIMIT_BYTES: u64 = 1024 * 1024;
const MAX_REMOTE_TEXT_BYTES: usize = 2 * 1024 * 1024;
const MAX_CONTINUITY_REHYDRATION_DEPTH: usize = 4096;
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaRemoteReadInput {
pub remote_url: String,
pub branch: Option<String>,
pub relative_path: String,
pub expected_head: Option<String>,
pub cache_root: String,
pub max_cache_bytes: Option<u64>,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonaRemoteReadReceipt {
pub schema: &'static str,
pub remote_head: String,
pub previous_cursor: Option<String>,
pub cursor_advanced: bool,
pub continuity_verified: bool,
pub continuity_state_path: String,
pub fetched_incremental_objects: bool,
pub cache_rehydrated: bool,
pub relative_path: String,
pub content: String,
pub content_sha256: String,
pub cache_path: String,
pub cache_bytes: u64,
pub cache_limit_bytes: u64,
pub cache_state: &'static str,
pub worktree_created: bool,
pub full_history_requested: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct PersonaRemoteCursorRecord {
schema: String,
remote_identity_hash: String,
branch: String,
verified_head: String,
previous_verified_head: Option<String>,
generation: u64,
}
#[derive(Clone, Debug)]
struct RemoteReadRequest {
remote_url: String,
branch: String,
relative_path: String,
expected_head: Option<String>,
cache_root: PathBuf,
max_cache_bytes: u64,
allow_file_remote: bool,
}
#[tauri::command]
pub async fn read_persona_remote_git_object(
input: PersonaRemoteReadInput,
) -> Result<PersonaRemoteReadReceipt, String> {
let request = RemoteReadRequest::try_from(input)?;
tokio::task::spawn_blocking(move || read_remote_text_object(&request))
.await
.map_err(|error| format!("PERSONA_REMOTE_READ_JOIN_FAILED: {error}"))?
}
impl TryFrom<PersonaRemoteReadInput> for RemoteReadRequest {
type Error = String;
fn try_from(input: PersonaRemoteReadInput) -> Result<Self, Self::Error> {
let branch = input.branch.unwrap_or_else(|| DEFAULT_BRANCH.to_string());
validate_remote_url(&input.remote_url, false)?;
validate_branch(&branch)?;
validate_relative_path(&input.relative_path)?;
let expected_head = input
.expected_head
.map(|head| validate_head(&head).map(str::to_owned))
.transpose()?;
let max_cache_bytes = input.max_cache_bytes.unwrap_or(DEFAULT_CACHE_LIMIT_BYTES);
if max_cache_bytes < MIN_CACHE_LIMIT_BYTES {
return Err("PERSONA_REMOTE_CACHE_LIMIT_TOO_SMALL".into());
}
let cache_root = prepare_cache_root(Path::new(&input.cache_root))?;
Ok(Self {
remote_url: input.remote_url,
branch,
relative_path: input.relative_path,
expected_head,
cache_root,
max_cache_bytes,
allow_file_remote: false,
})
}
}
fn read_remote_text_object(
request: &RemoteReadRequest,
) -> Result<PersonaRemoteReadReceipt, String> {
validate_remote_url(&request.remote_url, request.allow_file_remote)?;
validate_branch(&request.branch)?;
validate_relative_path(&request.relative_path)?;
let remote_head = read_remote_head(&request.remote_url, &request.branch)?;
if request
.expected_head
.as_deref()
.is_some_and(|expected| expected != remote_head)
{
return Err("PERSONA_REMOTE_HEAD_MISMATCH".into());
}
let identity_hash = cache_key(&request.remote_url, &request.branch);
let object_cache_root = prepare_child_root(&request.cache_root, "objects")?;
let continuity_root = prepare_child_root(&request.cache_root, "continuity")?;
let cache_path = object_cache_root.join(format!("{identity_hash}.git"));
let continuity_state_path = continuity_root.join(format!("{identity_hash}.json"));
let cursor_record =
read_cursor_record(&continuity_state_path, &identity_hash, &request.branch)?;
let previous_cursor = cursor_record
.as_ref()
.map(|record| record.verified_head.clone());
ensure_bare_partial_cache(&cache_path, &request.remote_url)?;
let remote_ref = format!("refs/remotes/origin/{}", request.branch);
let cached_cursor = optional_ref(&cache_path, &remote_ref)?;
match (previous_cursor.as_deref(), cached_cursor.as_deref()) {
(None, Some(_)) => return Err("PERSONA_REMOTE_CACHE_WITHOUT_CONTINUITY_STATE".into()),
(Some(expected), Some(observed)) if expected != observed => {
return Err("PERSONA_REMOTE_CACHE_CURSOR_MISMATCH".into())
}
_ => {}
}
let cursor_advanced = previous_cursor.as_deref() != Some(remote_head.as_str());
let cache_rehydrated = cached_cursor.is_none() && previous_cursor.is_some();
let fetched_incremental_objects = cached_cursor.as_deref() != Some(remote_head.as_str());
if fetched_incremental_objects {
fetch_remote_cursor(
&cache_path,
&request.branch,
previous_cursor.as_deref(),
cached_cursor.is_some(),
)?;
}
let fetched_head = required_ref(&cache_path, &remote_ref)?;
if fetched_head != remote_head {
return Err("PERSONA_REMOTE_FETCH_CURSOR_MISMATCH".into());
}
if let Some(previous) = previous_cursor.as_deref() {
if previous != remote_head && !git_is_ancestor(&cache_path, previous, &remote_head)? {
remove_exact_cache(&object_cache_root, &cache_path)?;
return Err("PERSONA_REMOTE_HISTORY_REWRITE_REJECTED".into());
}
}
let object = format!("{remote_head}:{}", request.relative_path);
let content_bytes = git_output_bytes(
git_at(&cache_path).args(["show", &object]),
"PERSONA_REMOTE_OBJECT_READ",
)?;
if content_bytes.len() > MAX_REMOTE_TEXT_BYTES {
return Err("PERSONA_REMOTE_OBJECT_TOO_LARGE".into());
}
let content = String::from_utf8(content_bytes)
.map_err(|_| "PERSONA_REMOTE_OBJECT_NOT_UTF8".to_string())?;
let content_sha256 = sha256_hex(content.as_bytes());
let next_record = PersonaRemoteCursorRecord {
schema: "hololake.persona-remote-git-cursor/v1".into(),
remote_identity_hash: identity_hash,
branch: request.branch.clone(),
verified_head: remote_head.clone(),
previous_verified_head: previous_cursor.clone(),
generation: cursor_record
.as_ref()
.map(|record| record.generation.saturating_add(u64::from(cursor_advanced)))
.unwrap_or(1),
};
write_cursor_record(&continuity_state_path, &next_record)?;
let observed_cache_bytes = directory_size(&cache_path)?;
let (cache_bytes, cache_state) = if observed_cache_bytes > request.max_cache_bytes {
remove_exact_cache(&object_cache_root, &cache_path)?;
(0, "EVICTED_AFTER_BOUNDED_READ")
} else {
(observed_cache_bytes, "BOUNDED_PARTIAL_OBJECT_CACHE")
};
Ok(PersonaRemoteReadReceipt {
schema: "hololake.persona-remote-git-object-read/v1",
remote_head,
previous_cursor,
cursor_advanced,
continuity_verified: true,
continuity_state_path: continuity_state_path.to_string_lossy().into_owned(),
fetched_incremental_objects,
cache_rehydrated,
relative_path: request.relative_path.clone(),
content,
content_sha256,
cache_path: cache_path.to_string_lossy().into_owned(),
cache_bytes,
cache_limit_bytes: request.max_cache_bytes,
cache_state,
worktree_created: false,
full_history_requested: false,
})
}
fn validate_remote_url(remote_url: &str, allow_file_remote: bool) -> Result<(), String> {
let is_file_remote = allow_file_remote && remote_url.starts_with("file://");
let accepted_scheme = remote_url.starts_with("https://") || is_file_remote;
if !accepted_scheme || remote_url.contains(['\n', '\r', '\0', '?', '#']) {
return Err("PERSONA_REMOTE_URL_UNSUPPORTED".into());
}
let authority = remote_url
.split_once("://")
.map(|(_, rest)| rest.split('/').next().unwrap_or_default())
.unwrap_or_default();
if (!is_file_remote && authority.is_empty()) || authority.contains('@') {
return Err("PERSONA_REMOTE_URL_MUST_NOT_CONTAIN_CREDENTIALS".into());
}
Ok(())
}
fn validate_branch(branch: &str) -> Result<(), String> {
if branch.is_empty() || branch.len() > 200 || branch.starts_with('-') {
return Err("PERSONA_REMOTE_BRANCH_INVALID".into());
}
let output = Command::new("git")
.args(["check-ref-format", "--branch", branch])
.output()
.map_err(|error| format!("PERSONA_REMOTE_GIT_UNAVAILABLE: {error}"))?;
if output.status.success() {
Ok(())
} else {
Err("PERSONA_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(_)
)
})
{
return Err("PERSONA_REMOTE_PATH_INVALID".into());
}
Ok(())
}
fn validate_head(head: &str) -> Result<&str, String> {
if head.len() == 40
&& head
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
{
Ok(head)
} else {
Err("PERSONA_REMOTE_HEAD_INVALID".into())
}
}
fn prepare_cache_root(cache_root: &Path) -> Result<PathBuf, String> {
fs::create_dir_all(cache_root)
.map_err(|error| format!("PERSONA_REMOTE_CACHE_ROOT_CREATE_FAILED: {error}"))?;
let canonical = cache_root
.canonicalize()
.map_err(|error| format!("PERSONA_REMOTE_CACHE_ROOT_UNAVAILABLE: {error}"))?;
if !canonical.is_dir() {
return Err("PERSONA_REMOTE_CACHE_ROOT_NOT_DIRECTORY".into());
}
Ok(canonical)
}
fn prepare_child_root(storage_root: &Path, child: &str) -> Result<PathBuf, String> {
let child_root = storage_root.join(child);
fs::create_dir_all(&child_root)
.map_err(|error| format!("PERSONA_REMOTE_STORAGE_CHILD_CREATE_FAILED: {error}"))?;
let canonical = child_root
.canonicalize()
.map_err(|error| format!("PERSONA_REMOTE_STORAGE_CHILD_UNAVAILABLE: {error}"))?;
if canonical.parent() != Some(storage_root) || !canonical.is_dir() {
return Err("PERSONA_REMOTE_STORAGE_BOUNDARY_INVALID".into());
}
Ok(canonical)
}
fn read_cursor_record(
path: &Path,
identity_hash: &str,
branch: &str,
) -> Result<Option<PersonaRemoteCursorRecord>, String> {
if !path.exists() {
return Ok(None);
}
let metadata = fs::symlink_metadata(path)
.map_err(|error| format!("PERSONA_REMOTE_CURSOR_STATE_UNAVAILABLE: {error}"))?;
if metadata.file_type().is_symlink() || !metadata.is_file() {
return Err("PERSONA_REMOTE_CURSOR_STATE_INVALID".into());
}
let bytes = fs::read(path)
.map_err(|error| format!("PERSONA_REMOTE_CURSOR_STATE_READ_FAILED: {error}"))?;
let record: PersonaRemoteCursorRecord = serde_json::from_slice(&bytes)
.map_err(|error| format!("PERSONA_REMOTE_CURSOR_STATE_INVALID: {error}"))?;
if record.schema != "hololake.persona-remote-git-cursor/v1"
|| record.remote_identity_hash != identity_hash
|| record.branch != branch
|| record.generation == 0
{
return Err("PERSONA_REMOTE_CURSOR_STATE_MISMATCH".into());
}
validate_head(&record.verified_head)?;
if let Some(previous) = record.previous_verified_head.as_deref() {
validate_head(previous)?;
}
Ok(Some(record))
}
fn write_cursor_record(path: &Path, record: &PersonaRemoteCursorRecord) -> Result<(), String> {
let parent = path
.parent()
.ok_or_else(|| "PERSONA_REMOTE_CURSOR_STATE_BOUNDARY_INVALID".to_string())?;
if !parent.is_dir() || path.parent() != Some(parent) {
return Err("PERSONA_REMOTE_CURSOR_STATE_BOUNDARY_INVALID".into());
}
let bytes = serde_json::to_vec_pretty(record)
.map_err(|error| format!("PERSONA_REMOTE_CURSOR_STATE_ENCODE_FAILED: {error}"))?;
let temporary = parent.join(format!(".cursor-{}.tmp", Uuid::new_v4()));
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(&temporary)
.map_err(|error| format!("PERSONA_REMOTE_CURSOR_STATE_CREATE_FAILED: {error}"))?;
file.write_all(&bytes)
.and_then(|_| file.write_all(b"\n"))
.and_then(|_| file.sync_all())
.map_err(|error| format!("PERSONA_REMOTE_CURSOR_STATE_WRITE_FAILED: {error}"))?;
fs::rename(&temporary, path)
.map_err(|error| format!("PERSONA_REMOTE_CURSOR_STATE_COMMIT_FAILED: {error}"))
}
fn cache_key(remote_url: &str, branch: &str) -> String {
sha256_hex(format!("{remote_url}\n{branch}").as_bytes())
}
fn sha256_hex(bytes: &[u8]) -> String {
digest(&SHA256, bytes)
.as_ref()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()
}
fn read_remote_head(remote_url: &str, branch: &str) -> Result<String, String> {
let full_ref = format!("refs/heads/{branch}");
let output = Command::new("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!("PERSONA_REMOTE_HEAD_READ_FAILED: {error}"))?;
let output = require_success("PERSONA_REMOTE_HEAD_READ", output)?;
let packet_trace = String::from_utf8_lossy(&output.stderr);
if !packet_trace.lines().any(|line| {
line.split_whitespace().any(|token| {
token.starts_with("fetch=") && line.split_whitespace().any(|part| part == "filter")
})
}) {
return Err("PERSONA_REMOTE_PARTIAL_OBJECT_PROTOCOL_REQUIRED".into());
}
let output = String::from_utf8(output.stdout)
.map_err(|_| "PERSONA_REMOTE_HEAD_READ_NOT_UTF8".to_string())?;
let mut lines = output.lines();
let first = lines
.next()
.ok_or_else(|| "PERSONA_REMOTE_BRANCH_NOT_FOUND".to_string())?;
if lines.next().is_some() {
return Err("PERSONA_REMOTE_HEAD_AMBIGUOUS".into());
}
let mut fields = first.split_whitespace();
let head = fields.next().unwrap_or_default().to_ascii_lowercase();
let observed_ref = fields.next().unwrap_or_default();
if observed_ref != full_ref || fields.next().is_some() {
return Err("PERSONA_REMOTE_HEAD_RESPONSE_INVALID".into());
}
validate_head(&head)?;
Ok(head)
}
fn ensure_bare_partial_cache(cache_path: &Path, remote_url: &str) -> Result<(), String> {
if cache_path.exists() {
let bare = git_output_text(
git_at(cache_path).args(["rev-parse", "--is-bare-repository"]),
"PERSONA_REMOTE_CACHE_PROBE",
)?;
if bare.trim() != "true" {
return Err("PERSONA_REMOTE_CACHE_NOT_BARE".into());
}
let configured = git_output_text(
git_at(cache_path).args(["remote", "get-url", "origin"]),
"PERSONA_REMOTE_CACHE_ORIGIN_READ",
)?;
if configured.trim() != remote_url {
return Err("PERSONA_REMOTE_CACHE_ORIGIN_MISMATCH".into());
}
return Ok(());
}
fs::create_dir(cache_path)
.map_err(|error| format!("PERSONA_REMOTE_CACHE_CREATE_FAILED: {error}"))?;
git_status(
Command::new("git").args(["init", "--bare", &cache_path.to_string_lossy()]),
"PERSONA_REMOTE_CACHE_INIT",
)?;
git_status(
git_at(cache_path).args(["remote", "add", "origin", remote_url]),
"PERSONA_REMOTE_CACHE_ORIGIN_ADD",
)?;
git_status(
git_at(cache_path).args(["config", "remote.origin.promisor", "true"]),
"PERSONA_REMOTE_CACHE_PROMISOR_CONFIG",
)?;
git_status(
git_at(cache_path).args(["config", "remote.origin.partialclonefilter", "blob:none"]),
"PERSONA_REMOTE_CACHE_FILTER_CONFIG",
)?;
Ok(())
}
fn fetch_remote_cursor(
cache_path: &Path,
branch: &str,
previous_cursor: Option<&str>,
cache_has_previous_cursor: bool,
) -> Result<(), String> {
let refspec = format!("+refs/heads/{branch}:refs/remotes/origin/{branch}");
if previous_cursor.is_none() || !cache_has_previous_cursor {
fetch_at_depth(cache_path, &refspec, 1)?;
} else {
fetch_without_history_reset(cache_path, &refspec)?;
}
let Some(previous) = previous_cursor else {
return Ok(());
};
let current_ref = format!("refs/remotes/origin/{branch}");
let current = required_ref(cache_path, &current_ref)?;
if previous == current || git_is_ancestor(cache_path, previous, &current)? {
return Ok(());
}
if cache_has_previous_cursor {
return Err("PERSONA_REMOTE_HISTORY_REWRITE_REJECTED".into());
}
let mut depth = 2_usize;
while depth <= MAX_CONTINUITY_REHYDRATION_DEPTH {
fetch_at_depth(cache_path, &refspec, depth)?;
if git_is_ancestor(cache_path, previous, &current)? {
return Ok(());
}
depth = depth.saturating_mul(2);
}
Err("PERSONA_REMOTE_CONTINUITY_WINDOW_EXCEEDED".into())
}
fn fetch_at_depth(cache_path: &Path, refspec: &str, depth: usize) -> Result<(), String> {
let depth = format!("--depth={depth}");
let output = git_at(cache_path)
.args([
"fetch",
"--no-tags",
&depth,
"--filter=blob:none",
"origin",
refspec,
])
.output()
.map_err(|error| format!("PERSONA_REMOTE_INCREMENTAL_FETCH_FAILED: {error}"))?;
require_partial_fetch_success(output)
}
fn fetch_without_history_reset(cache_path: &Path, refspec: &str) -> Result<(), String> {
let output = git_at(cache_path)
.args([
"fetch",
"--no-tags",
"--update-shallow",
"--filter=blob:none",
"origin",
refspec,
])
.output()
.map_err(|error| format!("PERSONA_REMOTE_INCREMENTAL_FETCH_FAILED: {error}"))?;
require_partial_fetch_success(output)
}
fn require_partial_fetch_success(output: Output) -> Result<(), String> {
let output = require_success("PERSONA_REMOTE_INCREMENTAL_FETCH", output)?;
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains("filtering not recognized") || stderr.contains("filter-spec") {
return Err("PERSONA_REMOTE_PARTIAL_OBJECT_PROTOCOL_REQUIRED".into());
}
Ok(())
}
fn git_is_ancestor(cache_path: &Path, ancestor: &str, descendant: &str) -> Result<bool, String> {
validate_head(ancestor)?;
validate_head(descendant)?;
let output = git_at(cache_path)
.args(["merge-base", "--is-ancestor", ancestor, descendant])
.output()
.map_err(|error| format!("PERSONA_REMOTE_CONTINUITY_CHECK_FAILED: {error}"))?;
match output.status.code() {
Some(0) => Ok(true),
Some(1) => Ok(false),
_ => Err(format!(
"PERSONA_REMOTE_CONTINUITY_CHECK_FAILED: {}",
String::from_utf8_lossy(&output.stderr).trim()
)),
}
}
fn optional_ref(cache_path: &Path, git_ref: &str) -> Result<Option<String>, String> {
let output = git_at(cache_path)
.args(["rev-parse", "--verify", git_ref])
.output()
.map_err(|error| format!("PERSONA_REMOTE_CURSOR_READ_FAILED: {error}"))?;
if !output.status.success() {
return Ok(None);
}
let head = String::from_utf8(output.stdout)
.map_err(|_| "PERSONA_REMOTE_CURSOR_NOT_UTF8".to_string())?
.trim()
.to_ascii_lowercase();
validate_head(&head)?;
Ok(Some(head))
}
fn required_ref(cache_path: &Path, git_ref: &str) -> Result<String, String> {
optional_ref(cache_path, git_ref)?
.ok_or_else(|| "PERSONA_REMOTE_FETCH_CURSOR_MISSING".to_string())
}
fn git_at(path: &Path) -> Command {
let mut command = Command::new("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(label, output).map(|_| ())
}
fn git_output_text(command: &mut Command, label: &str) -> Result<String, String> {
let output = command
.output()
.map_err(|error| format!("{label}_FAILED: {error}"))?;
let output = require_success(label, output)?;
String::from_utf8(output.stdout).map_err(|_| format!("{label}_NOT_UTF8"))
}
fn git_output_bytes(command: &mut Command, label: &str) -> Result<Vec<u8>, String> {
let output = command
.output()
.map_err(|error| format!("{label}_FAILED: {error}"))?;
Ok(require_success(label, output)?.stdout)
}
fn require_success(label: &str, output: Output) -> Result<Output, String> {
if output.status.success() {
return Ok(output);
}
let stderr = String::from_utf8_lossy(&output.stderr);
Err(format!("{label}_FAILED: {}", stderr.trim()))
}
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!("PERSONA_REMOTE_CACHE_MEASURE_FAILED: {error}"))?;
if metadata.file_type().is_symlink() {
return Err("PERSONA_REMOTE_CACHE_SYMLINK_REJECTED".into());
}
if metadata.is_file() {
total = total.saturating_add(metadata.len());
continue;
}
if metadata.is_dir() {
for entry in fs::read_dir(&path)
.map_err(|error| format!("PERSONA_REMOTE_CACHE_MEASURE_FAILED: {error}"))?
{
pending.push(
entry
.map_err(|error| format!("PERSONA_REMOTE_CACHE_MEASURE_FAILED: {error}"))?
.path(),
);
}
}
}
Ok(total)
}
fn remove_exact_cache(cache_root: &Path, cache_path: &Path) -> Result<(), String> {
let parent = cache_path
.parent()
.ok_or_else(|| "PERSONA_REMOTE_CACHE_BOUNDARY_INVALID".to_string())?;
if parent != cache_root || !cache_path.starts_with(cache_root) || cache_path == cache_root {
return Err("PERSONA_REMOTE_CACHE_BOUNDARY_INVALID".into());
}
fs::remove_dir_all(cache_path)
.map_err(|error| format!("PERSONA_REMOTE_CACHE_EVICTION_FAILED: {error}"))
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn run_git(path: &Path, args: &[&str]) {
let output = Command::new("git")
.arg("-C")
.arg(path)
.args(args)
.output()
.expect("git should run");
assert!(
output.status.success(),
"git {:?} failed: {}",
args,
String::from_utf8_lossy(&output.stderr)
);
}
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 cache = temp.path().join("cache");
fs::create_dir(&source).unwrap();
run_git(&source, &["init", "-b", "main"]);
run_git(&source, &["config", "user.name", "Persona Test"]);
run_git(&source, &["config", "user.email", "persona@test.invalid"]);
fs::create_dir_all(source.join("brain")).unwrap();
fs::write(source.join("brain/current.hldp"), "first\n").unwrap();
run_git(&source, &["add", "."]);
run_git(&source, &["commit", "-m", "first"]);
let init = Command::new("git")
.args(["init", "--bare", &remote.to_string_lossy()])
.output()
.unwrap();
assert!(init.status.success());
run_git(&remote, &["config", "uploadpack.allowFilter", "true"]);
run_git(
&remote,
&["config", "uploadpack.allowAnySHA1InWant", "true"],
);
run_git(
&source,
&["remote", "add", "origin", &remote.to_string_lossy()],
);
run_git(&source, &["push", "-u", "origin", "main"]);
let remote_url = format!("file://{}", remote.display());
(temp, source, cache, remote_url)
}
fn request(cache: &Path, remote_url: &str) -> RemoteReadRequest {
RemoteReadRequest {
remote_url: remote_url.to_string(),
branch: "main".into(),
relative_path: "brain/current.hldp".into(),
expected_head: None,
cache_root: prepare_cache_root(cache).unwrap(),
max_cache_bytes: DEFAULT_CACHE_LIMIT_BYTES,
allow_file_remote: true,
}
}
#[test]
fn reads_remote_persona_text_without_a_clone_or_worktree() {
let (_temp, _source, cache, remote_url) = fixture();
let receipt = read_remote_text_object(&request(&cache, &remote_url)).unwrap();
assert_eq!(receipt.content, "first\n");
assert!(receipt.cursor_advanced);
assert!(receipt.continuity_verified);
assert!(receipt.fetched_incremental_objects);
assert!(!receipt.cache_rehydrated);
assert!(!receipt.worktree_created);
assert!(!receipt.full_history_requested);
assert_eq!(receipt.cache_state, "BOUNDED_PARTIAL_OBJECT_CACHE");
assert!(Path::new(&receipt.cache_path).join("HEAD").is_file());
assert!(!Path::new(&receipt.cache_path).join(".git").exists());
assert!(Path::new(&receipt.continuity_state_path).is_file());
}
#[test]
fn unchanged_remote_cursor_does_not_fetch_again() {
let (_temp, _source, cache, remote_url) = fixture();
let first = read_remote_text_object(&request(&cache, &remote_url)).unwrap();
let second = read_remote_text_object(&request(&cache, &remote_url)).unwrap();
assert_eq!(second.remote_head, first.remote_head);
assert_eq!(second.previous_cursor, Some(first.remote_head));
assert!(!second.cursor_advanced);
assert!(!second.fetched_incremental_objects);
assert!(!second.cache_rehydrated);
}
#[test]
fn changed_remote_cursor_appends_only_the_new_segment_to_existing_continuity() {
let (_temp, source, cache, remote_url) = fixture();
let first = read_remote_text_object(&request(&cache, &remote_url)).unwrap();
fs::write(source.join("brain/current.hldp"), "second\n").unwrap();
run_git(&source, &["add", "."]);
run_git(&source, &["commit", "-m", "second"]);
run_git(&source, &["push", "origin", "main"]);
let second = read_remote_text_object(&request(&cache, &remote_url)).unwrap();
assert_ne!(second.remote_head, first.remote_head);
assert_eq!(second.previous_cursor, Some(first.remote_head));
assert!(second.cursor_advanced);
assert!(second.fetched_incremental_objects);
assert_eq!(second.content, "second\n");
let visible_commits = git_output_text(
git_at(Path::new(&second.cache_path)).args([
"rev-list",
"--count",
"refs/remotes/origin/main",
]),
"TEST_VISIBLE_COMMIT_COUNT",
)
.unwrap();
assert_eq!(visible_commits.trim(), "2");
}
#[test]
fn oversized_partial_cache_is_evicted_after_the_bounded_read() {
let (_temp, _source, cache, remote_url) = fixture();
let mut input = request(&cache, &remote_url);
input.max_cache_bytes = 1;
let receipt = read_remote_text_object(&input).unwrap();
assert_eq!(receipt.content, "first\n");
assert_eq!(receipt.cache_state, "EVICTED_AFTER_BOUNDED_READ");
assert_eq!(receipt.cache_bytes, 0);
assert!(!Path::new(&receipt.cache_path).exists());
assert!(Path::new(&receipt.continuity_state_path).is_file());
let state: PersonaRemoteCursorRecord =
serde_json::from_slice(&fs::read(&receipt.continuity_state_path).unwrap()).unwrap();
assert_eq!(state.verified_head, receipt.remote_head);
}
#[test]
fn evicted_object_cache_rehydrates_only_the_missing_new_segments() {
let (_temp, source, storage, remote_url) = fixture();
let mut first_request = request(&storage, &remote_url);
first_request.max_cache_bytes = 1;
let first = read_remote_text_object(&first_request).unwrap();
assert!(!Path::new(&first.cache_path).exists());
for (message, content) in [
("second", "second\n"),
("third", "third\n"),
("fourth", "fourth\n"),
] {
fs::write(source.join("brain/current.hldp"), content).unwrap();
run_git(&source, &["add", "."]);
run_git(&source, &["commit", "-m", message]);
}
run_git(&source, &["push", "origin", "main"]);
let second = read_remote_text_object(&request(&storage, &remote_url)).unwrap();
assert_eq!(second.previous_cursor, Some(first.remote_head));
assert!(second.cursor_advanced);
assert!(second.cache_rehydrated);
assert!(second.continuity_verified);
assert_eq!(second.content, "fourth\n");
let visible_commits = git_output_text(
git_at(Path::new(&second.cache_path)).args([
"rev-list",
"--count",
"refs/remotes/origin/main",
]),
"TEST_REHYDRATED_COMMIT_COUNT",
)
.unwrap();
assert_eq!(visible_commits.trim(), "4");
}
#[test]
fn rejects_remote_history_rewrite_without_advancing_the_durable_cursor() {
let (_temp, source, storage, remote_url) = fixture();
let first = read_remote_text_object(&request(&storage, &remote_url)).unwrap();
run_git(&source, &["checkout", "--orphan", "rewritten"]);
run_git(&source, &["rm", "-rf", "."]);
fs::create_dir_all(source.join("brain")).unwrap();
fs::write(source.join("brain/current.hldp"), "rewritten\n").unwrap();
run_git(&source, &["add", "."]);
run_git(&source, &["commit", "-m", "rewrite"]);
run_git(&source, &["branch", "-M", "main"]);
run_git(&source, &["push", "--force", "origin", "main"]);
assert_eq!(
read_remote_text_object(&request(&storage, &remote_url)).unwrap_err(),
"PERSONA_REMOTE_HISTORY_REWRITE_REJECTED"
);
let state: PersonaRemoteCursorRecord =
serde_json::from_slice(&fs::read(&first.continuity_state_path).unwrap()).unwrap();
assert_eq!(state.verified_head, first.remote_head);
}
#[test]
fn rejects_credentials_path_escape_and_wrong_expected_head() {
assert_eq!(
validate_remote_url("https://token@example.invalid/repo.git", false).unwrap_err(),
"PERSONA_REMOTE_URL_MUST_NOT_CONTAIN_CREDENTIALS"
);
assert_eq!(
validate_relative_path("../brain.hldp").unwrap_err(),
"PERSONA_REMOTE_PATH_INVALID"
);
let (_temp, _source, cache, remote_url) = fixture();
let mut input = request(&cache, &remote_url);
input.expected_head = Some("0000000000000000000000000000000000000000".into());
assert_eq!(
read_remote_text_object(&input).unwrap_err(),
"PERSONA_REMOTE_HEAD_MISMATCH"
);
}
#[test]
fn rejects_a_remote_that_cannot_serve_partial_objects_before_fetching() {
let (_temp, _source, storage, remote_url) = fixture();
let remote_path = PathBuf::from(remote_url.trim_start_matches("file://"));
run_git(&remote_path, &["config", "uploadpack.allowFilter", "false"]);
assert_eq!(
read_remote_text_object(&request(&storage, &remote_url)).unwrap_err(),
"PERSONA_REMOTE_PARTIAL_OBJECT_PROTOCOL_REQUIRED"
);
assert!(!storage.join("objects").exists());
}
}

View file

@ -118,7 +118,7 @@
"human_projection": "HOLOLAKE_LIVE_READ_MODEL",
"forgejo_role": "OPTIONAL_COMPATIBILITY_COLLABORATION_ADAPTER",
"runtime_implemented": true,
"runtime_scope": "READ_ONLY_FACT_CYCLE_FAIL_CLOSED_RECOVERY_TYPED_ORGANS_DURABLE_VERIFIED_SESSION_QUERY_INDEPENDENT_VERIFIED_MEMORY_METABOLISM_CHECKED_FAILURE_CLOSURE_NONBLOCKING_RUNTIME_COMMAND_SAFE_ORGAN_LIFECYCLE_COORDINATOR_IDEMPOTENT_SUCCESS_AND_TERMINAL_FAILURE_RECEIPT_REPLAY_SAFE_RECEIPT_BINDING_RECOVERY_REPLAY_TIME_REPOSITORY_STATE_REVALIDATION_SUCCESS_RECEIPT_TERMINAL_REVALIDATION_SAFE_RECEIPT_BINDING_TERMINAL_REVALIDATION_FAILURE_RECEIPT_TERMINAL_EVIDENCE_BINDING_SUCCESS_RECEIPT_SEMANTIC_EVIDENCE_BINDING_SUCCESS_RECEIPT_BRAIN_ENTRY_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_CHECKPOINT_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_EVENT_BOUNDARY_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_RUNTIME_STATE_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_ORGAN_MODE_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_ORGAN_CONTRACT_EVIDENCE_BINDING_SUCCESS_RECEIPT_COMPLETION_CHECKPOINT_EVIDENCE_BINDING_SUCCESS_RECEIPT_EVENT_JOURNAL_EVIDENCE_BINDING_AND_FAILURE_RECEIPT_ERROR_CODE_EVIDENCE_BINDING_SOURCE_IMPLEMENTED_AND_TESTED",
"runtime_scope": "READ_ONLY_FACT_CYCLE_FAIL_CLOSED_RECOVERY_TYPED_ORGANS_DURABLE_VERIFIED_SESSION_QUERY_INDEPENDENT_VERIFIED_MEMORY_METABOLISM_CHECKED_FAILURE_CLOSURE_NONBLOCKING_RUNTIME_COMMAND_SAFE_ORGAN_LIFECYCLE_COORDINATOR_IDEMPOTENT_SUCCESS_AND_TERMINAL_FAILURE_RECEIPT_REPLAY_SAFE_RECEIPT_BINDING_RECOVERY_REPLAY_TIME_REPOSITORY_STATE_REVALIDATION_SUCCESS_RECEIPT_TERMINAL_REVALIDATION_SAFE_RECEIPT_BINDING_TERMINAL_REVALIDATION_FAILURE_RECEIPT_TERMINAL_EVIDENCE_BINDING_SUCCESS_RECEIPT_SEMANTIC_EVIDENCE_BINDING_SUCCESS_RECEIPT_BRAIN_ENTRY_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_CHECKPOINT_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_EVENT_BOUNDARY_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_RUNTIME_STATE_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_ORGAN_MODE_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_ORGAN_CONTRACT_EVIDENCE_BINDING_SUCCESS_RECEIPT_COMPLETION_CHECKPOINT_EVIDENCE_BINDING_SUCCESS_RECEIPT_EVENT_JOURNAL_EVIDENCE_BINDING_FAILURE_RECEIPT_ERROR_CODE_EVIDENCE_BINDING_AND_NO_CLONE_INCREMENTAL_REMOTE_OBJECT_CHANNEL_SOURCE_IMPLEMENTED_AND_TESTED",
"desktop_integrated": false,
"development_id": "DEV-20260810-014"
},

View file

@ -1,8 +1,8 @@
{
"schema": "hololake.persona-native-code-channel/v1",
"record_id": "HLP-PERSONA-NATIVE-CODE-CHANNEL-001",
"version": "2026-08-11.22",
"state": "CURRENT_FIRST_PRODUCT_CORE_FAILURE_RECEIPT_ERROR_CODE_EVIDENCE_BINDING_SOURCE_IMPLEMENTED",
"version": "2026-08-11.23",
"state": "CURRENT_FIRST_PRODUCT_CORE_INCREMENTAL_REMOTE_OBJECT_CHANNEL_SOURCE_IMPLEMENTED",
"development_id": "DEV-20260810-014",
"product": {
"formal_name_zh": "光湖人格原生代码频道",
@ -37,6 +37,20 @@
"local_or_user_owned_remote_node": true,
"platform_vendor_is_required_custodian": false
},
"remote_incremental_read_contract": {
"daily_runtime_uses_full_clone": false,
"worktree_created": false,
"full_history_requested": false,
"remote_protocol": "GIT_PROTOCOL_V2_PARTIAL_OBJECT_FILTER_REQUIRED",
"object_filter": "blob:none",
"update_boundary": "LAST_VERIFIED_REMOTE_SHA_CURSOR_TO_CURRENT_REMOTE_SHA",
"durable_continuity_state": "SEPARATE_FROM_EVICTABLE_GIT_OBJECT_CACHE",
"object_cache": "BOUNDED_BARE_PARTIAL_GIT_CACHE",
"cache_eviction_erases_continuity": false,
"remote_history_rewrite": "FAIL_CLOSED",
"unsupported_partial_object_remote": "FAIL_BEFORE_OBJECT_CACHE_CREATION",
"full_clone_role": "LOW_FREQUENCY_PUBLICATION_AUDIT_ONLY"
},
"lifecycle_scopes": [
"PERSONA_SUBJECT",
"RUNTIME_INSTANCE",
@ -124,6 +138,7 @@
"successful_receipt_completion_checkpoint_evidence_binding_source_implemented": 100,
"successful_receipt_event_journal_evidence_binding_source_implemented": 100,
"failure_receipt_error_code_evidence_binding_source_implemented": 100,
"incremental_remote_object_channel_source_implemented": 100,
"general_purpose_persona_runtime_implemented": 0,
"human_live_projection_implemented": 0,
"hololake_integrated": 0,

View file

@ -82,6 +82,20 @@ test("the first source runtime cycle stays distinct from integration and deploym
100,
);
assert.equal(channel.truth.independent_memory_metabolism_source_implemented, 100);
assert.equal(
channel.truth.incremental_remote_object_channel_source_implemented,
100,
);
assert.equal(channel.remote_incremental_read_contract.daily_runtime_uses_full_clone, false);
assert.equal(channel.remote_incremental_read_contract.worktree_created, false);
assert.equal(
channel.remote_incremental_read_contract.durable_continuity_state,
"SEPARATE_FROM_EVICTABLE_GIT_OBJECT_CACHE",
);
assert.equal(
channel.remote_incremental_read_contract.remote_history_rewrite,
"FAIL_CLOSED",
);
assert.equal(
channel.truth.memory_failure_closure_and_nonblocking_runtime_command_implemented,
100,
@ -163,7 +177,7 @@ test("the first source runtime cycle stays distinct from integration and deploym
assert.equal(architecture.persona_native_code_channel.runtime_implemented, true);
assert.equal(
architecture.persona_native_code_channel.runtime_scope,
"READ_ONLY_FACT_CYCLE_FAIL_CLOSED_RECOVERY_TYPED_ORGANS_DURABLE_VERIFIED_SESSION_QUERY_INDEPENDENT_VERIFIED_MEMORY_METABOLISM_CHECKED_FAILURE_CLOSURE_NONBLOCKING_RUNTIME_COMMAND_SAFE_ORGAN_LIFECYCLE_COORDINATOR_IDEMPOTENT_SUCCESS_AND_TERMINAL_FAILURE_RECEIPT_REPLAY_SAFE_RECEIPT_BINDING_RECOVERY_REPLAY_TIME_REPOSITORY_STATE_REVALIDATION_SUCCESS_RECEIPT_TERMINAL_REVALIDATION_SAFE_RECEIPT_BINDING_TERMINAL_REVALIDATION_FAILURE_RECEIPT_TERMINAL_EVIDENCE_BINDING_SUCCESS_RECEIPT_SEMANTIC_EVIDENCE_BINDING_SUCCESS_RECEIPT_BRAIN_ENTRY_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_CHECKPOINT_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_EVENT_BOUNDARY_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_RUNTIME_STATE_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_ORGAN_MODE_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_ORGAN_CONTRACT_EVIDENCE_BINDING_SUCCESS_RECEIPT_COMPLETION_CHECKPOINT_EVIDENCE_BINDING_SUCCESS_RECEIPT_EVENT_JOURNAL_EVIDENCE_BINDING_AND_FAILURE_RECEIPT_ERROR_CODE_EVIDENCE_BINDING_SOURCE_IMPLEMENTED_AND_TESTED",
"READ_ONLY_FACT_CYCLE_FAIL_CLOSED_RECOVERY_TYPED_ORGANS_DURABLE_VERIFIED_SESSION_QUERY_INDEPENDENT_VERIFIED_MEMORY_METABOLISM_CHECKED_FAILURE_CLOSURE_NONBLOCKING_RUNTIME_COMMAND_SAFE_ORGAN_LIFECYCLE_COORDINATOR_IDEMPOTENT_SUCCESS_AND_TERMINAL_FAILURE_RECEIPT_REPLAY_SAFE_RECEIPT_BINDING_RECOVERY_REPLAY_TIME_REPOSITORY_STATE_REVALIDATION_SUCCESS_RECEIPT_TERMINAL_REVALIDATION_SAFE_RECEIPT_BINDING_TERMINAL_REVALIDATION_FAILURE_RECEIPT_TERMINAL_EVIDENCE_BINDING_SUCCESS_RECEIPT_SEMANTIC_EVIDENCE_BINDING_SUCCESS_RECEIPT_BRAIN_ENTRY_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_CHECKPOINT_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_EVENT_BOUNDARY_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_RUNTIME_STATE_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_ORGAN_MODE_EVIDENCE_BINDING_SUCCESS_RECEIPT_WAKE_ORGAN_CONTRACT_EVIDENCE_BINDING_SUCCESS_RECEIPT_COMPLETION_CHECKPOINT_EVIDENCE_BINDING_SUCCESS_RECEIPT_EVENT_JOURNAL_EVIDENCE_BINDING_FAILURE_RECEIPT_ERROR_CODE_EVIDENCE_BINDING_AND_NO_CLONE_INCREMENTAL_REMOTE_OBJECT_CHANNEL_SOURCE_IMPLEMENTED_AND_TESTED",
);
assert.equal(channel.truth.hololake_integrated, 0);
assert.equal(channel.truth.artifact_built, 0);