Files
CC-Switch/docs/pi-support-review-contract-zh.md
T

943 lines
43 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 pinschema、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 narrowingmodel 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 catalograw valid + composed + importable +
direct-only(`api_family_not_gateway_supported`)
- built-in/extension 因缺 catalog/default-model contextraw 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 使用精确行 mutationtouch 影响零行返回 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 为 authorityPi 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 familyinput total 包含 cached bucketfresh 为 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 ownedHost、framing、hop-by-hop、proxy trace、gateway bearer | 剥离或重建,不进 equality |
| Protocol`anthropic-version``anthropic-beta``openai-beta``openai-version` | 最终有效值进 equality |
| Candidate authAuthorization、`x-api-key``x-goog-api-key` | 每次 attempt 延迟解析 |
| Candidate tenantorganization/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 scratchpublish 同样只用 Backup API。
Scratch 必须在不可信执行前关闭 trigger、关闭 `trusted_schema`、开启 defensive、
设 attached DB limit 为 0,并设置 SQL/value/page-count limits、有限 VM-step
budget 与取消检查。不注册应用函数、不启用 load-extensionauthorizer 至少拒绝
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 sentinelcurrent、上一版与
最老支持版同时覆盖 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`
累积检查点:
- Abase..commit 4
- Bbase..commit 10
- Cbase..commit 16。
B 必须零 validated blocker/high 才可进入数据面。B 通过后记录 `CONTROL_FREEZE_SHA=commit 10`commit 1116 每次必须通过:
```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 时停止,不进行第八轮。