# 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 -> /AGENTS.md SystemOverride -> /SYSTEM.md SystemAppend -> /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 只位于 `/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 `,并使用完整进程树的 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 时停止,不进行第八轮。