mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 03:32:25 +08:00
docs(pi): freeze support contracts and extension design
This commit is contained in:
@@ -0,0 +1,822 @@
|
||||
# Pi 支持重设计:冻结契约与实施方案
|
||||
|
||||
> 文档状态:规范性设计契约(normative),是新 implementation 的第一交付物。
|
||||
> 定稿日期:2026-07-31(UTC);v2 修订同日。
|
||||
> 上游文档:[pi-support-handoff-zh.md](./pi-support-handoff-zh.md)。本文不重复其背景与七轮审查历史,只固定新实现的契约。
|
||||
> 收敛过程:基于 handoff 诊断,经四轮独立架构对审(Claude Fable 5 × GPT-5.6)逐项交叉验证代码后收敛;双方多个初始立场被代码证据推翻,本文只记录最终结论。
|
||||
> 注意:本文含诊断与决策背景,不适合作为 blind reviewer 的唯一材料;commit 1 需另交付纯规范性的 `docs/pi-support-review-contract-zh.md`(不含历史 finding、实现摘要与设计论辩)。
|
||||
>
|
||||
> 范围决定(用户 2026-07-31 拍板,v2):
|
||||
> - 单个 PR 完成全部任务(handoff §2.2 的首选项),不再拆控制面/数据面两个 PR;
|
||||
> - Pi 官方原生支持、且落在 CC Switch 既有管理域内的能力全部纳入:Provider/Model、Skills、AGENTS.md、**SYSTEM.md / APPEND_SYSTEM.md**、prompt templates、Sessions、proxy/failover(产品层例外);
|
||||
> - **extensions:本 PR 只交付 design-only 附录(§13),不实现、不留代码占位**;
|
||||
> - themes / keybindings:排除;MCP:维持排除(Pi 无原生 MCP,`McpAppId` 已正确排除 pi);OAuth `auth.json`:维持 Pi 所有权,不管理。
|
||||
>
|
||||
> Pi 官方实现 authority 固定为
|
||||
> `ab366ebe94cacd419d986be454f12b1b9913aaca`。2026-07-31 Phase 0 刷新
|
||||
> `ls-remote` 时远端 `HEAD` 已前进到
|
||||
> `977ec833bbb86e245057e9162dbc1443c7b6e707`;后者仅是远端观察值,不是第二个
|
||||
> implementation pin。所有 schema/oracle/源码注释以 `ab366ebe…` 的干净源码
|
||||
> 快照为准。
|
||||
|
||||
## 0. 总诊断(一段话)
|
||||
|
||||
041ff113 七轮不收敛的根因是状态模型而非功能边界:Pi 是第一个打破仓库四个隐含 1:1 假设的 app——`app_type ↔ wire family`、`目录存在 ≈ 我方部署`、`Provider = 单行`、`settings 表 = 可迁移`——实现用调用点 `if app == "pi"` 分支模拟这些新区分,每修一处,fresh reviewer 就在另一处发现同类泄漏。修复方式是把区分写进类型与表(下述契约),并在单 PR 内用"控制面冻结点 + 累积检查点盲审"把每轮认证的新增面缩到"零 high"在概率上可达成的规模。
|
||||
|
||||
## 1. 契约 1:显式 Input Token Semantics
|
||||
|
||||
**落点**:新建 `src-tauri/src/proxy/usage/semantics.rs`;修改 `proxy/handler_context.rs`(或 handler config)、`proxy/usage/calculator.rs`、`proxy/usage/logger.rs`、`services/sql_helpers.rs`、`services/usage_stats.rs`、`src/types/usage.ts`。
|
||||
|
||||
**类型**:
|
||||
|
||||
```rust
|
||||
#[repr(i64)]
|
||||
pub enum InputTokenSemantics {
|
||||
TotalIncludesCacheBuckets = 1, // OpenAI/Gemini 系:input 已含 cached
|
||||
FreshExcludesCache = 2, // Anthropic 系:input 即 fresh
|
||||
}
|
||||
|
||||
pub enum StoredInputTokenSemantics {
|
||||
Live(InputTokenSemantics),
|
||||
LegacyTotalIncludesCacheRead = 3,
|
||||
UnknownLegacy = 4,
|
||||
}
|
||||
```
|
||||
|
||||
**Authoritative source**:
|
||||
- 新请求:wire adapter/response parser 的 `UsageParserConfig.input_semantics`,在请求上下文创建时由 wire family 显式确定;
|
||||
- 落库后:`proxy_request_logs.input_token_semantics` 列;
|
||||
- `app_type` 只保留逻辑归属与 UI 筛选;TS `KNOWN_APP_TYPES` 加入 `pi`,但永远不参与 token 语义。
|
||||
|
||||
**Legacy 迁移边界**:
|
||||
- schema migration 是唯一允许从旧 `app_type` 推断语义的地方;
|
||||
- 旧值 `0`:codex/gemini/grokbuild → `LegacyTotalIncludesCacheRead`;其他已发布 app → `FreshExcludesCache`;意外出现的未发布 Pi 旧行 → `UnknownLegacy`(不猜 family、不自动回填成本,UI 显示"语义未知");
|
||||
- rollup 旧 `0` 全部迁为 `FreshExcludesCache`(rollup 已归一化);
|
||||
- 迁移后 `0` 不得存在。不使用 DB trigger(仓库无此惯例):迁移事务消灭全部 `0` + typed DAO 写入签名不接受 `0` + runtime decoder/import 校验拒绝 `0`;所有 session import raw SQL 显式写语义。
|
||||
|
||||
**Success postcondition**:cost、明细、dedup hash、backfill、rollup、前端 fresh-input 展示读取同一个 stored semantics;Pi→OpenAI/Gemini 行 `app_type=pi` 且正确扣 cache,Pi→Anthropic 行不扣。
|
||||
|
||||
**Failure postcondition**:live writer 未提供语义时拒绝写入,不落"看似成功但计费错误"的行。
|
||||
|
||||
**并发边界**:语义在 request context 创建时复制,随异步日志任务移动;catalog/provider 后续变化不改变已 admission 请求的语义;request-id collision equality 包含语义。
|
||||
|
||||
**故障注入测试**(3):
|
||||
1. 四 family cache-heavy 表:`1000 input / 800 cached`,Anthropic fresh=1000,其余三类 fresh=200,逻辑 app 均为 Pi;
|
||||
2. 日志 spawn 后切换 provider/catalog,落库仍用请求 lease 中的语义,dedup 不把不同语义当同一事件;
|
||||
3. 迁移中断后重跑幂等、无 `0` 残留、新 raw insert 缺语义被拒、Unknown Pi 行不被猜测。
|
||||
|
||||
## 2. 契约 2:Skill Deployment Ownership(三态模型)
|
||||
|
||||
**落点**:`database/schema.rs`、新建 `database/dao/skill_deployments.rs`、新建 `services/skill_deployment.rs`;`services/skill.rs` 的 Pi toggle/sync-all/uninstall/storage migration 改走新服务。
|
||||
|
||||
**表**:
|
||||
|
||||
```sql
|
||||
CREATE TABLE skill_deployments (
|
||||
app_type TEXT NOT NULL CHECK (app_type = 'pi'),
|
||||
skill_id TEXT NOT NULL,
|
||||
destination TEXT NOT NULL, -- 部署时词法规范化的绝对路径
|
||||
destination_key TEXT NOT NULL, -- 按目标卷大小写语义生成,不用 SQLite NOCASE
|
||||
method TEXT NOT NULL CHECK (method IN ('symlink', 'copy')), -- Auto 必须落地实际方法
|
||||
source_identity TEXT NOT NULL,
|
||||
deployed_digest TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (app_type, skill_id, destination_key),
|
||||
UNIQUE (app_type, destination_key)
|
||||
);
|
||||
```
|
||||
|
||||
- 不加 cascade FK:portable restore 可能替换 `skills` 表,不能因此丢本机删除证据;
|
||||
- 本表进入 portable export/sync 的 skip + preserve 列表(device-local);
|
||||
- 主键含 `destination_key`,允许 `PI_CODING_AGENT_DIR` 迁移期间同一 skill 短暂持有新旧两条记录。
|
||||
|
||||
**三态分离(authoritative source 各自独立,不得互相覆写)**:
|
||||
- `desired_enabled`:`skills` 表的 Pi 启用位——用户意图;
|
||||
- `owned_deployment`:`skill_deployments` 有匹配记录——所有权证据;
|
||||
- `effectively_discovered`:实时 Pi discovery scanner(frontmatter name、first-wins、shadow)——实际生效。
|
||||
|
||||
禁止回归:`apply_effective_pi_status` 改写 `apps.pi`、迁移逻辑把 `apps.pi` 当所有权证据,均废除。API 返回三个独立字段(`SkillAppStatus { desired_enabled, owned_deployment, effectively_discovered, ownership, discovery, issue }`);UI toggle 只绑定 desired,"CC Switch 已部署"与"Pi 实际加载/被 shadow/原生活跃"独立显示;`desired=false, owned=false, discovered=true` 显示"原生 Skill 活跃",toggle 不点亮。
|
||||
|
||||
**Success postcondition**:
|
||||
- enable:碰撞检查 → 临时路径原子部署(atomic rename)→ 单 DB 事务写 ledger + desired=true;
|
||||
- disable:先持久化 desired=false → 仅当 ledger 存在且 identity/digest 匹配时删除目标 → 删 ledger。
|
||||
|
||||
**Failure postcondition**:
|
||||
- 部署成功但记账前崩溃:留下 unowned orphan,不自动删除、不自动收养;
|
||||
- 同名目录、同内容 copy、指向 SSOT 的 symlink 均为 unowned collision,默认拒绝;
|
||||
- owned copy digest 漂移或 symlink retarget 后拒删,保留 ledger 与显式冲突状态;
|
||||
- `PI_CODING_AGENT_DIR` 改变后旧记录显示 `stale_destination`,reconcile 先部署登记新目标,再仅在 digest/source identity 匹配时删旧目标。
|
||||
|
||||
**并发边界**:toggle、sync-all、uninstall、Skill 更新、storage migration 共用一个 deployment mutex;文件操作前后重新 `symlink_metadata`/identity check;本 PR 只启用 Pi,其他 app 未来通过显式 schema migration 扩充 CHECK。
|
||||
|
||||
**故障注入测试**(3):
|
||||
1. 预置三种 collision(同名目录/同内容 copy/指向 SSOT 的 symlink),enable 不覆盖、不建 ledger、不改 desired;
|
||||
2. 原子部署后、ledger 事务前崩溃,重启后显示 unowned/discovered,disable/uninstall 不删除;
|
||||
3. 篡改 owned copy 或 retarget owned symlink 后执行 disable/storage migration 必须拒删;portable restore 不转移 ledger。
|
||||
|
||||
## 3. 契约 3:Provider Aggregate Hydration + Pi Catalog Coordinator
|
||||
|
||||
**落点**:`database/dao/providers.rs`(统一 hydration)、新建 `services/pi_catalog.rs`(协调器);`services/provider/mod.rs` 中所有 Pi 分支与手工补偿收编。
|
||||
|
||||
**统一 hydration**:
|
||||
|
||||
```rust
|
||||
pub struct ProviderAggregate {
|
||||
pub provider: Provider,
|
||||
pub endpoints: IndexMap<String, CustomEndpoint>,
|
||||
}
|
||||
|
||||
fn get_provider_aggregate(&self, app_type: &str, id: &str) -> Result<Option<ProviderAggregate>>;
|
||||
fn get_all_provider_aggregates(&self, app_type: &str) -> Result<IndexMap<String, ProviderAggregate>>;
|
||||
```
|
||||
|
||||
`ProviderAggregate` 只用于完整读取、rollback snapshot 与 restore,不是通用
|
||||
mutation DTO。`get_provider_by_id()` / `get_all_providers()` 只能从上述函数转换;
|
||||
禁止再存在 row-only Provider 读取路径。
|
||||
|
||||
**写所有权**:
|
||||
|
||||
- 删除 `save_provider()`、`save_provider_row_on_tx()`、一切
|
||||
`upsert_provider*`、通用 aggregate save 及保持旧签名的兼容壳。read DTO
|
||||
(`Provider`/`ProviderAggregate`/hydration 后的 `ProviderMeta`)不得作为写 DTO;
|
||||
不提供从 read DTO 到 write DTO 的隐式 `From`/`Into`。
|
||||
- 写面只暴露类型化操作:
|
||||
|
||||
```rust
|
||||
fn create_provider(input: NewProviderAggregate) -> Result<(), AppError>;
|
||||
fn update_provider(key: &ProviderKey, row: &ProviderRowUpdate) -> Result<(), AppError>;
|
||||
fn rename_db_only_additive_provider(input: RenameProvider) -> Result<(), AppError>;
|
||||
fn add_provider_endpoint(key: &ProviderKey, endpoint: NewEndpoint) -> Result<(), AppError>;
|
||||
fn remove_provider_endpoint(key: &ProviderKey, url: &str) -> Result<(), AppError>;
|
||||
fn touch_provider_endpoint(key: &ProviderKey, url: &str, at: i64) -> Result<(), AppError>;
|
||||
```
|
||||
|
||||
- `ProviderRowUpdate` 不含 `id`、endpoint 集合、`is_current`、
|
||||
`in_failover_queue`、`sort_index`;这些状态只由专用 authority 修改。
|
||||
`update_provider` 必须恰好更新一行,零行返回 NotFound,绝不转为 INSERT。
|
||||
- `create_provider` 在单事务内严格 INSERT 主行与全部 `initial_endpoints`;目标已存在、
|
||||
endpoint 重复或任一插入失败时整体零副作用,绝不转为 update/upsert。
|
||||
- existing-provider 表单与 update IPC 不携带 endpoint 集合;后端收到非空
|
||||
`custom_endpoints` 必须显式拒绝。create IPC 在 service boundary 拆出
|
||||
`NewProviderAggregate.initial_endpoints`。`CustomEndpoint.added_at` 在
|
||||
Rust/IPC/TS 全链路为 nullable,不得用 `0`、`COALESCE` 或 unwrap 默认值代替 NULL。
|
||||
- rename 只允许 OpenCode/OpenClaw 的 DB-only、非 live、非 OMO additive provider。
|
||||
单事务严格插入新主行、逐字段复制全部 endpoint
|
||||
`url/added_at/last_used`(含 NULL)、删除旧主行;目标冲突与所有不允许的来源均整体
|
||||
失败。`provider_health` 可重建并可丢弃;usage/history 保留旧 provider id。
|
||||
- live import、seed、universal sync 等 reconcile 必须显式执行
|
||||
“读取 → strict create 或 strict update”;并发 create 冲突返回 conflict,或重验
|
||||
source version 后重试,不得覆盖更新。
|
||||
- 完整 replacement 只允许 coordinator compensation 使用
|
||||
`pub(super)`/sealed permit 的 `restore_provider_aggregate_on_tx()`;endpoint
|
||||
add/remove/touch 只走专用操作。删除 Provider 继续由 FK cascade 删除 endpoints。
|
||||
- 结构测试必须证明禁止符号为零,并只允许 provider row/state DAO、endpoint DAO、
|
||||
schema migration 与 canonical data copier 对 `providers` /
|
||||
`provider_endpoints` 发出生产 DML;扫描器须按 AST/SQL 类别区分并忽略
|
||||
`#[cfg(test)]`。
|
||||
|
||||
**协调器**:
|
||||
|
||||
```rust
|
||||
pub enum PiCatalogMutation {
|
||||
CreateProvider { provider: Provider, initial_endpoints: Vec<NewProviderEndpoint> },
|
||||
UpdateProvider { provider: Provider },
|
||||
AddEndpoint { provider_id: String, endpoint: NewProviderEndpoint },
|
||||
RemoveEndpoint { provider_id: String, url: String },
|
||||
DeleteProvider { provider_id: String },
|
||||
ImportNative { provider_key: String, expected_fingerprint: String },
|
||||
SetDefault { provider_id: String, model_id: String },
|
||||
// 数据面 commits 增加 route/failover/takeover variants,不改调用协议
|
||||
}
|
||||
|
||||
pub async fn apply_pi_catalog_mutation(state: &AppState, mutation: PiCatalogMutation)
|
||||
-> Result<PiCatalogMutationOutcome, PiCatalogMutationError>;
|
||||
```
|
||||
|
||||
纯排序不在枚举内:`update_pi_route_order()` 只拿 Pi switch lock、执行单个 DB 事务,并(数据面起)原子发布下一份 even-epoch runtime snapshot,不碰 listener lock、odd epoch、admission 和 `models.json` 投影。
|
||||
|
||||
**固定顺序**:
|
||||
|
||||
```text
|
||||
Pi switch lock
|
||||
→ listener lock(数据面;冻结已绑定 origin)
|
||||
→ begin epoch(odd / close admission)
|
||||
→ load complete PiCatalogSnapshot
|
||||
→ DB transaction apply mutation
|
||||
→ atomic exact-key projection
|
||||
→ build runtime from the same hydrated catalog
|
||||
→ publish even epoch
|
||||
→ activate listener generation
|
||||
```
|
||||
|
||||
projection helper 不再自行 `begin_mutation()`;epoch 只能由协调器管理。
|
||||
|
||||
Provider 表单的 update payload 不得携带 endpoint 集合;endpoint 控件可位于同一页面,
|
||||
但 submit 与 endpoint IPC 的写集合必须分离。
|
||||
|
||||
**Authoritative source**:managed aggregate=SQLite;exact key ownership=`pi_provider_projections`;`models.json`=共享投影;runtime=已成功发布 generation 的 immutable snapshot;`provider_health` 可重建,不进 rollback snapshot。
|
||||
|
||||
**Failure postcondition(显式状态机,绝不假成功)**:
|
||||
- DB mutation 前失败:DB/文件不变,旧 runtime 以新 even epoch republish;
|
||||
- DB mutation 后投影/runtime 失败:用完整 `PiCatalogSnapshot` 单事务恢复 provider rows + endpoints + claims + Pi route state,用 before/attempted claims 精确集合恢复文件,恢复成功后返回原错误,`authoritative_state = previous_restored`;
|
||||
- DB restore 事务失败:其本身回滚,mutated DB 保持权威,按实际 DB reconcile,报告 `mutated_database_authoritative`;
|
||||
- DB 已恢复但文件恢复失败:旧 DB 权威、gateway admission 关闭,报告 `projection_pending`。
|
||||
|
||||
**并发边界**:Provider CRUD、sort、default、Pi endpoint CRUD、failover queue、takeover start/stop、database restore 全部走同一 Pi switch boundary;锁序固定 `switch → listener → epoch → 短 DB 事务`;不跨文件 I/O 持 SQLite connection mutex;旧 lease 可完成响应,但 epoch 不匹配时不得写 usage/health。
|
||||
|
||||
**真实入口与故障注入测试**:
|
||||
1. 经 `ProviderService::add` 创建含多个 initial endpoints 的 Provider,完整 hydration
|
||||
逐字段读回相同集合;duplicate create 后主行、endpoint、current/failover 全不变;
|
||||
2. existing-provider update 明确拒绝非空 endpoint payload;表单已去除 endpoint 后,
|
||||
在 update 前经公开 service add/remove/touch 的结果全部存活;
|
||||
3. 经 `ProviderService::update` 覆盖 DB-only rename 矩阵:成功保留全部 endpoint 与
|
||||
NULL 时间戳;目标冲突、live、OMO、非 additive 来源均零副作用失败;
|
||||
4. endpoint mutation 后 projection 失败:完整 aggregate 恢复且单项/全量 hydration
|
||||
完全一致;
|
||||
5. 带多个 `provider_endpoints` 的 Provider 删除后令 `models.json` atomic replace
|
||||
失败:完整 aggregate、claims、旧 runtime 全恢复,`get_by_id` 与 `get_all`
|
||||
hydration 完全一致;
|
||||
6. rollback-reject trigger + projection failure 双注入:错误明确报告 mutated DB
|
||||
authoritative;runtime 要么从实际 DB 重建,要么保持 admission closed,绝不发布旧
|
||||
Provider;
|
||||
7. projection 阶段阻塞并并发 provider edit、default switch、restore 和请求:
|
||||
mutation 串行、odd epoch 拒绝新 admission、旧 writeback 被 fence、最终三层同
|
||||
generation。
|
||||
|
||||
## 4. 契约 4:设备本地稳定 Gateway Token
|
||||
|
||||
**落点**:`settings.rs`、`commands/settings.rs`、新建 `pi_config/gateway_token.rs`、`database/backup.rs`。
|
||||
|
||||
**前置加固**(必须先做):`save_settings_file` 当前是 `.truncate(true)` 直写;改为同目录临时文件 + `0600` + flush/fsync + rename,并对已存在文件校验/收紧权限(Unix `.mode(0o600)` 只约束新建文件)。
|
||||
|
||||
**生命周期三分(彻底分开,不再混用)**:
|
||||
- `gateway token`:设备安装级机密,跨进程重启稳定,存 `~/.cc-switch/settings.json`;仅显式"重置网关凭据"时旋转,并警告运行中的 Pi 会话需重启;
|
||||
- `server_generation`:本进程 listener 实例级,只防旧 listener/runtime 误 admission;
|
||||
- `catalog_epoch`:catalog mutation 级,只防请求与 writeback 跨 catalog 污染。
|
||||
|
||||
**Authoritative source**:设备文件中的 token;SQLite `settings.pi_gateway_token` 只作旧版迁移输入,永远不是新 authority。
|
||||
|
||||
**前端安全**:`get_settings_for_frontend()` 清空 token;`merge_settings_for_save()` 无条件从 existing 恢复(前端 payload 不能设置/清空/旋转);token 不进 TS settings 类型;`GatewayToken` newtype 的 Debug/Display 脱敏;token 比较不记录原值,使用常量时间比较。
|
||||
|
||||
**Legacy 迁移**:仅应用启动时从当前 live DB 迁移;device 文件已有 token 时 device 胜出并删 DB key;staged import/portable restore 的远端 token 直接删除,绝不 adopt;`export_sql_string()` 与 `export_sql_string_for_sync()` 各自断言产物不含 token key 也不含 value(不依赖迁移已完成)。
|
||||
|
||||
**Bind/reconcile 顺序**:
|
||||
|
||||
```text
|
||||
bind exact loopback host/port
|
||||
→ 持久化首次动态端口(persist_ephemeral_listen_port_if_needed 既有机制)
|
||||
→ 读取稳定 token
|
||||
→ close Pi admission
|
||||
→ compare exact owned projection
|
||||
→ mismatch 时 atomic reconcile(不旋转 token)
|
||||
→ publish runtime(origin + token + catalog)
|
||||
→ activate listener generation
|
||||
```
|
||||
|
||||
**Success postcondition**:重启后同 host/port/token,停机期间连接失败的旧 Pi 进程恢复后可无缝重连;旧端口被占用时显式报"无法恢复原 listener",不偷偷换端口。
|
||||
|
||||
**Failure postcondition**:bind、settings durable write、projection reconcile 任一步失败:token 不旋转、Pi admission 保持关闭;listener 可继续服务其他 app,Pi takeover 显示 degraded/pending,不自动清除用户 desired takeover。
|
||||
|
||||
**故障注入测试**(3):
|
||||
1. 模拟 Pi 只读一次投影,销毁旧 ProxyService、同端口建新实例:文件 token 不变、无需重投影、旧 bearer 成功;
|
||||
2. 迁移在 settings fsync 后、DB delete 前崩溃并重跑:token 稳定、frontend/save roundtrip 不泄露不清空、两个 SQL export 均不含 key/value;
|
||||
3. bind 后制造错误 port/token 投影并令文件只读:admission 从未打开、token 未旋转、unknown keys 不变;恢复权限后同 token reconcile 成功。
|
||||
|
||||
## 5. 契约 5:Pi Native Diagnostics 与显式导入
|
||||
|
||||
**落点**:新建 `pi_config/native.rs`(或 `diagnostics.rs`)、`commands/pi.rs`、`src/lib/api/pi.ts`;UI 新建 `components/providers/PiNativeCatalogPanel.tsx`。
|
||||
|
||||
**类型**:
|
||||
|
||||
```rust
|
||||
pub struct PiNativeDiagnostic {
|
||||
pub provider_key: String,
|
||||
pub display_name: Option<String>,
|
||||
pub fingerprint: String,
|
||||
pub kind: PiNativeEntryKind, // BuiltInOverlay | CustomCatalog | ExtensionOverlay | UnknownShape
|
||||
pub raw_validity: PiRawNativeValidity, // Valid | Invalid | Unknown
|
||||
pub managed_assessment: PiManagedAssessment,
|
||||
pub composition_status: PiCompositionStatus,
|
||||
pub management_status: PiManagementStatus, // Importable | Managed{provider_id} | Unsupported
|
||||
pub gateway_status: PiGatewayStatus, // Proxyable | DirectOnly | Unknown
|
||||
pub reasons: Vec<PiDiagnosticReason>,
|
||||
}
|
||||
```
|
||||
|
||||
Reason 必须结构化为 `{layer,code,json_pointer?}`。raw schema failure、managed
|
||||
narrowing、composition failure 与 gateway limitation 不得合并成一个
|
||||
`malformed_native_entry`。
|
||||
|
||||
**四层判定**:
|
||||
|
||||
```text
|
||||
importable = raw_validity == Valid AND managed_assessment == Manageable
|
||||
proxyable = raw_validity == Valid
|
||||
AND composition_status == Composed
|
||||
AND gateway 已识别 API family
|
||||
AND 完整 CandidateHeaderPlan 可构造
|
||||
```
|
||||
|
||||
Raw 层只解释固定 Pi `models.json.providers[*]` TypeBox schema 的纯 JSON 判定图。
|
||||
其输入与结果类型为无损边界:
|
||||
|
||||
```rust
|
||||
struct PiRawApiId(String); // 任意 Pi-valid 非空 API ID
|
||||
struct PiRawValidProvider { raw: serde_json::Value }
|
||||
```
|
||||
|
||||
只有输入实际经过的 operator 全部位于 provenance manifest 的显式 allowlist 时才可
|
||||
返回 Valid/Invalid;未知 operator、custom validator/transform、schema pin/hash
|
||||
漂移或适用 schema 不确定时返回 Unknown。Raw 层:
|
||||
|
||||
- 直接消费 raw `serde_json::Value`,不得先反序列化为 managed 类型;
|
||||
- 精确处理 absent/null、object/record/array/union/literal/enum、string/boolean/
|
||||
Pi Number、additionalProperties、provider/model/modelOverrides、cost、headers、
|
||||
input、thinking map与完整递归 `compat`;
|
||||
- 不解释 built-in/extension registry、OAuth/env/`!command`、文件/平台/网络、
|
||||
credential、composer fallback 或 gateway 能力。
|
||||
|
||||
Managed 层评估 CC Switch 可管理性。API ID 必须以 opaque string round-trip,不能由
|
||||
四 family enum 收窄;仅出现新的 Pi API ID 不构成 managed rejection。每个其他额外
|
||||
限制都返回独立 managed-layer reason:
|
||||
model id 唯一非空、override key 必须闭合于显式 model 集、present URL 必须非空且为
|
||||
绝对 HTTP(S)。Pi Number 不得收窄为整数;小数必须保持 JSON number 语义并可
|
||||
round-trip。Managed 拒绝不得改写成 raw malformed,也不得控制 composer 是否执行。
|
||||
|
||||
Composer 层只组合“显式 custom catalog + 显式 models + matching overrides”的闭合
|
||||
子集,直接消费 `PiRawValidProvider`。`PiComposedNativeModel` 保留 opaque
|
||||
`PiRawApiId`、`thinking_level_map`/`compat` 的 `Value`、header map,以及
|
||||
provider/model/override 中未知 shaping 字段。composer 是 credential-blind 纯函数:
|
||||
OAuth、env、`!command`、文件或网络表达式只作为 deferred transport material 保留,
|
||||
不得执行,也不得因此返回 Unknown。需要 pinned built-in/extension catalog/default
|
||||
model context 且无法真实执行的语义才 fail-closed 为 Unknown,并给精确
|
||||
catalog-required reason。
|
||||
|
||||
Gateway 层是四 family enum 唯一允许存在的位置。转换严格单向:
|
||||
`raw valid → managed assessment`、`raw valid → native composition`、
|
||||
`native composition → gateway candidate assessment`;gateway 不支持 family 是
|
||||
DirectOnly 的已知事实,不得污染 composition。capability 阶段必须构造完整
|
||||
`CandidateHeaderPlan`,验证 header 名/值表达式以及 protected/hop-by-hop 冲突;
|
||||
request 阶段逐候选、发网前物化 auth/tenant/custom/protocol header。失败只淘汰当前
|
||||
候选,不得复用上一候选的 URL、Host、auth 或 header。`Host` 只由当前 URL 生成;
|
||||
protocol/wire-contract header 的最终值进入 failover identity,auth/tenant/普通及未知
|
||||
自定义 header 不进入。
|
||||
|
||||
状态矩阵固定为:未知 API 的显式 custom catalog =
|
||||
`raw Valid + Composed + Importable + DirectOnly(api_family_not_gateway_supported)`;
|
||||
built-in/extension 仅因缺 catalog/default-model context =
|
||||
`raw Valid + Unknown composition + Unknown gateway`。
|
||||
|
||||
Composer oracle 的 expected output 必须由固定 Pi 源码中的
|
||||
`composeModelProvider`、`Provider.getModels`、
|
||||
`resolveCompatibilityRequestConfig` 实际执行产生。provenance 记录 Pi commit、
|
||||
入口、源码 hash、harness hash 与不可执行语义;本仓库手写 mirror 不得充当 oracle。
|
||||
|
||||
**Authoritative source**:diagnostic 每次读取当前 `models.json` 得出,不持久化;
|
||||
managed Provider 只有显式 import 成功后由 SQLite 权威;raw validity、
|
||||
manageability、composition 与 gateway capability 独立展示。
|
||||
|
||||
**Success postcondition**:import 经 `PiCatalogMutation::ImportNative` 在 switch lock 下重读 exact key 并比较 fingerprint(TOCTOU 防护);DB 事务原子插入 aggregate + exact projection claim;不写 `models.json`、不切默认、不解析 auth command/env;成功后 diagnostic 变为 Managed,Provider 列表出现;只有随后的用户操作才能进 failover queue。
|
||||
|
||||
**Failure postcondition**:inspection 永远无副作用;fingerprint 改变、claim collision、DB failure、unsupported shape 均不产生 Provider 或 ownership,原生 entry 原样保留并继续显示原因。彻底替代 041ff113 的"debug log + 静默跳过"。
|
||||
|
||||
**UI**:Pi Provider 页顶部独立"Pi 原生配置(只读)"面板,不用 ProviderCard,无 edit/delete/sort/failover actions;只有 `Importable` 显示"导入并由 CC Switch 管理";DirectOnly 导入后在 managed 卡片上明确显示"仅直连"。
|
||||
|
||||
**真实入口与 oracle 测试**:
|
||||
1. 公开 inspection service 覆盖未知 API custom catalog 的
|
||||
Valid/Composed/Importable/DirectOnly,以及 built-in/extension 的
|
||||
Valid/Unknown/Unknown;managed rejection 不得改变 composition;
|
||||
2. raw oracle 逐项匹配 TypeBox 1.3.7 实际执行,含嵌套 compat invalid、unknown
|
||||
thinking key/value 与 union 边界;composer oracle 覆盖 fallback、重复 model、
|
||||
三层 precedence、小数、unmatched override、URL、未知 API 与未知 thinking;
|
||||
3. OAuth/env/`!command`/header material 无损保留且 composer 不执行;未知
|
||||
provider/model/override 字段逐层保留;
|
||||
4. gateway 覆盖非法 header、protected header、缺失 deferred value、跨 origin
|
||||
URL/Host/auth 隔离、protocol identity 与四 family 成功物化;
|
||||
5. 模块依赖结构测试禁止 raw/composer 导入 managed/gateway family,并禁止 gateway
|
||||
反向构造 raw-valid 类型;inspect 后 fingerprint 改变或 import/claim 失败仍不产生
|
||||
Provider/claim。
|
||||
|
||||
## 6. Managed Provider 数据模型(选项 b:provider 默认 + model 覆盖)
|
||||
|
||||
**落点**:新建 `pi_config/model.rs`(替代 041ff113 的 `PiProviderShape` 收窄式 validator)。
|
||||
|
||||
```rust
|
||||
pub struct PiManagedProviderConfig {
|
||||
pub base_url: Option<String>, // provider default
|
||||
pub api: Option<PiManagedApiId>, // opaque provider default
|
||||
pub api_key: Option<String>, // unresolved expression(env/!command 不解析)
|
||||
pub headers: HeaderMap,
|
||||
pub models: Vec<PiManagedModel>,
|
||||
pub model_overrides: BTreeMap<String, PiManagedModelOverride>,
|
||||
pub compat: Option<Value>,
|
||||
pub extra: BTreeMap<String, Value>,
|
||||
}
|
||||
|
||||
pub struct PiManagedModel {
|
||||
pub id: String,
|
||||
pub name: Option<String>,
|
||||
pub base_url: Option<String>, // model override
|
||||
pub api: Option<PiManagedApiId>, // opaque model override
|
||||
// reasoning / input / contextWindow: PiNumber / maxTokens: PiNumber /
|
||||
// compat(Value) / cost / headers / unknown extra(Value)...
|
||||
}
|
||||
|
||||
pub fn effective_pi_model(provider: &PiManagedProviderConfig, model_id: &str)
|
||||
-> Result<PiEffectiveModel, PiConfigError>;
|
||||
```
|
||||
|
||||
managed 唯一继承规则:`api_family = model.api ?? provider.api`;`endpoint =
|
||||
model.base_url ?? provider.base_url`。每个 managed model 必须能解析出两者;provider
|
||||
默认可为空,只要所有 model 显式填写。路由单位固定为 `(provider, model,
|
||||
effective opaque API id)`——一个 Pi provider 可以合法地 1:N。只有 gateway
|
||||
adapter 把 opaque ID 映射为四种受支持 family。Native composer 的
|
||||
默认模型 fallback 属于 Layer 3,不得偷偷扩张 managed 两级继承规则。
|
||||
|
||||
控制面的 Provider 表单必须支持 provider/model 两级 api 与 baseUrl,避免数据面 commits 反向改 schema(这是 041ff113 中 managed validator 收窄导致 M1 的历史教训)。
|
||||
|
||||
## 7. Failover:同 family、跨 host/base path、请求级
|
||||
|
||||
**落点**:重写 `proxy/pi_route.rs` 的 wire profile(endpoint/origin/API-root 移出 equality)、`proxy/forwarder.rs` 的候选物化。
|
||||
|
||||
**Eligibility 谓词**:
|
||||
|
||||
```text
|
||||
eligible(primary, candidate, route) :=
|
||||
candidate exposes route.model_id
|
||||
AND effective_api_family(primary) == route.api_family
|
||||
AND effective_api_family(candidate) == route.api_family
|
||||
AND canonical_request_wire_profile(primary) == canonical_request_wire_profile(candidate)
|
||||
AND canonical_protocol_header_profile(primary) == canonical_protocol_header_profile(candidate)
|
||||
```
|
||||
|
||||
明确不进 equality:scheme/host/port/base path、provider/model `baseUrl`、API key 与认证 header、tenant headers、provider/model/modelOverrides 中普通自定义 headers、display/pricing/排序/默认模型等管理元数据。
|
||||
|
||||
**Wire shape 保守规则**:已知 shaping 字段(`compat`、`reasoning`、`thinkingLevelMap`、`input`、`contextWindow`、`maxTokens` 等)进 equality;除显式 transport/auth/display denylist 外的**未知字段默认纳入 equality**——Pi 新增 shaping 字段在 CC Switch 尚未理解时保守地阻止 failover,不错误放行。JSON object key 顺序不敏感,array 顺序敏感,不做数字/字符串宽松转换。
|
||||
|
||||
**Header 六分类**:
|
||||
|
||||
| 类别 | 处理 |
|
||||
|---|---|
|
||||
| Gateway/HTTP owned(`Host`、framing、hop-by-hop、proxy trace、客户端 gateway bearer) | 剥离或重建,不进 equality |
|
||||
| Protocol/wire contract(`anthropic-version`、`anthropic-beta`、`openai-beta`/`openai-version`) | 最终有效值进 equality |
|
||||
| Candidate auth(`Authorization`、`x-api-key`、`x-goog-api-key`) | 逐候选解析 |
|
||||
| Candidate tenant(`openai-organization`、`openai-project`、`x-goog-user-project`) | 逐候选解析 |
|
||||
| Provider custom(`headers` 容器中除 protocol registry 外全部) | 逐候选解析(属于目标中转商的传输身份) |
|
||||
| Incoming request-local | 统一过滤后同值用于每个候选,不参与 equality |
|
||||
|
||||
未知 header 规则:来自 Pi 配置 `headers` 容器的一律 candidate-local(projector 不把上游 headers 暴露给 Pi,它们不改变网关收到的请求形态);未来某 header 被协议定义为 wire-semantic 时,必须在同一个 adapter 变更中加入 protocol registry 与谓词测试。已知 protocol header 使用不可预测 `!command` 表达式时,该候选标记 failover-ineligible;普通认证/私有 header 仍按 attempt 延迟解析。
|
||||
|
||||
**候选物化**:`materialize_pi_candidate()` 经 `effective_pi_model()` 得到该候选的 family/base URL/headers/auth,复用对应 family adapter 的 URL builder(删除 041ff113 的通用字符串拼接器);最终 `Host` 从该候选最终 URL 重建,不来自客户端或 primary。SSE 只允许在首个语义事件提交给 Pi 之前切换候选(comment keep-alive 不算提交);同一序列化请求 clone 给所有候选,不做候选级 body/model 改写。
|
||||
|
||||
**测试矩阵**(8 个函数):
|
||||
1. `pi_route_identity_matrix`:provider 默认 API、model override API、缺模型、family 不同;
|
||||
2. `pi_cross_origin_materialization_matrix`:不同 scheme/host/port/base path,最终 URL、Host、auth、tenant、自定义 header 全取自当前候选、不串值;
|
||||
3. `pi_wire_profile_canonical_matrix`:key reorder 相等;已知/未知 shaping 变化排除;baseUrl/auth/display/pricing 变化不排除;
|
||||
4. `pi_protocol_header_profile_matrix`:协议头差异排除;tenant/自定义/未知配置 header 差异允许;protected/hop-by-hop 拒绝;
|
||||
5. `pi_four_family_candidate_materialization`:四 family、两级 api/baseUrl、header override 与各 adapter URL/auth 形式;
|
||||
6. `pi_failover_error_policy_matrix`:网络/超时/401/403/404/408/409/429/其他非结构性 4xx/5xx 可换候选;400/405/406/413/414/415/422/501 不换;候选收到字节等价 body;
|
||||
7. `pi_sse_commit_boundary_matrix`:连接失败/首包失败/keep-alive 后失败可切换;首个语义事件后禁止 replay;
|
||||
8. `pi_snapshot_epoch_race`:并发编辑/排序与长请求使用单一 immutable snapshot,旧 epoch 不回写 health/usage、不改全局 current。
|
||||
|
||||
## 8. 契约 6:Pi 原生指令文件(AGENTS.md / SYSTEM.md / APPEND_SYSTEM.md)
|
||||
|
||||
先例:`prompt_files.rs` 已将每个 app 映射到一个原生指令文件并由 prompt library 投影;`hermes_config.rs:1006` 的 `MemoryKind` 是"一个 app 多个原生文件、按 kind 管理、不造影子状态"的既有模式。Pi 与 hermes 的语义差异:SYSTEM/APPEND 没有原生启用位,**文件存在即生效**。
|
||||
|
||||
**落点**:`prompt_files.rs`、新建 `services/pi_prompt_files.rs`、`commands/prompt.rs`、`src/lib/api/prompts.ts`、`PromptPanel.tsx` 重构。
|
||||
|
||||
**类型与 API**:
|
||||
|
||||
```rust
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PiPromptFileKind {
|
||||
GlobalContext, // ~/.pi/agent/AGENTS.md
|
||||
SystemOverride, // ~/.pi/agent/SYSTEM.md
|
||||
SystemAppend, // ~/.pi/agent/APPEND_SYSTEM.md
|
||||
}
|
||||
|
||||
pub struct PiPromptFileRevision { pub exists: bool, pub sha256: Option<String> }
|
||||
|
||||
pub fn read_pi_prompt_file(kind: PiPromptFileKind) -> Result<PiPromptFileSnapshot>;
|
||||
pub fn replace_pi_prompt_file(kind: PiPromptFileKind, expected: PiPromptFileRevision, content: &str)
|
||||
-> Result<PiPromptFileSnapshot>;
|
||||
pub fn delete_pi_prompt_file(kind: PiPromptFileKind, expected: PiPromptFileRevision) -> Result<bool>;
|
||||
```
|
||||
|
||||
IPC 的 direct replace/delete 只允许 `SystemOverride` / `SystemAppend`;`GlobalContext` 由现有 Prompt library service 内部调用(不绕过单选启用与备份逻辑)。
|
||||
|
||||
**Enable/disable 语义(不造影子状态)**:`exists=true → active`,`exists=false → inactive`;创建即启用,确认删除即停用;UI 不提供 Switch、不写 DB enabled 位、不用 `.disabled`/改名/空内容 sentinel;whitespace-only 内容拒绝保存,用户必须显式选择"删除并停用"。状态每次从当前 Pi agent root 实时读取;`PI_CODING_AGENT_DIR` 改变时不迁移、不清理旧 root 文件。
|
||||
|
||||
**Prompt library 投影**:只投影 `GlobalContext`(与其他 app 的 CLAUDE.md/AGENTS.md 同构);SYSTEM/APPEND 不进 `prompts` 表、不参与 profile 切换/deep-link import/portable sync,只提供当前设备原生文件的直接查看、编辑、删除。新增共享文件保护:若 live AGENTS.md 的 revision 已偏离最后投影内容,disable/切换/清空必须报 conflict 并保留文件,不能凭 DB `enabled=true` 覆盖外部修改。
|
||||
|
||||
**UI 结构**:Pi 顶层 tab 从 `System Prompt | Prompt Templates` 改为 `Instruction Files | Prompt Templates`;Instruction Files 内三个子页:
|
||||
1. **Global Context · AGENTS.md** — 现有 Prompt library 列表,文案"注入全局上下文",禁止称 system prompt;
|
||||
2. **System Override · SYSTEM.md** — 直接 Markdown 编辑器 + 红色危险提示("`SYSTEM.md` 会整体替换 Pi 的内置 system prompt,可能移除 Pi 默认的工具说明、行为约束和运行指令;仅在明确需要完全接管时创建")+ 存在/不存在 badge,创建与删除均需确认;
|
||||
3. **System Append · APPEND_SYSTEM.md** — 直接编辑器 + 琥珀色"追加到 Pi system prompt"说明,同样以存在/删除表达启停。
|
||||
|
||||
SYSTEM 与 APPEND 同时存在时的拼装次序与生效时点需对照 Pi 官方源码 commit 验证;验证前 UI 不提供"最终合成预览",不声称即时影响已运行会话。
|
||||
|
||||
**所有权与安全边界**:三个文件均为"用户 + CC Switch 共享";只访问 `get_pi_agent_dir().join(kind.filename())` 三个精确路径,不递归扫描、不清理其他文件;只接受 UTF-8 regular file(上限 1 MiB);symlink/目录/设备文件 fail closed;同目录临时文件 + atomic rename 并保留既有权限;replace/delete 要求 expected revision,外部修改后返回 conflict;三个文件操作共用进程内 mutex;不因隐藏 Pi、关闭 gateway、切 provider、恢复数据库而删除;SYSTEM/APPEND 不进 DB 备份/Profile/自动同步。Prompt Templates 仍在 `prompts/*.md`,即使存在名为 `SYSTEM.md` 的 template 也与 agent root 的 SYSTEM 是不同资源,UI 显示路径区分。
|
||||
|
||||
**故障注入测试**(4):
|
||||
1. 三 kind 精确映射 agent root,SYSTEM/APPEND 存在状态不产生任何 DB 行;
|
||||
2. 外部修改后用旧 revision 写/删必须 conflict;symlink 目标不读、不覆盖、不删除;
|
||||
3. library prompt 切换只改 AGENTS.md,SYSTEM/APPEND 与未知文件逐字节不变;
|
||||
4. 创建 SYSTEM 后 active、确认删除后 absent;空白保存不能暗中充当 disable。
|
||||
|
||||
## 9. 单 PR 结构与盲审协议
|
||||
|
||||
### 9.0 Canonical schema 与 native oracle artifacts
|
||||
|
||||
`schema.rs` 的 current-schema factory、current DDL 与 migration chain 是完整
|
||||
canonical schema authority;其中 `CanonicalTableSpec` / `SchemaInvariantSpec`
|
||||
机械描述 Provider/endpoint/device-local ledger 的承重 invariant,机器可读审查快照
|
||||
位于 `tests/fixtures/pi/canonical-schema-manifest-v1.json`。测试必须从代码 spec
|
||||
生成等价值并与该文件比较,禁止运行时反向读取 fixture 作为 authority。其余 current
|
||||
user table 由下述穷举 `RestoreTableSpec` 锁定恢复策略与固定列。任何 manifest 只能
|
||||
用于 code review/CI drift detection,不得认证外部 imported schema 或使其获得
|
||||
publish 资格。
|
||||
|
||||
Native oracle 六件套固定在 `tests/fixtures/pi/native-oracle/`:
|
||||
|
||||
1. `provider-schema.snapshot.json`:固定 TypeBox schema JSON;
|
||||
2. `raw-oracle-v1.json`:官方 TypeBox 1.3.7 `Value.Check` 的 valid/invalid 样例;
|
||||
3. `composer-oracle-v1.json`:由固定上游 composer 入口实际执行得到的输入与期望结果;
|
||||
4. `transport-oracle-v1.json`:由固定上游 transport resolver 实际执行得到的 deferred
|
||||
value/header 解析结果;
|
||||
5. `field-coverage-v1.json`:枚举 schema 的全部 canonical field path,并为每个字段
|
||||
绑定成功的 TypeBox 执行和其所属 provider/model/override 层的成功 Pi composer
|
||||
执行;
|
||||
6. `provenance-v1.json`:Pi repo/commit、TypeBox version、schema/composer 源文件
|
||||
路径与 SHA-256、schema/oracle SHA-256、generator 与 evaluator operator
|
||||
allowlist。
|
||||
|
||||
CI 只消费 vendored artifacts,不运行 Node、不联网。更新 Pi pin、TypeBox 版本、源码
|
||||
hash、schema snapshot 或 operator allowlist 必须显式重新生成并人工审查全部 oracle。
|
||||
snapshot/provenance hash 不一致时 evaluator 返回 Unknown,不得继续猜测。
|
||||
任一 schema field 缺少实际成功执行、引用不存在的 case、输入中没有该字段或只由
|
||||
本仓库手写期望覆盖时,整个 bundle 认证失败;Pi 无法执行或无法证明的语义必须
|
||||
fail-closed 为 Unknown。
|
||||
|
||||
另有三份机械边界快照:
|
||||
|
||||
1. `tests/fixtures/pi/provider-write-api-v1.json`:Provider write DTO 字段与唯一允许的
|
||||
Database mutation signatures;
|
||||
2. `tests/fixtures/pi/restore-policy-v1.json`:每张 canonical user table 恰好一种 restore
|
||||
policy、固定列/父子拓扑及资源限制;
|
||||
3. `tests/fixtures/pi/module-boundaries-v1.json`:raw/managed/composer/gateway 的允许依赖
|
||||
方向。
|
||||
|
||||
快照由测试从代码 authority 生成等价值再比较;fixture 从不驱动生产行为。
|
||||
|
||||
### 9.1 Commit 序列(16 个,每个独立 green)
|
||||
|
||||
| # | Commit | 交付 |
|
||||
|---:|---|---|
|
||||
| 1 | `docs(pi): freeze support contracts and extension design` | 纯规范性审查契约(`pi-support-review-contract-zh.md`)、状态机、fixture 规范、extensions design-only 附录;themes 明确排除 |
|
||||
| 2 | `domain(pi): add managed model and capability contracts` | capability、API family、两级 api/baseUrl、`effective_pi_model()`、diagnostic 类型、纯函数测试 |
|
||||
| 3 | `db(pi): add aggregate hydration and device-local ledgers` | aggregate hydration、projection/skill ledger、迁移、portable/local 边界、DAO 测试 |
|
||||
| 4 | `config(pi): add read-only native catalog inspection` | JSONC/CST reader、native diagnostics;不写原生配置 |
|
||||
| 5 | `provider(pi): add catalog mutation coordinator` | CRUD/default/import、exact-key projection、完整 aggregate compensation |
|
||||
| 6 | `skill(pi): add ownership-safe deployment reconciliation` | 三态、碰撞、崩溃、root relocation |
|
||||
| 7 | `ui(pi): expose provider native and skill control plane` | Provider 表单、native panel、Skill 三态、四语 i18n |
|
||||
| 8 | `prompt(pi): support native instruction files and templates` | AGENTS library 投影、SYSTEM/APPEND direct CRUD、PromptPanel 重构、templates(slug 收紧) |
|
||||
| 9 | `session(pi): add tree-aware session management` | active branch、恢复/删除、relative sessionDir 显式不可枚举 |
|
||||
| 10 | `test(pi): close and freeze control-plane contracts` | 控制面集成测试;生成并冻结 `tests/fixtures/pi/control-contract-v1.json` |
|
||||
| 11 | `settings(pi): harden local settings and persist gateway credential` | settings 原子写/0600、stable token、legacy 清理、导出断言 |
|
||||
| 12 | `gateway(pi): add listener admission and immutable runtime` | 端口连续性、bind reconcile、server generation、catalog epoch、takeover |
|
||||
| 13 | `usage(pi): make input token semantics explicit` | typed 写入契约、legacy 迁移、四 family usage、UI filter |
|
||||
| 14 | `routing(pi): materialize four native API families` | request route、`effective_pi_model()` 消费、逐候选 URL/auth/header |
|
||||
| 15 | `failover(pi): add cross-host failover and SSE commit fence` | wire predicate、circuit breaker、retry policy、SSE semantic commit |
|
||||
| 16 | `test(pi): close recovery concurrency and end-to-end boundaries` | restore、并发 mutation、stale writeback、长会话 token、完整 UI/i18n 验收 |
|
||||
|
||||
Extensions 只出现在 commit 1 的设计附录;无 Rust/TS/schema/UI 占位。
|
||||
|
||||
### 9.2 三个累积盲审检查点
|
||||
|
||||
- **检查点 A(commit 1–4)**:两位 fresh blind reviewer 审完整 `base..C4`;聚焦类型契约、DB 形态、只读 native 分类;未清零 validated blocker/high/data-integrity 不进入写配置阶段。
|
||||
- **检查点 B(commit 1–10)**:两位全新 reviewer 审完整 `base..C10`;控制面必须零 validated blocker/high;通过后记录 `CONTROL_FREEZE_SHA=C10`,数据面开始。冻结内容:`PiManagedProviderConfig`/`PiEffectiveModel` 及继承规则、Provider IPC payload 与表单字段、managed/native/direct-only 分类、aggregate 与 ledger schema、`control-contract-v1.json`。数据面期间以等价检查守护:
|
||||
|
||||
```bash
|
||||
git diff --exit-code "$CONTROL_FREEZE_SHA"..HEAD -- \
|
||||
src-tauri/src/pi_config/model.rs \
|
||||
src/components/providers/forms/PiProviderForm.tsx \
|
||||
tests/fixtures/pi/control-contract-v1.json
|
||||
```
|
||||
|
||||
(共享 `schema.rs` 因 usage migration 不能整文件冻结,由 schema snapshot/contract tests 约束 managed 表形态。)若数据面需要改上述字段或解释:禁止在 commit 11–16"顺手修正"——停止数据面、修订规范与控制面 commits、检查点 B 失效并重新累积盲审,七轮计数不重置。
|
||||
- **检查点 C(commit 1–16)**:两位全新 reviewer 审完整 `base..HEAD`(不能只看数据面);跑完整 Rust/frontend/migration/故障注入/renderer/diff 检查;主审逐条复现 finding 并审计整个 diff。
|
||||
|
||||
Reviewer 材料:纯规范性审查契约、精确冻结 SHA、中立仓库地图与 authority 表、
|
||||
canonical schema manifest、Pi pin/provenance manifest、schema snapshot、带期望结果的
|
||||
raw/composer oracle fixtures、完整验证命令与通用审查维度;不提供 handoff、既有审查
|
||||
输出、实现摘要、设计论辩、疑似文件/行号、定向检查提示或另一 reviewer 结论。
|
||||
|
||||
### 9.3 七轮预算、体量红线与止损
|
||||
|
||||
- 三个计划检查点占三轮,保留四轮给 material repair;material fix 后由两位 fresh reviewer 重审完整累积范围,仅在明显收敛且补丁局部化时允许单 reviewer;
|
||||
- 预计体量:65–90 文件、净增 9k–12k 行;**超过 12k 行或 90 文件触发一次 scope audit**,不得以"已经写完"为继续扩面的理由;
|
||||
- 风险声明:9k–12k 单 PR 在七轮内零 high 是现实可行但非高置信;三个累积检查点能提前暴露架构错误,但不消除最终 reviewer 审完整范围的认知负荷;
|
||||
- 止损策略:
|
||||
1. 检查点 C 后新出现且局部的数据面问题:只做一个聚合修复批次,下一轮重新双审完整范围;
|
||||
2. 要求反改控制面 schema、或重复 A/B 已出现的不变量:立即停止局部补丁,回契约层并使 B 失效;
|
||||
3. 同一 invariant 连续两轮失败:提前硬停止,不消耗剩余轮次做变体补丁;
|
||||
4. 第七轮后仍有 blocker/high/data-integrity:不启动第八轮、不合并、不静默延期;提交审查历史、未解不变量与可选缩围方案。
|
||||
|
||||
当前重认证轮次固定为:R1–R3 已消耗;R4 只认证 commit 1–4 的新 checkpoint。
|
||||
R4 若再次出现 endpoint ownership、canonical restore、native layering 任一家族,
|
||||
立即项目级停止且不启动 R5。R4 clean 后 R5=检查点 B、R6=检查点 C、R7 仅可用于
|
||||
最终局部修复重认证。R4 若出现新的 material finding,下一轮只能用于检查点 A 修复
|
||||
重认证,不得同时计作 B。
|
||||
|
||||
### 9.4 Phase 0(开工前)
|
||||
|
||||
- 从最新 `main` 建新分支(041ff113 只作 diff/测试/UI 素材,不在其上追加);
|
||||
- 重新执行 handoff §10.5 的 `git ls-remote` 并记录 Pi 官方与参考项目 commit;
|
||||
- 重跑 handoff §16 状态检查;不动 `stash@{0}`。
|
||||
|
||||
2026-07-31 UTC 刷新记录:
|
||||
|
||||
| Repository | `git ls-remote … HEAD` | 本实现角色 |
|
||||
|---|---|---|
|
||||
| `earendil-works/pi` | `977ec833bbb86e245057e9162dbc1443c7b6e707` | 远端观察值;实现 authority 仍固定为经实际执行 oracle 的 `ab366ebe94cacd419d986be454f12b1b9913aaca` |
|
||||
| `CallmeLins/pi-switch` | `5cfa2e8d3f6657b0508ee66ea63acdedfb53388d` | 产品层参考 |
|
||||
| `Ginkgoooo/pi-cc-switch-provider` | `740c06692b15ad891e6d3ac3ee24c053d2626a9a` | 互操作参考 |
|
||||
| `Wing900/Pi-switch` | `e2cc7f7837136277d317b742b650f402048ca80d` | 交互参考 |
|
||||
|
||||
## 10. 041ff113 复用判定
|
||||
|
||||
**可搬运(核心或测试夹具)**:`pi_config/document.rs`(JSONC/CST、unknown-field preservation、原子 patch 及测试);`get_pi_agent_dir` 与 `PI_CODING_AGENT_DIR` 解析;`pi_provider_projections` 表与大部分 DAO;session tree/active-branch parser 及 ID/cycle/size/path guards(仅替换 relative-root fallback);prompt template 单文件 CRUD 核心(收紧 slug 与 SYSTEM 分类);`PiRuntimeStore` 的 immutable lease、odd/even epoch、writeback fence 机制(snapshot 内容与调用顺序更换);Pi handler 的 path/model 提取与四 family parser fixtures;app icon、presets、默认模型 dialog、Prompt UI JSX、四语文案骨架。
|
||||
|
||||
**必须重写**:`PiProviderShape` validator 与全量 import/projector orchestration(→ typed model + diagnostics + coordinator);`previous Provider` 手工补偿;row-only `get_provider_by_id` 与 update 不协调 endpoints 的路径;`apply_effective_pi_status` 及 Pi toggle/sync-all/storage migration/uninstall;`services/proxy.rs` 的 token/投影/runtime 编排(保留 listener lock 思路);`backup.rs` 的 DB token preserve/restore;`pi_route.rs` 的 endpoint-in-wire-profile 与 provider-level shape;forwarder 的 provider-only base URL/header 解析(→ effective model);usage 三处 app whitelist 推断;`runtime.rs` 直接 child kill + 无界 reader join;native entry 的 debug-only skip;`PiProviderForm.tsx` 的必填 provider api/baseUrl;session relative fallback、prompt whitespace slug、AGENTS/SYSTEM 术语;SQLite gateway token 及一切依据 `enabled_pi`/path/content 自动生成 ownership 的迁移。
|
||||
|
||||
## 11. 相比 041ff113 的净减项
|
||||
|
||||
- 删除 SQLite token 存储、import 端保留特判及其协调概念(token 归 device 文件);
|
||||
- 删除 endpoint/origin/API-root 的 wire equality 与 Pi 专属 same-origin 收窄;
|
||||
- 删除 Pi 通用 URL 字符串拼接器,复用 family adapter transport;
|
||||
- 删除 provider-level-only API 限制与"按 family 拆 synthetic provider"路径;
|
||||
- 删除 `apps.pi` 同时充当 intent/ownership/discovery 的状态改写;
|
||||
- 删除从既有路径/content 自动收养 Skill 所有权的迁移;
|
||||
- 不引入通用 `managed_resource_deployments`、通用事务框架、未落地 compatibility group;
|
||||
- 删除静默跳过 native entry(→ 只读诊断 + 显式导入);
|
||||
- 删除 Pi failover 对全局 current 的持久切换与一切跨协议预留 glue;
|
||||
- 删除 Rust/SQL/TS 三处 cache 语义平行推断;
|
||||
- SYSTEM/APPEND 不建 enabled 影子状态、不进 DB,消除一整类同步/备份边界。
|
||||
|
||||
## 12. 验收标准
|
||||
|
||||
Handoff §14 全部条款继续适用,增补:
|
||||
|
||||
- `input_token_semantics` 迁移后无 `0` 残留;两个 SQL export 产物断言不含 token key/value;
|
||||
- Skill 三态在 API/UI 可独立观察;三类 collision fixture 全部 fail closed;
|
||||
- `get_provider_by_id` 与 `get_all_providers` 对同一 provider 返回逐字段一致的 aggregate;
|
||||
- 契约 3 的三个 authoritative_state(`previous_restored` / `mutated_database_authoritative` / `projection_pending`)均有对应测试与用户可见错误报文;
|
||||
- 跨 host failover 的 8 个矩阵测试全过;
|
||||
- SYSTEM/APPEND 的 4 个契约测试全过;Pi Prompt UI 不再出现"system prompt"指代 AGENTS.md 的文案(四语);
|
||||
- `CONTROL_FREEZE_SHA` 等价检查在数据面每个 commit 后通过;
|
||||
- 同一 invariant 在检查点审查中重复失败两次即停并回契约层。
|
||||
|
||||
### 12.1 Canonical restore
|
||||
|
||||
外部数据库永远只是 data source,其 schema 不能获得 authority。运行时不得以
|
||||
manifest、`sqlite_schema` 比较或“结构等价”把输入连接升级为可发布连接。类型屏障
|
||||
固定为:
|
||||
|
||||
```rust
|
||||
struct UntrustedScratch(Connection);
|
||||
struct CanonicalStage(Connection); // 字段私有,仅 schema.rs current-schema factory 构造
|
||||
```
|
||||
|
||||
`publish_canonical_stage` 只消费 `CanonicalStage`;不存在
|
||||
`UntrustedScratch → CanonicalStage` 转换,也不存在接收裸 `Connection` 的 publish。
|
||||
SQL import 与 binary restore 共用以下 data-only 管线:
|
||||
|
||||
```text
|
||||
untrusted input
|
||||
→ UntrustedScratch
|
||||
→ scratch 中跑 current migration
|
||||
→ 逐行 decode + RestoreTableSpec 固定列数据搬运
|
||||
→ schema.rs 从空磁盘库创建全新 CanonicalStage
|
||||
→ 从 live DB 固定列复制 device-local rows
|
||||
→ canonical validation
|
||||
→ SQLite Backup API publish
|
||||
```
|
||||
|
||||
Canonical stage 必须由 `schema.rs` 建立当前全部 table/index/collation、PK conflict
|
||||
policy、CHECK、FK action、canonical trigger/view、PRAGMA 与 `user_version`;不得从
|
||||
source `sqlite_schema` 复制 table/index/trigger/view。restore 生产代码不得包含
|
||||
`CREATE TABLE/INDEX/TRIGGER/VIEW` 字符串。
|
||||
|
||||
`RestoreTableSpec` 必须对当前每张 user table 恰好指定一种策略:
|
||||
|
||||
| 策略 | 当前语义 |
|
||||
|---|---|
|
||||
| `PortableIncoming` | 从不可信输入逐行验证并搬运 portable 数据 |
|
||||
| `PreserveLive` | `pi_provider_projections`、`skill_deployments` 等设备本地数据只从 live 复制 |
|
||||
| `RebuildRuntime` | `provider_health` 等运行态不从输入恢复 |
|
||||
| `SeedCanonical` | 由 current schema/seeding authority 重建 |
|
||||
|
||||
机器测试必须证明 canonical user-table 集合与策略集合完全相等;新增表未选择策略即
|
||||
失败。每个 spec 固定列名、复制顺序、nullable/storage 类型、row validator 与父子
|
||||
拓扑,不得用 source `PRAGMA table_info`、`SELECT *` 或其他 introspection 决定搬运
|
||||
列。
|
||||
|
||||
Data transfer 在 `foreign_keys=ON` 的单个 canonical-stage transaction 中使用 plain
|
||||
`INSERT`;禁止 `INSERT OR IGNORE`、`INSERT OR REPLACE`、`REPLACE`、目标端
|
||||
`ON CONFLICT DO UPDATE` 与复制 `sqlite_sequence`。重复 canonical key、弱 source
|
||||
UNIQUE 产生的重复行、FK orphan、storage class 或 decoder 错误必须整体 abort。
|
||||
Provider 行在写 stage 前必须用生产 hydration 的同一 decoder 验证
|
||||
`settings_config`、`meta`、数值与 NULL,并保留未知 JSON 字段原字节语义,不得通过
|
||||
重序列化抹掉未知字段。
|
||||
|
||||
Scratch 在执行不可信内容前必须关闭 trigger、关闭 `trusted_schema`、开启
|
||||
defensive、设 attached-database limit 为 0,并设置 SQL/value/page-count 限制、有限
|
||||
VM-step budget 与取消检查。不注册应用函数、不启用 load-extension;authorizer 至少
|
||||
拒绝 ATTACH/DETACH、vtable、load-extension、writable-schema、非白名单 PRAGMA 与
|
||||
unknown action。外部 batch 完成后删除 source trigger/view/显式 index,再运行可信
|
||||
migration。`user_version > SCHEMA_VERSION` 立即拒绝;每个
|
||||
`0..=SCHEMA_VERSION` 都必须走现有 migration chain,迁移后缺任一固定列或行 decode
|
||||
失败即 abort。
|
||||
|
||||
文件入口必须 nofollow、regular、identity-stable、size-bounded。SQL 入口复用
|
||||
`symlink_metadata → nofollow open → opened/current identity` 检查并有限读取;
|
||||
binary 入口使用 `READ_ONLY | NOFOLLOW | PRIVATE_CACHE` 与同样 identity 检查。
|
||||
边界冻结为 `MAX_SQL_IMPORT_BYTES=256MiB`、
|
||||
`MAX_BINARY_RESTORE_BYTES=2GiB`、`MAX_SCRATCH_BYTES=2GiB`;改值必须同步更新
|
||||
normative fixture 与 N/N+1 测试。
|
||||
|
||||
`pi_provider_projections` 与 `skill_deployments` 永不读取 source rows,只在既定
|
||||
live/switch commit boundary 持 live DB lock 复制。AUTOINCREMENT 表可搬运显式 ID,
|
||||
但不复制 `sqlite_sequence`,并验证下一次 INSERT 的 ID 大于当前最大值。WAL/SHM
|
||||
永不复制、替换或重命名;binary source 只经 SQLite Backup API 克隆到 private
|
||||
scratch,publish 也只经 Backup API。任一 publish 前失败保持 live 及本机 ledger
|
||||
逐行不变;publish 后 reconcile 失败必须报告真实 authority/admission。
|
||||
|
||||
Publish 前必须执行 `integrity_check`、`foreign_key_check`、全表 decoder sweep 与
|
||||
行为断言,至少实际证明 providers PK 为 BINARY、精确 duplicate 为 ABORT、duplicate
|
||||
不会 REPLACE/级联删除 endpoints。C13 激活 usage 表语义时同步修改 canonical DDL、
|
||||
migration/data mapping 与 restore validator。
|
||||
|
||||
SQL `import_sql_string*` 与 binary `restore_from_backup` 两个公开入口必须参数化覆盖:
|
||||
|
||||
1. NOCASE/REPLACE provider PK、弱 endpoint UNIQUE/FK、注入 trigger/view/index,
|
||||
发布后只剩 current canonical schema;
|
||||
2. 不可解码 Provider JSON、错误 storage class、重复 canonical key、FK orphan,
|
||||
abort 且 live 与本机 ledgers 逐行不变;
|
||||
3. NULL `added_at` 等 nullable 列与显式 AUTOINCREMENT ID 无损往返;
|
||||
4. symlink、目录、FIFO、N+1、VM/page budget、ATTACH/VACUUM INTO 在 publish 前
|
||||
失败;
|
||||
5. 每个受支持 `user_version=0..SCHEMA_VERSION` 至少一个 migration sentinel,
|
||||
current、上一版、最老版同时走 SQL 与 binary。
|
||||
|
||||
## 13. 附录:Pi Extensions(design-only,本 PR 不实现)
|
||||
|
||||
### 13.1 信息边界(诚实声明)
|
||||
|
||||
仓库内可确认:handoff 记录 Pi 核心有 extensions/packages/project resources;社区示例用 `pi install npm:...` / `pi install git:...`;`pi_config/tests.rs:392` 证明投影必须保留未知 `settings.json.packages` 与 `theme` 字段。仓库内**不可确认**:extension manifest、全局目录、load order、启停字段、inventory/remove 命令、trust 流程。社区安装示例不是 Pi 原生契约;以下所有条件能力在未来实现前必须对照 Pi 官方文档与固定源码 commit 验证。
|
||||
|
||||
### 13.2 未来可安全支持的窄能力(全部有条件)
|
||||
|
||||
1. **只读 inventory**:仅当可经官方 machine-readable CLI 或静态 manifest/config 获取、且不加载不执行 extension 代码;展示 native id、来源、scope、位置、原生启停状态与诊断;
|
||||
2. **原生 enable/disable passthrough**:仅当 Pi 存在明确持久的原生启停位或官方 CLI 操作;状态 authority 始终是 Pi 原生配置,CC Switch 不建 DB mirror;
|
||||
3. **install/remove 委托**:仅当官方 CLI 提供 scope 明确、非交互(或可安全交互)、结果可判定的接口;用户逐次确认后调用;不预设命令名/参数/返回格式;
|
||||
4. **固定 extension 窄集成**:参考 `claude_plugin.rs` 的"检测 + 修改一个固定 owned key + 清理该 key"边界,但必须补 atomic write、revision conflict、exact-key ownership,不复制其整文件容错写法。
|
||||
|
||||
### 13.3 绝对不做
|
||||
|
||||
不实现 registry/依赖解析/semver/lockfile/升级策略等第二套 package manager;不下载/解压/安装 npm/git 内容;不替用户决定代码 trust、不自动批准首次执行;不管理 project-local extensions/resources;不做跨来源版本仲裁;不执行 extension 获取 metadata;不复制/接管 extension 凭据;不自动安装/更新/卸载;不把 extension 提供的 MCP 伪装成 Pi 原生 MCP(`McpAppId` 维持排除 pi);不因安装 extension 自动 claim/import 其 provider overlay;不顺带加入 themes/keybindings。
|
||||
|
||||
### 13.4 数据所有权
|
||||
|
||||
| 数据/资源 | Authority | CC Switch 允许 |
|
||||
|---|---|---|
|
||||
| Global extension inventory | Pi 官方配置/CLI | 请求期只读 DTO,不建持久 mirror |
|
||||
| 原生 enabled 状态 | Pi 原生启停位(若存在) | exact-key/官方 CLI passthrough,无 DB enabled |
|
||||
| Package bytes/依赖/lockfile | Pi 原生 package 机制 | 不直接写 |
|
||||
| Project-local extension 与 trust | Pi + 项目用户 | 不管理、不修改 |
|
||||
| Extension provider overlay | `models.json` | 复用 `PiNativeEntryKind::ExtensionOverlay`,不自动 claim |
|
||||
| Extension 暴露的 Skills | Pi discovery | 显示为 native discovered,不写 `skill_deployments` |
|
||||
| 凭据与私有配置 | Pi auth/env/extension | 不读取、不迁移、不导出 |
|
||||
| 安装操作输出 | Pi CLI 子进程 | 仅本次 UI 展示与脱敏日志,不成为状态 authority |
|
||||
|
||||
### 13.5 与既有契约的复用点
|
||||
|
||||
Provider overlay 复用契约 5 的分层诊断;extension Skill 复用 discovery 展示(不伪造 ownership);若官方存在 enable key,复用 exact-key JSONC mutation + unknown-field preservation + revision conflict;若允许 CLI 委托,复用有界 stdout + 完整进程树 timeout/cancel;extension 安装不纳入 Provider/Skill mutation coordinator(安装目录与回滚 authority 属 Pi)。
|
||||
|
||||
### 13.6 未来实现前必须回答(对照固定官方 commit)
|
||||
|
||||
1. 官方 manifest、全局目录、project-local 目录分别是什么?
|
||||
2. `settings.json.packages` 的确切 schema 与 load order?
|
||||
3. extension 与 package 是否一一对应,是否存在非 package extension?
|
||||
4. inventory 能否在不执行任意代码的前提下完成?
|
||||
5. 是否有原生 enable/disable 位;若没有,放弃开关 UI;
|
||||
6. 官方 list/install/remove 命令与 machine-readable 输出?
|
||||
7. install/remove 是否触发 trust、postinstall、网络或项目文件修改?
|
||||
8. Pi 与 CC Switch 并发修改原生配置的锁/冲突检测机制?
|
||||
9. 卸载是否删除用户数据,是否有 dry-run/保留数据选项?
|
||||
10. extension 注册的 provider/model 如何归因到具体 extension?
|
||||
11. 操作失败后 Pi 的权威恢复/重扫机制?
|
||||
|
||||
以上未回答前,extensions 只停留在本附录:不创建 disabled UI、IPC command、数据库表或 capability flag。
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,942 @@
|
||||
# Pi 一等支持:盲审规范契约
|
||||
|
||||
> 状态:规范性(normative)
|
||||
>
|
||||
> 用途:本文件是 Pi 一等支持累积盲审的唯一产品与行为契约。审查只使用本文件列明的 authority、artifacts 与验证命令。
|
||||
>
|
||||
> 优先级:本文件若与 `pi-support-contracts-zh.md` 冲突,以后者为准;Pi 原生格式与加载行为以本节固定的官方源码提交为准。
|
||||
|
||||
## 0. 固定来源与术语
|
||||
|
||||
### 0.1 源码固定点
|
||||
|
||||
Pi 实现 authority 与 2026-07-31 UTC Phase 0 远端观察值必须分开记录:
|
||||
|
||||
| 来源 | 提交 | 规范用途 |
|
||||
|---|---|---|
|
||||
| `earendil-works/pi` | `ab366ebe94cacd419d986be454f12b1b9913aaca` | 唯一 Pi implementation pin;schema、composer、transport 与源码注释 authority |
|
||||
| `earendil-works/pi` remote HEAD | `977ec833bbb86e245057e9162dbc1443c7b6e707` | Phase 0 观察值;不是第二个 implementation pin |
|
||||
| `CallmeLins/pi-switch` | `5cfa2e8d3f6657b0508ee66ea63acdedfb53388d` | 产品层 gateway/failover 参考,不定义 Pi 原生契约 |
|
||||
| `Ginkgoooo/pi-cc-switch-provider` | `740c06692b15ad891e6d3ac3ee24c053d2626a9a` | 互操作边界参考,不定义主架构 |
|
||||
| `Wing900/Pi-switch` | `e2cc7f7837136277d317b742b650f402048ca80d` | Provider/Model 桌面交互参考,不定义数据契约 |
|
||||
|
||||
实现与测试中的 Pi schema、文件加载顺序和行为注释只能标明 implementation pin。
|
||||
社区项目和远端观察值不得覆盖该 authority。
|
||||
|
||||
### 0.2 术语
|
||||
|
||||
- `logical_app_type`:产品归属、UI 与筛选;Pi 的值为 `pi`。
|
||||
- `wire_api_family`:请求实际使用的协议 family。
|
||||
- `input_token_semantics`:wire family 对输入与缓存 token 的计数语义。
|
||||
- `managed aggregate`:SQLite 中由 CC Switch 管理的 Provider 主行及全部持久化子行。
|
||||
- `native entry`:当前 Pi `models.json` 中的一个原生 provider key。
|
||||
- `projection claim`:CC Switch 对 `models.json` 中一个精确 key 的设备本地所有权证据。
|
||||
- `desired_enabled`:用户希望 CC Switch 为 Pi 部署某 Skill。
|
||||
- `owned_deployment`:设备本地 ledger 证明目标由 CC Switch 部署。
|
||||
- `effectively_discovered`:Pi 按当前 discovery 规则实际发现该 Skill。
|
||||
- `gateway token`:设备安装级 loopback bearer 凭据。
|
||||
- `server_generation`:当前进程 listener 实例代次。
|
||||
- `catalog_epoch`:catalog mutation 与请求/writeback fence。
|
||||
|
||||
上述概念均为独立状态;不得通过 `app_type`、文件存在、内容相同或路径相同互相推导。
|
||||
|
||||
## 1. 功能范围
|
||||
|
||||
### 1.1 必须交付
|
||||
|
||||
- Pi 应用注册、显示/隐藏和切换;
|
||||
- Provider/Model CRUD、复制、排序、预设、拉取模型、默认 Provider/Model;
|
||||
- Pi 原生全局 Skills;
|
||||
- 全局 `AGENTS.md`;
|
||||
- 全局 `SYSTEM.md` 与 `APPEND_SYSTEM.md`;
|
||||
- 全局 prompt templates;
|
||||
- Pi sessions 的发现、树形 active branch 展示、恢复和显式删除;
|
||||
- loopback gateway、同 family 请求级 failover、circuit breaker 与 SSE;
|
||||
- usage 明细、聚合、费用、Pi 筛选;
|
||||
- portable backup、同步、restore 与运行态 reconcile;
|
||||
- en、zh、zh-TW、ja 四语 UI。
|
||||
|
||||
### 1.2 明确排除
|
||||
|
||||
- 跨 API family 转换;
|
||||
- User-Agent 伪装;
|
||||
- Pi `auth.json` 或 OAuth 生命周期管理;
|
||||
- MCP;
|
||||
- themes、keybindings;
|
||||
- project-local resources、packages 与 trust;
|
||||
- extensions/package 的 inventory、安装、启停、升级或删除;
|
||||
- agent 内自动模型切换。
|
||||
|
||||
extensions 仅允许保留本文件第 16 节的设计契约;不得出现 Rust、SQL、TS、UI、IPC、capability flag 或空占位实现。
|
||||
|
||||
## 2. 数据所有权
|
||||
|
||||
| 数据或资源 | 权威来源 | 允许操作 | 禁止操作 |
|
||||
|---|---|---|---|
|
||||
| Pi managed Provider aggregate | SQLite | aggregate CRUD、默认与 route state | 把它视作 Pi 全部 provider universe |
|
||||
| `pi_provider_projections` | 当前设备 SQLite | 记录精确 provider key claim | 以前缀、内容或命名猜所有权 |
|
||||
| `models.json` | Pi、用户与第三方共享 | 原子 patch 已 claim 的精确 key | 整体覆盖或删除 unowned key |
|
||||
| `settings.json`(Pi) | Pi 与用户共享 | 原子 patch 明确的默认字段 | 重写未知字段 |
|
||||
| `auth.json` | Pi | 只判断凭据能力是否可用 | 读取、复制、导出、覆盖凭据 |
|
||||
| `skill_deployments` | 当前设备 SQLite | 证明 CC Switch 的 Pi deployment | portable/sync 搬运或由内容猜记录 |
|
||||
| Pi Skill 目录 | Pi、用户与 CC Switch 共享 | 仅操作 identity/digest 匹配的 owned target | 覆盖或删除 unowned target |
|
||||
| `AGENTS.md` | 用户与 CC Switch 共享 | revision-safe library 投影 | 以 DB enabled 状态覆盖外部修改 |
|
||||
| `SYSTEM.md` / `APPEND_SYSTEM.md` | 当前设备用户共享文件 | revision-safe 直接 CRUD | 建 DB enabled mirror 或进 portable sync |
|
||||
| prompt templates | 当前设备用户共享目录 | 用户指定的单文件 CRUD | 递归扫描或删除未知文件 |
|
||||
| Pi sessions | Pi | 只读解析、用户显式恢复/删除 | 修改 transcript |
|
||||
| gateway token | 设备 settings 文件 | 本地生成、持久化、显式旋转 | 进入 DB authority、URL、日志、export/sync |
|
||||
| runtime snapshot | 当前成功发布的 generation | immutable request lease | 请求中回读可变 DB/global Provider |
|
||||
|
||||
所有 `~/.pi/agent` 路径必须经一个统一的 `get_pi_agent_dir()` 解析并尊重 `PI_CODING_AGENT_DIR`。Session 另按第 9 节解析 session root。
|
||||
|
||||
## 3. Managed Provider 与 capability
|
||||
|
||||
### 3.1 API family
|
||||
|
||||
本范围只代理下列四个 Pi 原生 family:
|
||||
|
||||
- `anthropic-messages`
|
||||
- `openai-completions`
|
||||
- `openai-responses`
|
||||
- `google-generative-ai`
|
||||
|
||||
其他合法 Pi native family 可显示为 unsupported/direct-only 诊断,但不得被误路由。
|
||||
|
||||
### 3.2 两级模型
|
||||
|
||||
控制面必须表达:
|
||||
|
||||
```text
|
||||
PiManagedProviderConfig
|
||||
provider baseUrl?: string
|
||||
provider api?: PiManagedApiId
|
||||
apiKey?: unresolved expression
|
||||
headers
|
||||
models[]
|
||||
modelOverrides
|
||||
compat
|
||||
|
||||
PiManagedModel
|
||||
id
|
||||
name?
|
||||
model baseUrl?: string
|
||||
model api?: PiManagedApiId
|
||||
contextWindow?: PiNumber
|
||||
maxTokens?: PiNumber
|
||||
remaining native shaping fields
|
||||
```
|
||||
|
||||
唯一继承规则:
|
||||
|
||||
```text
|
||||
effective api family = model.api ?? provider.api
|
||||
effective endpoint = model.baseUrl ?? provider.baseUrl
|
||||
```
|
||||
|
||||
每个 managed model 必须解析出 family 与 endpoint。Provider 默认字段可以为空,只要每个 model 自身完整。一个 Provider 可以包含多个不同 family 的 model;路由单位是 `(provider_id, model_id, effective_api_family)`。
|
||||
|
||||
`effective_pi_model()` 是投影、诊断、runtime、routing 与 failover 的共同解析入口。不得在调用点复制继承规则。
|
||||
|
||||
`PiNumber` 保持 Pi/JavaScript JSON number 语义;整数与小数都可无损经过
|
||||
raw→managed→projection round-trip,不得以 `u64` DTO 收窄。Managed 额外的正值
|
||||
要求属于 managed-layer narrowing,不改变 raw validity。
|
||||
|
||||
Managed API ID 是 opaque non-empty string,必须无损 round-trip。四 family enum
|
||||
只能存在于 gateway 层;新的 Pi-valid API ID 本身不构成 managed rejection。
|
||||
|
||||
### 3.3 capability 独立性
|
||||
|
||||
每个 native entry 必须独立表达分层结果:
|
||||
|
||||
- raw native validity:`valid | invalid | unknown`;
|
||||
- managed assessment:`manageable | unsupported`;
|
||||
- composition status:`composed | failed | unknown`;
|
||||
- management status:`importable | managed | unsupported`;
|
||||
- gateway status:`proxyable | direct_only | unknown`。
|
||||
|
||||
```text
|
||||
importable = raw validity == valid AND managed assessment == manageable
|
||||
proxyable = raw validity == valid
|
||||
AND composition status == composed
|
||||
AND API family、credential 与 headers 均受 gateway 支持
|
||||
```
|
||||
|
||||
后层不得把 raw invalid/unknown 提升为 importable/proxyable。`valid` 不蕴含
|
||||
`manageable`;`manageable` 不蕴含 `proxyable`;direct-only managed entry 不得进入
|
||||
failover 候选。
|
||||
|
||||
Reason 必须是 `{layer, code, json_pointer?}`。raw schema failure、managed
|
||||
narrowing、composition failure 与 gateway limitation 使用不同 layer 和稳定 code。
|
||||
|
||||
## 4. Native catalog diagnostics 与 import
|
||||
|
||||
### 4.1 只读检查
|
||||
|
||||
检查必须每次读取当前 `models.json`,不得写文件、数据库、claim、默认值或 runtime。每个原生 key 都必须产生诊断,包括:
|
||||
|
||||
- built-in overlay;
|
||||
- modelOverrides-only;
|
||||
- 完整 custom catalog;
|
||||
- extension overlay;
|
||||
- malformed/unknown shape。
|
||||
|
||||
每项至少返回 provider key、可选显示名、确定性 fingerprint、entry kind、四层判定、
|
||||
management/gateway capability 和结构化 reason。
|
||||
|
||||
Layer 1 只运行 vendored pinned TypeBox schema 的 JSON evaluator:
|
||||
|
||||
- 直接消费 raw JSON,不先反序列化为 managed 类型;
|
||||
- 支持 provenance allowlist 中的 required/optional、absent/null、object/record/array、
|
||||
union/literal/enum、string/boolean/Number、additionalProperties,以及完整递归
|
||||
`compat`;
|
||||
- 遇到未支持 operator、custom validator/transform、pin/hash 漂移或适用 schema
|
||||
不确定时返回 unknown;
|
||||
- 不解释 built-in/extension registry、OAuth、env、`!command`、文件、网络、
|
||||
credential、composer fallback 或 gateway。
|
||||
|
||||
Layer 2 独立评估 managed narrowing:model id 唯一非空、override key 闭合、present
|
||||
URL 为非空绝对 HTTP(S)。API ID 以 opaque string 保留。Pi Number 保持 number
|
||||
语义,小数不得因整数 DTO 而变成 raw invalid。
|
||||
|
||||
Layer 3 只组合显式 custom catalog、显式 models 与 matching overrides。默认值与
|
||||
precedence 来自实际执行 pinned Pi 的 vendored composer oracle。Composer 直接消费
|
||||
raw-valid value,不先转换为 managed DTO;未知 provider/model/override shaping
|
||||
字段、thinking map 与 compat 必须以 JSON value 无损保留。它是 credential-blind
|
||||
纯函数:OAuth/env/`!command`/文件/网络表达式只作为 deferred material 保留,不能
|
||||
执行或令 composition 变为 unknown。只有必须依赖但无法执行的
|
||||
built-in/extension catalog/default-model 语义才 fail-closed 为 unknown,并返回
|
||||
精确 catalog-required reason。
|
||||
|
||||
Layer 4 才把 composed API ID 缩窄到四个 gateway family,并在 capability 阶段构造
|
||||
完整 `CandidateHeaderPlan`。非法/protected/hop-by-hop header 使候选不可代理;
|
||||
request 阶段必须在任何网络 I/O 前逐候选物化 deferred auth、tenant、自定义与
|
||||
protocol header,失败只淘汰当前候选,不得复用前一候选的 URL、Host、auth 或
|
||||
header。Gateway 不支持已知 opaque API ID 时为 direct-only,不得把 composition
|
||||
降为 unknown。
|
||||
|
||||
固定状态矩阵:
|
||||
|
||||
- 未知 API 的显式 custom catalog:raw valid + composed + importable +
|
||||
direct-only(`api_family_not_gateway_supported`);
|
||||
- built-in/extension 因缺 catalog/default-model context:raw valid +
|
||||
unknown composition + unknown gateway。
|
||||
|
||||
未知或不支持的 entry 必须可见,不得只记录日志。
|
||||
|
||||
### 4.2 显式 import
|
||||
|
||||
只有 `importable` entry 可导入。Import 必须在 Pi switch boundary 内:
|
||||
|
||||
1. 重读精确 key;
|
||||
2. 重算并比较 caller 提供的 fingerprint;
|
||||
3. 原子插入完整 aggregate 与精确 claim;
|
||||
4. 不写 `models.json`;
|
||||
5. 不改默认 Provider/Model;
|
||||
6. 不解析 env、`!command` 或 OAuth 凭据;
|
||||
7. 不自动加入 failover queue。
|
||||
|
||||
fingerprint 变化、claim collision、unsupported shape 或 DB failure必须零副作用。成功重试不得生成重复 Provider 或 claim。
|
||||
|
||||
## 5. Provider aggregate 与数据库 ledger
|
||||
|
||||
### 5.1 Aggregate hydration
|
||||
|
||||
Provider DAO 必须提供完整 aggregate 的单项与全量读取。旧的 Provider 单行读取只能
|
||||
从 aggregate API 转换。Aggregate 只用于 read、rollback snapshot 与 restore,不是
|
||||
通用 mutation DTO。
|
||||
|
||||
- 单项与全量读取对同一 Provider 必须逐字段一致;
|
||||
- 删除 `save_provider()`、`save_provider_row_on_tx()`、全部
|
||||
`upsert_provider*`、通用 aggregate save 与同签名兼容壳;结构扫描必须证明生产
|
||||
与 integration test 真实语法中禁止符号为零;
|
||||
- `Provider`、`ProviderAggregate` 与 hydration 后的
|
||||
`ProviderMeta.custom_endpoints` 都不是主行 write DTO;不得提供从 read DTO 到
|
||||
write DTO 的隐式 `From`/`Into`;
|
||||
- write surface 只暴露 `create_provider(NewProviderAggregate)`、
|
||||
`update_provider(&ProviderKey,&ProviderRowUpdate)`、
|
||||
`rename_db_only_additive_provider(RenameProvider)` 与
|
||||
`add/remove/touch_provider_endpoint` 六类类型化操作;
|
||||
- `ProviderRowUpdate` 不得含 `id`、endpoint 集合、`is_current`、
|
||||
`in_failover_queue`、`sort_index`;update 恰好命中一行,零行返回 NotFound,
|
||||
永不转为 create;
|
||||
- create 在同一事务 strict INSERT 主行与全部 initial endpoints;重复 Provider、
|
||||
endpoint 或任一约束失败时主行、endpoint、current/failover 状态均零副作用;
|
||||
- existing-provider 表单与 update IPC 不携带 endpoints;后端收到非空
|
||||
`custom_endpoints` 必须显式拒绝,create IPC 在 service boundary 拆为
|
||||
`initial_endpoints`;
|
||||
- `CustomEndpoint.added_at` 在 Rust、IPC 与 TS 全链路 nullable;不得使用 epoch 0、
|
||||
`COALESCE` 或 unwrap default 代替 NULL;
|
||||
- add/remove/touch endpoint 使用精确行 mutation;touch 影响零行返回 NotFound;
|
||||
- rename 只允许 OpenCode/OpenClaw 的 DB-only、非 live、非 OMO additive
|
||||
Provider;单事务 strict INSERT 新主行、复制全部 endpoint 的
|
||||
`url/added_at/last_used`(含 NULL)、删除旧主行;目标冲突或不允许的来源均整体
|
||||
失败。`provider_health` 可丢弃,usage/history 保留旧 provider id;
|
||||
- live import、seed、universal sync 等 reconcile 必须先读取,再显式 strict create
|
||||
或 strict update;并发 create 冲突不得退化为 overwrite;
|
||||
- exact endpoint replacement 只允许 switch-lock 串行的 compensation coordinator
|
||||
调用 `pub(super)`/sealed `restore_provider_aggregate_on_tx()`;
|
||||
- delete/restore snapshot 必须包含 Provider 主行、endpoints、projection claims 与 Pi route state;
|
||||
- `provider_health` 可重建,不属于 rollback snapshot。
|
||||
|
||||
针对 `providers` / `provider_endpoints` 的 production DML 只允许存在于 provider
|
||||
row/state DAO、endpoint DAO、schema migration 与 canonical data copier。结构扫描
|
||||
必须区分 DML/DDL/SELECT/普通字符串、忽略 `#[cfg(test)]`,并以禁止符号、越权 DML
|
||||
的负向 fixture 证明规则会失败。
|
||||
|
||||
认证测试必须经过真实 `ProviderService`:
|
||||
|
||||
1. create 多个 initial endpoints 后完整 hydration 集合完全一致,duplicate create
|
||||
零副作用;
|
||||
2. stale update 前后的公开 endpoint add/remove/touch 全部存活,非空 endpoint
|
||||
update payload 显式拒绝;
|
||||
3. rename 成功保留 endpoint 与 NULL 时间戳;target conflict、live、OMO、非
|
||||
additive rename 均零副作用。
|
||||
|
||||
### 5.2 Projection ledger
|
||||
|
||||
projection claim 是设备本地状态:
|
||||
|
||||
- 精确绑定 provider key;
|
||||
- 不进入 portable export/sync;
|
||||
- restore 时保留 live device 的 ledger;
|
||||
- stale claim 清理失败必须显式失败;
|
||||
- claim collision 不得覆盖原生 entry。
|
||||
|
||||
### 5.3 Skill ledger
|
||||
|
||||
表的规范形态:
|
||||
|
||||
```sql
|
||||
CREATE TABLE skill_deployments (
|
||||
app_type TEXT NOT NULL CHECK (app_type = 'pi'),
|
||||
skill_id TEXT NOT NULL,
|
||||
destination TEXT NOT NULL,
|
||||
destination_key TEXT NOT NULL,
|
||||
method TEXT NOT NULL CHECK (method IN ('symlink', 'copy')),
|
||||
source_identity TEXT NOT NULL,
|
||||
deployed_digest TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (app_type, skill_id, destination_key),
|
||||
UNIQUE (app_type, destination_key)
|
||||
);
|
||||
```
|
||||
|
||||
- `destination` 是部署时词法规范化的绝对路径;
|
||||
- `destination_key` 按目标卷大小写语义生成,不使用 SQLite `NOCASE`;
|
||||
- `Auto` 必须落为实际 `symlink` 或 `copy`;
|
||||
- 不加 cascade FK;
|
||||
- portable/sync skip 且 restore 保留 live device rows;
|
||||
- 同一 skill 可在 root relocation 期间短暂拥有新旧两条记录。
|
||||
|
||||
## 6. Catalog mutation 状态机
|
||||
|
||||
Provider mutation 集合:
|
||||
|
||||
```text
|
||||
CreateProvider(provider, initial_endpoints)
|
||||
UpdateProvider(provider)
|
||||
AddEndpoint(provider_id, endpoint)
|
||||
RemoveEndpoint(provider_id, url)
|
||||
DeleteProvider(provider_id)
|
||||
ImportNative(provider_key, expected_fingerprint)
|
||||
SetDefault(provider_id, model_id)
|
||||
```
|
||||
|
||||
Provider form 的 update submit 不得携带 endpoint 集合;endpoint 控件只调用专用
|
||||
mutation。`last_used` 当前不影响 routing,使用精确单行 touch 且不进入 catalog
|
||||
epoch;若将来参与 routing,必须升级进入 switch boundary。
|
||||
|
||||
所有 Pi Provider CRUD、endpoint CRUD、default、import、route/failover queue、takeover start/stop 与 database restore 必须共享一个 switch boundary,锁序固定为:
|
||||
|
||||
```text
|
||||
Pi switch lock
|
||||
-> listener lock
|
||||
-> begin odd catalog epoch / close Pi admission
|
||||
-> load complete PiCatalogSnapshot
|
||||
-> short DB transaction
|
||||
-> atomic exact-key projection
|
||||
-> build runtime from the same hydrated catalog
|
||||
-> publish even epoch
|
||||
-> activate listener generation
|
||||
```
|
||||
|
||||
不得跨文件 I/O 持 SQLite connection mutex。Projection helper 不得自行开始 epoch。
|
||||
|
||||
纯排序只取得 Pi switch lock、执行单个 DB 事务并原子发布下一份 even-epoch runtime;不得取得 listener lock、进入 odd epoch或修改 `models.json`。
|
||||
|
||||
### 6.1 成功
|
||||
|
||||
DB、精确 projection、runtime 与 listener 必须指向同一 catalog generation。旧 request lease 可以完成响应,但 epoch 不匹配时不得写 usage/health。
|
||||
|
||||
### 6.2 失败
|
||||
|
||||
| 失败点 | 必须结果 |
|
||||
|---|---|
|
||||
| DB mutation 前 | DB/文件不变,旧 runtime 以新 even epoch republish |
|
||||
| DB 后、projection/runtime 失败,完整恢复成功 | aggregate、claims、route state 与精确文件集合恢复;返回原错误;`authoritative_state=previous_restored` |
|
||||
| DB restore 事务失败 | restore 事务自身回滚;mutated DB 为 authority;按实际 DB reconcile;`authoritative_state=mutated_database_authoritative` |
|
||||
| DB 已恢复、文件恢复失败 | 旧 DB 为 authority;Pi admission 保持关闭;`authoritative_state=projection_pending` |
|
||||
|
||||
任何分支都不得返回假成功,不得将旧 runtime 发布到 mutated DB 之上。
|
||||
|
||||
### 6.3 故障注入
|
||||
|
||||
必须覆盖:
|
||||
|
||||
- 带多个 endpoint 的 Provider 删除后 projection replace 失败;
|
||||
- rollback-reject trigger 与 projection failure 同时发生;
|
||||
- projection 阻塞时并发 edit、default、restore 与请求;
|
||||
- restore staging 与 live device ledger 的 commit-boundary race;
|
||||
- stale epoch usage/health writeback。
|
||||
|
||||
## 7. Skill 三态与安全部署
|
||||
|
||||
API 必须独立返回:
|
||||
|
||||
```text
|
||||
desired_enabled
|
||||
owned_deployment
|
||||
effectively_discovered
|
||||
ownership
|
||||
discovery
|
||||
issue
|
||||
```
|
||||
|
||||
UI toggle 只绑定 `desired_enabled`。原生发现但未 owned 的 Skill 显示“原生 Skill 活跃”,toggle 不得点亮。
|
||||
|
||||
### 7.1 Enable
|
||||
|
||||
在共享 deployment mutex 内:
|
||||
|
||||
1. 解析 source identity 与目标;
|
||||
2. 对同名目录、同内容 copy、指向 SSOT 的 symlink 一律视为 unowned collision;
|
||||
3. 使用同目录临时路径部署并 atomic rename;
|
||||
4. 在一个 DB 事务中写 ledger 与 `desired_enabled=true`。
|
||||
|
||||
collision 必须拒绝且不改目标、ledger 或 desired。
|
||||
|
||||
### 7.2 Disable
|
||||
|
||||
1. 先持久化 `desired_enabled=false`;
|
||||
2. 只有 ledger 存在且当前 identity/digest 匹配时删除目标;
|
||||
3. 成功删除后删 ledger。
|
||||
|
||||
owned copy 漂移或 symlink retarget 后必须拒删、保留 ledger并显示冲突。
|
||||
|
||||
### 7.3 Crash 与 relocation
|
||||
|
||||
- 部署完成但 ledger 写入前崩溃:目标为 unowned orphan;不得自动删除或收养;
|
||||
- agent root 变化:旧记录显示 `stale_destination`;
|
||||
- reconcile 先部署并登记新目标,再只在旧 identity/digest 匹配时清理旧目标;
|
||||
- toggle、sync-all、uninstall、Skill 更新和 storage migration 共用同一 mutex 与判断。
|
||||
|
||||
Discovery 必须遵循 frontmatter `name`、必填 `description`、root file/递归规则、ignore 文件、位置优先级与 effective-name first-wins shadow。
|
||||
|
||||
## 8. 原生指令文件与模板
|
||||
|
||||
### 8.1 三种文件
|
||||
|
||||
```text
|
||||
GlobalContext -> <agent-root>/AGENTS.md
|
||||
SystemOverride -> <agent-root>/SYSTEM.md
|
||||
SystemAppend -> <agent-root>/APPEND_SYSTEM.md
|
||||
```
|
||||
|
||||
只有 `SystemOverride` 与 `SystemAppend` 暴露 direct IPC replace/delete。`GlobalContext` 只能由现有 Prompt library service 投影。
|
||||
|
||||
官方固定提交的拼装契约:
|
||||
|
||||
- `SYSTEM.md` 存在时替换 Pi 默认 system prompt;
|
||||
- `APPEND_SYSTEM.md` 的内容追加在所选 default/SYSTEM prompt 之后;
|
||||
- context files 再作为 project context 追加;
|
||||
- 已运行进程只在其资源 reload/rebuild 边界观察变化,UI 不得承诺即时影响现有会话。
|
||||
|
||||
### 8.2 文件状态与并发
|
||||
|
||||
- 文件存在即 active,不存在即 inactive;
|
||||
- 不提供 Switch,不建 DB enabled 字段,不使用空内容、改名或 `.disabled` 表达停用;
|
||||
- whitespace-only replace 必须拒绝;
|
||||
- replace/delete 必须带 `exists + sha256` expected revision;
|
||||
- 外部修改后旧 revision 必须 conflict;
|
||||
- 三文件操作共用一个进程内 mutex;
|
||||
- 只接受 UTF-8 regular file,最大 1 MiB;
|
||||
- symlink、目录、设备文件 fail closed;
|
||||
- 写入使用同目录临时文件、flush/fsync 与 atomic rename,并保留既有权限;
|
||||
- agent root 变化时不迁移或清理旧 root;
|
||||
- 隐藏 Pi、关闭 gateway、切 Provider 或恢复数据库均不得删除这些文件。
|
||||
|
||||
### 8.3 Library 与 templates
|
||||
|
||||
- Prompt library 只投影 `AGENTS.md`;
|
||||
- live `AGENTS.md` revision 偏离最后投影内容时,disable/switch/clear 必须 conflict并保留文件;
|
||||
- `SYSTEM.md`/`APPEND_SYSTEM.md` 不进 `prompts` 表、Profile、portable backup 或自动同步;
|
||||
- templates 只位于 `<agent-root>/prompts/*.md`,非递归;
|
||||
- slug 必须是 Pi 可调用的单 token 安全文件名,拒绝空白、路径遍历、separator、控制字符、保留名及与 agent-root 指令文件混淆的名字;
|
||||
- template 名为 `SYSTEM.md` 时仍是模板目录资源,UI 必须显示完整路径以区分。
|
||||
|
||||
## 9. Sessions
|
||||
|
||||
- Pi JSONL 按 entry `id/parentId` 形成树;
|
||||
- UI 展示 header 指定/current leaf 的 active branch,不混入 abandoned branch;
|
||||
- session id、cycle、entry 数、文件大小、UTF-8、symlink 与路径均有 fail-closed guard;
|
||||
- 恢复使用 `pi --session <path-or-id>`,并使用完整进程树的 timeout/cancel;
|
||||
- 删除只在用户显式确认后删除精确 session regular file;
|
||||
- root 优先级必须明确处理 `PI_CODING_AGENT_SESSION_DIR` 与 `settings.json.sessionDir`;
|
||||
- relative `sessionDir` 无法从全局 UI 确定启动 cwd 时返回“不可枚举/project-relative”状态,不得回退扫描默认 root。
|
||||
|
||||
## 10. Device settings 与 gateway token
|
||||
|
||||
### 10.1 Settings 文件
|
||||
|
||||
CC Switch device settings 必须使用同目录临时文件:
|
||||
|
||||
1. 创建/校验 regular file;
|
||||
2. Unix 权限收紧为 `0600`;
|
||||
3. 写入完整内容;
|
||||
4. flush/fsync;
|
||||
5. atomic rename;
|
||||
6. 必要时同步父目录。
|
||||
|
||||
不得使用 `.truncate(true)` 直接写 authority 文件。
|
||||
|
||||
### 10.2 Token 生命周期
|
||||
|
||||
- gateway token 存于 device settings 文件;
|
||||
- 首次生成后跨进程重启稳定;
|
||||
- 只有显式“重置网关凭据”操作旋转;
|
||||
- reset UI 必须提示运行中的 Pi 会话需要重启;
|
||||
- `GatewayToken` 的 Debug/Display 脱敏;
|
||||
- bearer 比较采用常量时间;
|
||||
- frontend settings DTO 清空 token;
|
||||
- save merge 无条件保留 existing token;
|
||||
- TS settings 类型不得包含 token。
|
||||
|
||||
### 10.3 Legacy 与 export
|
||||
|
||||
- SQLite `settings.pi_gateway_token` 仅可在 live DB 启动迁移时读取;
|
||||
- device 文件已有 token 时 device 胜出,并删除 DB key;
|
||||
- staged import/portable restore 直接删除远端 key,不得 adopt;
|
||||
- `export_sql_string()` 与 `export_sql_string_for_sync()` 分别断言产物不含 token key和实际 value;
|
||||
- token migration 在文件 durable write 后、DB delete 前崩溃时必须幂等。
|
||||
|
||||
## 11. Listener、runtime 与 takeover
|
||||
|
||||
Bind/reconcile 顺序:
|
||||
|
||||
```text
|
||||
bind exact loopback host/port
|
||||
-> 持久化首次动态端口
|
||||
-> 读取稳定 token
|
||||
-> close Pi admission
|
||||
-> compare exact owned projection
|
||||
-> mismatch 时以同 token atomic reconcile
|
||||
-> publish immutable runtime(origin + token + catalog)
|
||||
-> activate listener generation
|
||||
```
|
||||
|
||||
- 重启必须恢复同 host/port/token;
|
||||
- 已占用的固定旧端口必须显式失败,不得暗换端口;
|
||||
- bind、settings durable write 或 projection reconcile 失败时 token 不旋转、Pi admission 不打开;
|
||||
- listener 可以继续服务其他 app;
|
||||
- desired takeover 保留并显示 degraded/pending;
|
||||
- 停止 takeover 先关闭 admission,再恢复 direct projection;
|
||||
- database restore、proxy start/stop、端口更新与 catalog mutation 使用同一锁序。
|
||||
|
||||
每个请求取得 immutable runtime lease,包含 origin、token、catalog、server generation 与 even catalog epoch。旧 generation 不得 admission;旧 epoch 不得 writeback。
|
||||
|
||||
## 12. Usage semantics
|
||||
|
||||
### 12.1 类型与 authority
|
||||
|
||||
```text
|
||||
InputTokenSemantics
|
||||
1 TotalIncludesCacheBuckets
|
||||
2 FreshExcludesCache
|
||||
|
||||
StoredInputTokenSemantics
|
||||
Live(1|2)
|
||||
3 LegacyTotalIncludesCacheRead
|
||||
4 UnknownLegacy
|
||||
```
|
||||
|
||||
- 新请求由 wire adapter/parser 在 request context 创建时提供 live semantics;
|
||||
- 落库 authority 是 `proxy_request_logs.input_token_semantics`;
|
||||
- logical `app_type` 不参与语义选择;
|
||||
- request-id collision equality 与 dedup hash 包含 semantics;
|
||||
- spawn 后移动到 logger 的值来自 admission lease,不受后续 catalog 变化影响;
|
||||
- live writer 缺 semantics 必须拒绝落库。
|
||||
|
||||
### 12.2 Fresh input
|
||||
|
||||
- Anthropic family:`input_tokens` 已是 fresh,使用 `FreshExcludesCache`;
|
||||
- OpenAI Completions、OpenAI Responses、Google family:input total 包含 cached bucket,fresh 为 total 减 cached,使用 `TotalIncludesCacheBuckets`。
|
||||
|
||||
费用、明细、dedup、backfill、rollup 与前端 fresh 展示必须消费同一 stored semantics。
|
||||
|
||||
### 12.3 Migration
|
||||
|
||||
schema migration 是唯一允许从旧 `app_type` 推断的路径:
|
||||
|
||||
- 旧值 `0` 且 app 为 codex/gemini/grokbuild -> `3`;
|
||||
- 其他已发布 app -> `2`;
|
||||
- 意外旧 Pi row -> `4`,不得猜 family或自动回填费用;
|
||||
- rollup 旧 `0` -> `2`;
|
||||
- migration 后所有相关表不得残留 `0`;
|
||||
- typed DAO、runtime decoder 与 import 拒绝 `0`;
|
||||
- 所有 session import raw SQL 显式写 semantics。
|
||||
|
||||
前端 `KNOWN_APP_TYPES` 包含 `pi`,但该列表只用于筛选。
|
||||
|
||||
## 13. Routing、candidate materialization 与 failover
|
||||
|
||||
### 13.1 Eligibility
|
||||
|
||||
```text
|
||||
eligible(primary, candidate, route) :=
|
||||
candidate exposes route.model_id
|
||||
AND effective_api_family(primary) == route.api_family
|
||||
AND effective_api_family(candidate) == route.api_family
|
||||
AND canonical_request_wire_profile(primary)
|
||||
== canonical_request_wire_profile(candidate)
|
||||
AND canonical_protocol_header_profile(primary)
|
||||
== canonical_protocol_header_profile(candidate)
|
||||
```
|
||||
|
||||
不进入 equality:
|
||||
|
||||
- scheme、host、port、base path;
|
||||
- provider/model `baseUrl`;
|
||||
- API key 与认证 header;
|
||||
- tenant 与普通自定义 headers;
|
||||
- display、pricing、排序、默认值。
|
||||
|
||||
进入 wire equality:
|
||||
|
||||
- 已知 request shaping:`compat`、`reasoning`、`thinkingLevelMap`、`input`、`contextWindow`、`maxTokens` 等;
|
||||
- 除明确 transport/auth/display denylist 之外的未知字段;
|
||||
- 已知 protocol header 的最终有效值。
|
||||
|
||||
JSON object key 顺序不敏感;array 顺序敏感;数字与字符串不宽松等价。
|
||||
|
||||
### 13.2 Header 分类
|
||||
|
||||
| 类别 | 行为 |
|
||||
|---|---|
|
||||
| Gateway/HTTP owned:Host、framing、hop-by-hop、proxy trace、gateway bearer | 剥离或重建,不进 equality |
|
||||
| Protocol:`anthropic-version`、`anthropic-beta`、`openai-beta`、`openai-version` | 最终有效值进 equality |
|
||||
| Candidate auth:Authorization、`x-api-key`、`x-goog-api-key` | 每次 attempt 延迟解析 |
|
||||
| Candidate tenant:organization/project headers | 每次 attempt 解析 |
|
||||
| Provider custom headers | 每次 attempt 解析 |
|
||||
| Incoming request-local | 统一过滤后复制到每个候选,不进 equality |
|
||||
|
||||
未知配置 header 视为 candidate-local。已知 protocol header 使用不可预测 `!command` 时,该候选 failover-ineligible。protected 与 hop-by-hop header 不得由配置覆盖。
|
||||
|
||||
### 13.3 Candidate materialization
|
||||
|
||||
- 每个候选先经 `effective_pi_model()`;
|
||||
- 复用对应 family adapter 的 URL builder;
|
||||
- 最终 `Host` 从该候选最终 URL 重建;
|
||||
- auth、tenant、custom header 均来自当前候选,不得从 primary 串值;
|
||||
- 同一序列化请求 body 字节等价地 clone 给每个候选;
|
||||
- 不得做候选级 body/model 改写;
|
||||
- command-valued secret 需要 process-group/job-object tree kill、整体 timeout、有界 stdout/stderr,且日志不得包含 resolved secret。
|
||||
|
||||
### 13.4 Retry 与 SSE fence
|
||||
|
||||
允许切换:网络错误、timeout、401、403、404、408、409、429、非结构性其他 4xx 与 5xx。
|
||||
|
||||
禁止切换:400、405、406、413、414、415、422、501。
|
||||
|
||||
SSE 只有在首个语义事件提交给 Pi 前可切换;comment/keep-alive 不算语义提交。语义事件后失败不得 replay。
|
||||
|
||||
Failover 只影响当前请求,不改全局 current Provider、默认值、live config 或 UI current state。
|
||||
|
||||
### 13.5 必须存在的矩阵测试
|
||||
|
||||
1. `pi_route_identity_matrix`
|
||||
2. `pi_cross_origin_materialization_matrix`
|
||||
3. `pi_wire_profile_canonical_matrix`
|
||||
4. `pi_protocol_header_profile_matrix`
|
||||
5. `pi_four_family_candidate_materialization`
|
||||
6. `pi_failover_error_policy_matrix`
|
||||
7. `pi_sse_commit_boundary_matrix`
|
||||
8. `pi_snapshot_epoch_race`
|
||||
|
||||
## 14. Shared-file 与 restore 安全
|
||||
|
||||
- JSONC/CST patch 保留 comments、unknown fields 与 unowned entries;
|
||||
- 只修改 manifest-owned exact keys;
|
||||
- 文件写入均为同目录 temp + durable atomic replace;
|
||||
- path traversal、symlink、目录、设备文件和超限内容 fail closed;
|
||||
- 删除前后重新执行 `symlink_metadata`/identity check;
|
||||
- portable export/sync 不包含 projection ledger、skill ledger、gateway token、SYSTEM/APPEND;
|
||||
- restore 完成后在 Pi switch boundary reconcile DB、projection、runtime 与 listener;
|
||||
- 任何 reconcile 失败都必须返回明确 authority/admission 状态。
|
||||
|
||||
### 14.1 Canonical schema authority
|
||||
|
||||
`src-tauri/src/database/schema.rs` 的 current-schema factory、current DDL 与
|
||||
migration chain 是完整 canonical schema authority。`CanonicalTableSpec` /
|
||||
`SchemaInvariantSpec` 机械描述 Provider/endpoint/device-local ledger 的承重
|
||||
invariant;`tests/fixtures/pi/canonical-schema-manifest-v1.json` 是由这些代码 spec
|
||||
产生并由测试锁定的审查快照。其余 current user table 由下述穷举
|
||||
`RestoreTableSpec` 锁定恢复策略与固定列。Fixture 不是运行时 authority,也不能
|
||||
认证 imported schema;外部 schema 无论通过多少结构比较都不得获得 publish 资格。
|
||||
|
||||
类型屏障固定为:
|
||||
|
||||
```rust
|
||||
struct UntrustedScratch(Connection);
|
||||
struct CanonicalStage(Connection); // 私有字段,仅 schema.rs current factory 构造
|
||||
```
|
||||
|
||||
Publish 只消费 `CanonicalStage`;不得接收裸 `Connection`,也不得存在
|
||||
`UntrustedScratch` 到 publish 参数类型的转换。Canonical stage 是全新磁盘临时库,
|
||||
由 `schema.rs` 从空库建立当前全部 tables/indexes/collations、PK conflict policy、
|
||||
CHECK、FK action、canonical trigger/view、PRAGMA 与 `user_version`,不得复制 source
|
||||
`sqlite_schema` 中的任何对象。Restore 生产代码不得包含
|
||||
`CREATE TABLE/INDEX/TRIGGER/VIEW` 字符串。
|
||||
|
||||
`RestoreTableSpec` 必须对每张 current user table 恰好指定一种策略:
|
||||
|
||||
- `PortableIncoming`:从 input 固定列逐行 decode 与复制;
|
||||
- `PreserveLive`:设备本地 rows 只从 live DB 复制;
|
||||
- `RebuildRuntime`:运行态不从 input 恢复;
|
||||
- `SeedCanonical`:由 current schema/seeding authority 重建。
|
||||
|
||||
结构测试必须证明 canonical user-table 集合与 policy 集合完全相等;新增表没有策略
|
||||
必须失败。每个 spec 固定列名、复制顺序、NULL/storage type、row validator 与父子
|
||||
拓扑;不得读取 source `PRAGMA table_info` 决定搬运列。
|
||||
|
||||
### 14.2 SQL 与 binary 共用 publish 管线
|
||||
|
||||
两入口统一执行:
|
||||
|
||||
```text
|
||||
untrusted input
|
||||
→ UntrustedScratch
|
||||
→ scratch 中 current migrations
|
||||
→ RestoreTableSpec 固定列逐行 decode
|
||||
→ schema.rs 从空库创建 CanonicalStage
|
||||
→ live device-local copy
|
||||
→ canonical validation
|
||||
→ SQLite Backup API publish
|
||||
```
|
||||
|
||||
- Data transfer 在 `foreign_keys=ON` 的单个 canonical-stage transaction 中使用
|
||||
plain INSERT;禁止 `SELECT *`、`INSERT OR IGNORE`、`INSERT OR REPLACE`、
|
||||
`REPLACE`、目标端 `ON CONFLICT DO UPDATE` 与复制 `sqlite_sequence`;
|
||||
- duplicate canonical key、弱 source UNIQUE 产生的重复行、FK orphan、storage
|
||||
class 或 decoder 错误必须整体 abort;
|
||||
- Provider 行写 stage 前使用生产 hydration 的同一 decoder 验证
|
||||
`settings_config`、`meta`、数字与 NULL;未知 JSON 字段必须保留,不得通过
|
||||
重序列化丢失;
|
||||
- `pi_provider_projections`、`skill_deployments` 只从 live DB 固定列复制,永不从
|
||||
source;复制与 publish 持既定 live/switch boundary,失败保持 live 逐行不变;
|
||||
- AUTOINCREMENT 表可复制显式 ID,但不复制 `sqlite_sequence`,并验证下一 INSERT
|
||||
的 ID 大于现有最大值;
|
||||
- WAL/SHM 永不复制、替换或重命名;binary source 只经 SQLite Backup API 克隆到
|
||||
private scratch,publish 同样只用 Backup API。
|
||||
|
||||
Scratch 必须在不可信执行前关闭 trigger、关闭 `trusted_schema`、开启 defensive、
|
||||
设 attached DB limit 为 0,并设置 SQL/value/page-count limits、有限 VM-step
|
||||
budget 与取消检查。不注册应用函数、不启用 load-extension;authorizer 至少拒绝
|
||||
ATTACH/DETACH、vtable、load-extension、writable-schema、非白名单 PRAGMA 与
|
||||
unknown action。外部 batch 后删除 source trigger/view/显式 index,再执行可信
|
||||
migration。未来 `user_version` 立即拒绝;`0..=SCHEMA_VERSION` 全部走现有 migration
|
||||
chain,缺固定列或 row decode 失败即 abort。
|
||||
|
||||
SQL 文件入口使用 symlink metadata、regular file、nofollow open、opened/current
|
||||
identity 与有限读取;binary 使用 `READ_ONLY | NOFOLLOW | PRIVATE_CACHE` 与同样
|
||||
检查。边界为 `MAX_SQL_IMPORT_BYTES=256MiB`、
|
||||
`MAX_BINARY_RESTORE_BYTES=2GiB`、`MAX_SCRATCH_BYTES=2GiB`。
|
||||
|
||||
Publish 前必须执行 `integrity_check`、`foreign_key_check`、全表 decoder sweep 与
|
||||
行为断言,至少证明 providers PK collation 为 BINARY、精确 duplicate 为 ABORT、
|
||||
duplicate 不会 REPLACE/级联删 endpoint。
|
||||
|
||||
SQL `import_sql_string*` 与 binary `restore_from_backup` 两个公开入口必须参数化覆盖:
|
||||
|
||||
1. NOCASE/REPLACE PK、弱 endpoint UNIQUE/FK、trigger/view/index injection 后只发布
|
||||
canonical schema;
|
||||
2. 不可解码 Provider JSON、错误 storage class、duplicate canonical key、FK orphan
|
||||
abort 且 live/device-local rows 不变;
|
||||
3. NULL `added_at` 等 nullable 值与显式 AUTOINCREMENT ID 无损往返;
|
||||
4. symlink、目录、FIFO、N+1、VM/page budget、ATTACH/VACUUM INTO 在 publish 前
|
||||
失败;
|
||||
5. 每个 `user_version=0..SCHEMA_VERSION` 有 migration sentinel,current、上一版与
|
||||
最老支持版同时覆盖 SQL 与 binary。
|
||||
|
||||
### 14.3 Native oracle provenance
|
||||
|
||||
盲审与 CI 消费以下 vendored artifacts,不运行 Node、不联网:
|
||||
|
||||
- `tests/fixtures/pi/native-oracle/provider-schema.snapshot.json`
|
||||
- `tests/fixtures/pi/native-oracle/raw-oracle-v1.json`
|
||||
- `tests/fixtures/pi/native-oracle/composer-oracle-v1.json`
|
||||
- `tests/fixtures/pi/native-oracle/transport-oracle-v1.json`
|
||||
- `tests/fixtures/pi/native-oracle/field-coverage-v1.json`
|
||||
- `tests/fixtures/pi/native-oracle/provenance-v1.json`
|
||||
|
||||
provenance 必须记录 Pi repo/commit、TypeBox version、schema/composer 源文件路径与
|
||||
SHA-256、实际调用的 composer/transport entry functions、全部 artifact SHA-256、
|
||||
generator 与 evaluator operator allowlist。Composer/transport expected output 只能
|
||||
来自 implementation pin 的实际执行,不能由本仓库 mirror 生成。
|
||||
|
||||
`field-coverage-v1.json` 必须枚举 schema 的每个 canonical field path。每个字段都
|
||||
必须同时引用:
|
||||
|
||||
1. 输入确实含该字段且 TypeBox 1.3.7 实际执行成功的 raw case;
|
||||
2. 输入确实含该字段、在其所属 provider/model/override 层实际执行成功且有 expected
|
||||
output 的 pinned Pi composer case。
|
||||
|
||||
缺字段、重复/陈旧字段、虚构 case、只在错误层出现或无法由 Pi 实际执行时 bundle
|
||||
认证失败;无法执行的语义在 Rust 中 fail-closed 为 unknown。任一 hash/pin 不一致
|
||||
时 raw evaluator 返回 unknown。更新 pin 必须显式重生成并审查。
|
||||
|
||||
## 15. UI、IPC、fixtures 与验收
|
||||
|
||||
### 15.1 UI/IPC
|
||||
|
||||
- Pi Provider 表单支持 provider/model 两级 `api` 与 `baseUrl`;
|
||||
- native catalog 使用独立只读面板,不使用 managed ProviderCard 操作;
|
||||
- 只有 importable entry 显示导入操作;
|
||||
- direct-only 在 managed 卡片上显式显示且无 failover 操作;
|
||||
- 多模型 Provider 不显示必然失败的无 model-id 通用 Enable;
|
||||
- Skill UI 独立展示 desired、owned、discovered/shadow;
|
||||
- Prompt 顶层名称为 `Instruction Files | Prompt Templates`,不得将 AGENTS.md 称为 system prompt;
|
||||
- SYSTEM 页面有替换默认 prompt 的危险警告;APPEND 页面有追加说明;
|
||||
- Sessions 显示不可枚举的 relative root 状态;
|
||||
- 所有异步入口有 loading、error、empty 与 disabled 状态;
|
||||
- en、zh、zh-TW、ja key parity,且不得依赖 fallback。
|
||||
|
||||
### 15.2 控制面 fixture
|
||||
|
||||
`tests/fixtures/pi/control-contract-v1.json` 必须稳定覆盖:
|
||||
|
||||
- 四 API family;
|
||||
- provider-level 与 model-level api/baseUrl 继承;
|
||||
- provider 默认为空、models 自包含;
|
||||
- built-in overlay、modelOverrides-only、custom、extension overlay、malformed;
|
||||
- raw validity / managed assessment / composition / management / gateway 分层分类与
|
||||
`{layer,code,json_pointer?}` reasons;
|
||||
- aggregate 主行 + 多 endpoint;
|
||||
- projection claim;
|
||||
- Skill desired/owned/discovered 的组合及 stale/collision/drift;
|
||||
- prompt file kinds、路径与 revision;
|
||||
- session tree、active branch 与 relative-root diagnostic;
|
||||
- 四语 UI/IPC 枚举与字段名。
|
||||
|
||||
fixture 只含无凭据的确定性示例。Commit 10 后其语义与字段冻结。
|
||||
|
||||
检查点 A 还必须锁定:
|
||||
|
||||
- canonical schema manifest 与 `schema.rs` code spec 等价;
|
||||
- raw oracle 的 valid/invalid 结果与 vendored TypeBox 1.3.7 期望逐项一致;
|
||||
- composer oracle 的默认值、provider/model/override headers/compat precedence、
|
||||
unmatched override、空 URL、built-in/extension unknown 结果逐项一致;
|
||||
- schema 每个 canonical field 均有实际成功的 TypeBox 与所属层 pinned Pi composer
|
||||
执行证据;transport resolver 的 deferred value/header 结果逐项来自 pinned Pi
|
||||
实际执行;
|
||||
- raw unknown/invalid inspection 对文件、DB、claim、projection、default 与 gateway
|
||||
均零副作用。
|
||||
|
||||
### 15.3 故障 fixture
|
||||
|
||||
至少覆盖:
|
||||
|
||||
- cache-heavy 四 family:`1000 input / 800 cached`;
|
||||
- native import fingerprint TOCTOU;
|
||||
- exact claim collision;
|
||||
- aggregate delete + projection failure;
|
||||
- DB rollback failure;
|
||||
- Skill 三种 unowned collision;
|
||||
- deploy 后 ledger 前 crash;
|
||||
- owned copy drift 与 symlink retarget;
|
||||
- external prompt revision conflict 与 symlink;
|
||||
- relative session root;
|
||||
- stable token restart、migration crash 与 unwritable projection;
|
||||
- concurrent mutation/restore/request;
|
||||
- cross-origin URL/header/auth materialization;
|
||||
- SSE pre/post semantic-commit failure。
|
||||
|
||||
### 15.4 验证命令
|
||||
|
||||
```bash
|
||||
cd src-tauri
|
||||
cargo fmt --all -- --check
|
||||
cargo clippy --lib -- -D warnings
|
||||
cargo test --lib
|
||||
|
||||
cd ..
|
||||
pnpm typecheck
|
||||
pnpm test:unit -- --run
|
||||
pnpm format:check
|
||||
pnpm build:renderer
|
||||
git diff --check
|
||||
```
|
||||
|
||||
renderer build 后必须删除 `dist/` 与 `dist-web/`。仓库不得提交生成物。
|
||||
|
||||
## 16. Extensions 设计边界(仅设计)
|
||||
|
||||
未来只有在固定官方源码确认下列原生能力后,才可另行实现:
|
||||
|
||||
- 不执行 extension 代码的 machine-readable inventory;
|
||||
- 原生持久 enable/disable 位或官方 passthrough;
|
||||
- scope 明确、结果可判定的官方 install/remove CLI;
|
||||
- 一个固定 owned key 的窄集成。
|
||||
|
||||
任何未来实现都必须以 Pi 原生配置/CLI 为 authority,不建 DB enabled mirror;不得自建 registry、依赖解析、semver、lockfile、下载器、trust 流程或 project-local 管理;不得执行 extension 取 metadata;不得复制凭据;不得自动 claim provider overlay或 Skill;不得把 extension MCP 表示为 Pi 原生 MCP。
|
||||
|
||||
本次实现不得包含以上能力的代码或 UI。
|
||||
|
||||
## 17. Commit 与盲审边界
|
||||
|
||||
实现必须保持 16 个独立 green commits:
|
||||
|
||||
1. `docs(pi): freeze support contracts and extension design`
|
||||
2. `domain(pi): add managed model and capability contracts`
|
||||
3. `db(pi): add aggregate hydration and device-local ledgers`
|
||||
4. `config(pi): add read-only native catalog inspection`
|
||||
5. `provider(pi): add catalog mutation coordinator`
|
||||
6. `skill(pi): add ownership-safe deployment reconciliation`
|
||||
7. `ui(pi): expose provider native and skill control plane`
|
||||
8. `prompt(pi): support native instruction files and templates`
|
||||
9. `session(pi): add tree-aware session management`
|
||||
10. `test(pi): close and freeze control-plane contracts`
|
||||
11. `settings(pi): harden local settings and persist gateway credential`
|
||||
12. `gateway(pi): add listener admission and immutable runtime`
|
||||
13. `usage(pi): make input token semantics explicit`
|
||||
14. `routing(pi): materialize four native API families`
|
||||
15. `failover(pi): add cross-host failover and SSE commit fence`
|
||||
16. `test(pi): close recovery concurrency and end-to-end boundaries`
|
||||
|
||||
累积检查点:
|
||||
|
||||
- A:base..commit 4;
|
||||
- B:base..commit 10;
|
||||
- C:base..commit 16。
|
||||
|
||||
B 必须零 validated blocker/high 才可进入数据面。B 通过后记录 `CONTROL_FREEZE_SHA=commit 10`,commit 11–16 每次必须通过:
|
||||
|
||||
```bash
|
||||
git diff --exit-code "$CONTROL_FREEZE_SHA"..HEAD -- \
|
||||
src-tauri/src/pi_config/model.rs \
|
||||
src/components/providers/forms/PiProviderForm.tsx \
|
||||
tests/fixtures/pi/control-contract-v1.json
|
||||
```
|
||||
|
||||
`schema.rs` 由 schema snapshot/contract tests 守护 managed aggregate 与 ledger 形态。数据面若需要改变冻结文件、Provider IPC/form 字段、managed/native/direct-only 分类或 aggregate/ledger schema,必须停止并使 B 失效,不得在数据面提交顺手修改。
|
||||
|
||||
每个检查点的 reviewer 必须检查完整累积 range,并按 correctness、regression、data integrity、concurrency、security、compatibility、UI state、i18n 与 tests 维度报告。每条 finding 必须包含 severity、confidence、精确文件与行、具体故障场景、推理与建议方向;无 finding 时明确写出。
|
||||
|
||||
检查点 A 的 reviewer 材料必须包含:精确 base/checkpoint SHA、中立仓库地图与
|
||||
authority 表、本文件、canonical schema manifest、native provenance manifest、
|
||||
schema snapshot、带期望结果的 raw/composer/transport oracle fixtures、field
|
||||
coverage、provider-write/restore-policy/module-boundary 机器快照、完整验证命令与
|
||||
通用审查维度。不得附带实现摘要、设计辩护、疑似文件/行号、既有审查输出或定向检查
|
||||
提示。
|
||||
|
||||
审查总预算最多七轮。同一 invariant 连续两轮失败时停止局部修补并回到契约/状态模型;第七轮后仍有 blocker/high/data-integrity finding 时停止,不进行第八轮。
|
||||
Reference in New Issue
Block a user