From 56acfd266ed693b0d3ace2e3dece4cf1aae2ba88 Mon Sep 17 00:00:00 2001 From: SaladDay Date: Mon, 3 Aug 2026 07:56:09 +0000 Subject: [PATCH] docs(pi): drop process-only handoff documents The handoff, contracts, restructure ruling, reviewer spec and main-project contract were scaffolding for the implementation process, not deliverables. Their technical invariants now live in the code and the certification suites. Kept: the extensions design appendix (future work) and the restore hardening debt register (known issues in shipped code). Certification suite comments that pointed at the removed restructure ruling are made self-contained. --- docs/pi-main-project-zh.md | 146 --- docs/pi-support-contracts-zh.md | 822 ------------- docs/pi-support-handoff-zh.md | 1093 ----------------- docs/pi-support-restructure-zh.md | 111 -- docs/pi-support-review-contract-zh.md | 942 -------------- .../database/backup_restore_certification.rs | 2 +- .../dao/provider_write_certification.rs | 4 +- .../native_inspection_certification.rs | 2 +- 8 files changed, 4 insertions(+), 3118 deletions(-) delete mode 100644 docs/pi-main-project-zh.md delete mode 100644 docs/pi-support-contracts-zh.md delete mode 100644 docs/pi-support-handoff-zh.md delete mode 100644 docs/pi-support-restructure-zh.md delete mode 100644 docs/pi-support-review-contract-zh.md diff --git a/docs/pi-main-project-zh.md b/docs/pi-main-project-zh.md deleted file mode 100644 index 378e4cac0..000000000 --- a/docs/pi-main-project-zh.md +++ /dev/null @@ -1,146 +0,0 @@ -# Pi 主工程契约(纲领) - -> 适用规则:`docs/pi-support-restructure-zh.md` §4.7(契约纲领化、协议放宽、增量审查)。 -> 前置 A/B/C 已认证;本文是本工程**唯一**契约,不再有逐条裁决。 -> 技术不变量以 `docs/pi-support-contracts-zh.md` 的五份契约为准(未变)。 - -## 目标 - -把 Pi 做成 cc-switch 的**一等公民 app**,与 Claude/Codex/OpenCode/Hermes 同级: -用户能在 Pi 这个 app 下管理供应商、模型、Skills、Prompts、Sessions、MCP, -能用网关代理它,能看到原生 `models.json` 的真实状态而不被我们的抽象骗。 - -## 验收标准 - -1. **同级**:`AppType::Pi` 在既有 app 的每一条通路上都存在且行为一致—— - 供应商与端点、切换与故障转移、Skills/Prompts/Sessions、MCP、深链、设置、 - 目录配置、可见性、使用统计、导入导出。以 **Hermes 为最近先例**逐条对照; - 凡先例有而 Pi 没有的通路,要么实现,要么在本文件写明为何不适用。 -2. **原生真实**:Pi 的原生资产(`models.json`、AGENTS.md / SYSTEM.md / - APPEND_SYSTEM.md、prompts、sessions)以 pinned Pi 的语义读写; - `exists = active`,不造影子状态;只读检查面已由前置 C 认证,主工程复用它, - 不得另起一套判定。 -3. **网关数据面**:Pi 供应商可经网关代理;候选物化、失败转移、协议身份沿用 - 前置 C 认证的判定结果。**OAuth 凭证(`sk-ant-oat` 子串)在本工程实现**—— - Anthropic 族按实测发 `Authorization: Bearer` + oauth beta 头、不发 x-api-key; - 完成前维持前置 C 的诚实降级(DirectOnly),不得半实现。 -4. **可用**:UI/i18n 中英文齐全;新用户能从零添加一个 Pi 供应商并切换成功。 -5. **不回退**:测试总数只增不减;既有 app 的行为不受影响。 - -## 边界 - -- 不碰:pinned 夹具、已重冻的 infra 三文件(`schema.rs`/`migration.rs`/`backup.rs`)、 - restore 面(见 `docs/restore-hardening-debt-zh.md`)、三份认证套件、 - `stash@{0}`、PR #5598; -- extensions 只做设计附录,themes 不做(用户已裁定); -- 不 push、不建 PR(终态交付仍是**一个** PR,由用户决定何时发)。 - -## 证据规则 - -凡断言"pinned Pi 如此行为",须有 oracle 或 `scripts/pi-transport-capture.mjs` -的实证,不接受读源码推断——这是四轮返工换来的唯一保留仪式。 - -## 自由度 - -范围划分、commit 粒度、DTO 与模块组织、测试组织、验证与审查节奏,全部自定。 -不必对齐红绿账本,不必按固定轮次做盲审,偏离不必硬停上报。 -遇到本文与既有契约冲突、或需要用户拍板的产品取舍(功能取舍、范围增删), -才上报;其余自行裁量,完成后报终态 SHA。 - -## 实现 authority 与可复现实证 - -- 唯一 Pi implementation pin: - `ab366ebe94cacd419d986be454f12b1b9913aaca`。主工程没有沿用历史文档中的 - 旧 pin,也没有从远端观察值推导行为。 -- `scripts/generate-pi-native-oracle.mjs` 负责 schema/composer 的冻结 oracle; - `scripts/pi-transport-capture.mjs` 负责需要真实执行 Pi 才能确认的 transport、 - compat、原生资源与 CLI 行为。两者都先校验上述 commit,pin 不符即拒绝运行。 -- 主工程捕获命令: - `PI_CHECKOUT= node scripts/pi-transport-capture.mjs`。 - 捕获直接执行 Pi,而非阅读上游源码推断。 -- 捕获的发行元数据来自 `packages/coding-agent/package.json`,该文件在 pin 上的 - SHA-256 为 - `e02deae1cec07035807436c1864c88342e2f7d49050d03b858a3719f0c7aedbf`: - 包名 `@earendil-works/pi-coding-agent`、可执行文件 `pi`、配置目录 `.pi`; - `parseArgs(["--version"])` 与带空格绝对路径的 - `parseArgs(["--session", path])` 均由捕获实际执行。 -- 同一捕获实际发出了四族请求并记录最终 URL/headers。Anthropic OAuth 凭证 - (`sk-ant-oat` 子串)最终为 `Authorization: Bearer `, - `anthropic-beta` 含 `claude-code-20250219,oauth-2025-04-20`,且主工程策略 - 强制不并发 `x-api-key`。OpenAI Responses/Completions、Google 与普通 - Anthropic key 的头部矩阵也在同一输出中。 - -## Hermes 先例逐项对照账本 - -| 通路 | Pi 终态 | Hermes 差异或不适用理由 | -|---|---|---| -| App 注册、切换器、图标、可见性 | `AppType::Pi` 贯穿 Rust/IPC/TS,旧设置缺字段时默认可见,可独立隐藏 | 无差异 | -| 供应商与模型 | 独立 Pi 表单无损保留 provider/model 的未知 JSON;支持 provider/model 级 `api`、`baseUrl`、headers、`authHeader`、credential、cost/compat 等 pinned 字段;原生 catalog 使用已认证 inspection 导入 | Hermes 的 Web UI overlay/只读 provider 是 Hermes 自身所有权模型,Pi 使用共享 `models.json`,不套用 overlay | -| 新用户首用 | 创建第一位 Pi provider 时,在数据库、完整初始 endpoints 与原生 catalog 同一协调边界内发布,并把其第一模型设为 native default;随后可直接切换/代理 | 无需先手工导入或另开 native 文件 | -| 原生 catalog 与当前状态 | 复用前置 C 的三层 inspection;展示 raw/managed/composer/gateway 状态;可用 fingerprint CAS 导入;当前 provider/model 直接读 native defaults | 不以数据库 current 字段制造第二份“当前”状态 | -| 端点 | provider 主端点与多个 typed custom endpoints 均可增删、测速;membership 与 provider hydration 一致;网关按主端点及有序候选展开 | Hermes 的 additive provider 不具备该网关数据面 | -| 切换、代理与故障转移 | Pi 有独立 loopback route/token、catalog epoch、健康状态、重试/熔断和 failover 队列;候选必须保持 wire family 与协议身份兼容;不可预测协议表达式只允许单次 direct attempt,不重放 | Hermes 当前没有 cc-switch gateway handler;Pi 对照的是已有完整代理 app 的安全边界 | -| OAuth 数据面 | 完整实现 Anthropic OAuth 的 Bearer + oauth beta,强制删除 `x-api-key`;deferred credential 在物化后分类 | 由 request-capture 实证;不再停留在前置 C 的 DirectOnly | -| Skills | Pi 进入统一 Skills UI;期望状态、原生发现、受管 ownership 分离;显式 native import 只在内容完全相同时接管;部署、删除、导入及 portable sync 有锁、CAS、事务补偿 | Pi 的原生目录是部署目标,不能复用其他 app 的“复制后即视为拥有”假设 | -| Prompts | Prompt 库投影 `AGENTS.md`;同时原生管理 `SYSTEM.md`、`APPEND_SYSTEM.md` 与 `prompts/*.md`;文件存在即 active,直接编辑空白值会拒绝并要求显式删除 | Hermes 的 prompt 通路仍可复用;Pi 额外公开 pinned 原生资源,不制造 enabled 影子字段 | -| Sessions | 扫描 pinned v1–v3 JSONL tree、只显示 active branch,支持详情、删除、终端恢复;遵守环境/设置/default sessionDir;相对 sessionDir 明示需要项目 cwd,不伪装为空列表 | Pi 的 tree/branch 与 Hermes session 格式不同,使用独立 parser 但复用统一 Session UI/安全入口 | -| MCP | **不适用**:pinned Pi core 没有原生 MCP registry,UI 不展示虚假 MCP 状态,服务入口结构化拒绝;capture 的 core tool inventory 为 `bash/edit/find/grep/ls/read/write` | Hermes 有原生 MCP registry;Pi extensions 可贡献 tools,但不等同 MCP。未来方案见 `docs/pi-extensions-design-appendix-zh.md` | -| 深链 | provider 深链支持 Pi provider key、模型、api、端点及 credential,走真实 `ProviderService::add`;prompt 深链走统一库与 Pi 文件所有权链 | 未识别 Pi API 保持 opaque,不用名称猜协议 | -| 设置与目录 | 设置页可发现/选择 Pi 配置目录,支持 `PI_CODING_AGENT_DIR`、WSL 路径展示、安装检测、网关/故障转移参数与凭证轮换;本地 gateway token 不下发前端 | 配置目录和 token 是 device-local,不写进 portable DB | -| 使用统计 | 四族 response/SSE 使用量进入统一日志、价格、筛选与 dashboard;每请求携带 input-token semantics,不能用 `app_type` 猜;Pi 混合协议的 cache-write 总数标为“部分可得” | Hermes 无 gateway usage;Pi 的单一 app 桶可能混合四族,不能显示误导性的确定 0 | -| 导入导出与远端同步 | DB provider/prompt/skill 期望状态可 portable;恢复后通过 catalog/prompt/skill reconciliation 与本机原生状态对齐;设备目录、native defaults、token、session 不跨机复制 | 原生资产属于设备/外部事实,不能塞进 SQL/binary 备份制造影子副本 | -| Profile 切换器 | **不适用**:现有项目 Profile 仅定义 Claude 组与 Codex 组,Hermes 本身也不属于任何 Profile scope;Pi 页面同样不展示 | 若未来产品定义新的跨 app Profile scope,应单独设计原生文件事务,不能暗自归入 Claude/Codex | -| Hermes Memory/Web UI | **不适用**:这是 Hermes 独有 daemon/API;Pi 的长期上下文入口是 `AGENTS.md` 与原生 instruction files,已在 Prompts 面公开 | 不伪造不存在的 Pi daemon 或 Web UI | -| 原生“一键导入”按钮 | Pi 不调用 generic `import_default`;改为 certified native catalog 中逐 entry inspection + fingerprint CAS 导入 | 这是更严格的等价通路,不是功能缺失 | -| Extensions / Themes | extensions 本工程只交设计附录;themes 明确排除 | 用户已裁定范围 | - -## 原生状态与 portable 状态边界 - -1. `models.json` 的读取、composition 与 gateway capability 只走前置 C 已认证链。 - 写入统一经 Pi catalog coordinator,协调 DB aggregate、typed endpoints、 - native document、default provider/model、epoch 与失败补偿;legacy live-sync - 不得旁路写它。 -2. `AGENTS.md`、`SYSTEM.md`、`APPEND_SYSTEM.md`、prompt templates、Skills - 与 Sessions 直接观察真实文件;所有 UI active 判断均来自 `exists` 或实际发现。 - revision/fingerprint 只用于并发保护,不是第二套 enabled 状态。 -3. portable import 恢复的是 cc-switch 拥有的 provider、prompt 与 Skill 期望状态, - 随后与本机 native 事实 reconcile;不会跨设备覆盖目录、凭证 token、native - defaults 或会话。 -4. Pi provider card 的“切换”选择该 provider 的第一模型;多模型的明确选择在 - native catalog panel 完成,并写回同一 native default,不维护每 provider 的 - 隐藏 last-model 状态。 - -## 完成审查约束 - -- 主工程盲审最多七轮;每轮冻结精确 SHA/range,两位 fresh、互相独立、只读 - reviewer。已审 clean SHA 作为下一轮增量基线;契约新增条款的作用域另做定向 - 全域调用点扫描。 -- P0/P1 finding 必须修复;P2 由主审按可达性、影响面、数据/安全后果与发生概率 - 独立裁决,只有低影响且极少触发的边界才可保留,并在最终报告列出理由。 -- 完成前做一次完整组件通读;零 validated P0/P1、相关验证全绿后才可交付。 - -## Scope audit(主工程预审检查点) - -以主工程基线 `26a95aeb059235612c665d6b6bcd92fde9c3572e` 计,终态候选为 -141 个文件、约 +16.9k 净行,触发历史 90 文件/12k 净行审计线。审计结论为 -**范围大但未越界**: - -- Rust 70 文件:Pi catalog/typed endpoints、共享文件与 ownership、gateway/runtime/ - usage、prompts/skills/sessions,以及把 `AppType::Pi` 接进现有 command/service/ - startup/sync 通路;其中新增的大文件按 catalog、transport、runtime、session、 - deployment 分开,避免一个全知模块。 -- 前端与构建配置 55 文件:3 个 Pi 专用 panel/form、typed IPC/query、既有导航/ - 设置/代理/使用统计接线、四语 i18n 与 Windows 进程树能力;没有复制第二套 app - shell。 -- 前端测试 13 文件:专用表单/prompt/usage 测试及既有跨 app matrix 增量。 -- 证据与文档 3 文件:transport capture、本文账本、extensions 设计附录。 -- `schema.rs`、`migration.rs`、`backup.rs`、三份认证套件及七个 pinned fixtures - 均零改动;restore、themes 与 extensions 实现零文件。 - -压缩方案也做过评估:把 Pi 强塞进 legacy live-sync、通用 additive provider、 -固定 app-type usage 语义或无 ledger Skill 复制可以减少文件,却会重新引入已经 -认证过的 ownership/原生真实/协议分类错误;把 UI 与领域代码揉成少数巨型文件只 -会减少文件数而增加审查风险。当前拆分复用了 provider write、proxy server、 -统一 OS 条件替换原语、Prompt/Skill/Session UI、同步锁和统计基础设施,新增代码 -只承载 Pi 不同的原生语义。 -因此不为满足计数机械缩并,也不扩展到契约外功能。 diff --git a/docs/pi-support-contracts-zh.md b/docs/pi-support-contracts-zh.md deleted file mode 100644 index a15ad0091..000000000 --- a/docs/pi-support-contracts-zh.md +++ /dev/null @@ -1,822 +0,0 @@ -# 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, -} - -fn get_provider_aggregate(&self, app_type: &str, id: &str) -> Result>; -fn get_all_provider_aggregates(&self, app_type: &str) -> Result>; -``` - -`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 }, - 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; -``` - -纯排序不在枚举内:`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, - 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, -} -``` - -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, // provider default - pub api: Option, // opaque provider default - pub api_key: Option, // unresolved expression(env/!command 不解析) - pub headers: HeaderMap, - pub models: Vec, - pub model_overrides: BTreeMap, - pub compat: Option, - pub extra: BTreeMap, -} - -pub struct PiManagedModel { - pub id: String, - pub name: Option, - pub base_url: Option, // model override - pub api: Option, // 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; -``` - -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 } - -pub fn read_pi_prompt_file(kind: PiPromptFileKind) -> Result; -pub fn replace_pi_prompt_file(kind: PiPromptFileKind, expected: PiPromptFileRevision, content: &str) - -> Result; -pub fn delete_pi_prompt_file(kind: PiPromptFileKind, expected: PiPromptFileRevision) -> Result; -``` - -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。 diff --git a/docs/pi-support-handoff-zh.md b/docs/pi-support-handoff-zh.md deleted file mode 100644 index 7fc376521..000000000 --- a/docs/pi-support-handoff-zh.md +++ /dev/null @@ -1,1093 +0,0 @@ -# Pi 支持重设计 Handoff - -> 文档状态:设计与实现交接,不是发布说明,也不表示当前实现可合并。 -> 快照日期:2026-07-31(UTC)。 -> 仓库:`farion1231/cc-switch`。 -> 当前工作分支:`feat/pi-switch-redesign`。 -> 实现基线:`b884595a237931808d4775fb709461141a26f508`。 -> 待重设计实现快照:`041ff113e1e0c6b03c0999a1659121fc5979b62a`。 -> 当前结论:第 7 轮双盲审后仍有两个已验证 High 和一个持久化数据一致性问题;本次 handoff 审计又确认了一个 device-local gateway token 外泄到 portable sync 的安全契约缺陷。不得把该快照直接合并、继续叠补丁或称为完成。 - -## 1. 一页摘要 - -这项工作的目标不是给 Pi 再造一个扩展系统,而是让 Pi 成为 CC Switch 的一等应用入口,并遵循一个简单边界: - -> Pi 核心原生提供、且与 CC Switch 现有管理域相符的能力,CC Switch 才直接支持;需要另造协议、扩展包、信任系统或兼容层的能力,不进入本次范围。 - -冻结后的目标范围包括: - -- Provider / Model 管理、默认 Provider / Model、预设和模型拉取; -- Pi 原生全局 Skills; -- Pi 原生全局 `AGENTS.md` 上下文; -- Pi 原生全局 prompt templates; -- Pi 原生 sessions 的发现、查看、恢复和删除; -- 同协议、请求级的本地 gateway、failover 和 circuit breaker; -- 与 CC Switch 既有界面一致的 UI、i18n、备份恢复和代理生命周期。 - -明确不做: - -- MCP:Pi 核心明确没有内置 MCP; -- OpenAI ↔ Anthropic 等跨协议转换; -- User-Agent 伪装或冒充官方客户端; -- Pi extension/package 的安装、启停和升级; -- project-local resource、package 和 trust 管理; -- Pi OAuth / `auth.json` 凭据生命周期管理; -- 为了“功能齐全”而继续加入 themes、keybindings、agent switcher 等可选域。 - -`041ff113` 已经覆盖上述大部分 UI 和后端入口,但其状态模型仍有根本缺陷: - -1. `app_type="pi"` 同时被当作产品归属和计费协议语义,导致 OpenAI/Gemini Pi 路由的缓存 token 与费用写错; -2. Skill 部署只有路径,没有所有权凭据,可能覆盖并删除用户原生 Pi Skill; -3. Provider 删除补偿只快照主表,投影失败时会永久丢失级联删除的自定义 endpoint; -4. 本机 gateway bearer token 存在通用 `settings` 表中,当前会被明文写入 portable SQL 和 WebDAV/S3 同步产物。 - -下一位开发者应把当前提交当作需求原型和测试素材,而不是待补齐的实现。先重审协议边界、资源所有权和 Provider aggregate,再以净减代码量为目标重构。 - -## 2. 用户要求与不可变约束 - -### 2.1 产品要求 - -- Pi 在 CC Switch 中必须是一等应用,不是“通过 Claude/Codex 假装支持 Pi”。 -- 所有实际入口都要一致工作:应用切换器、Provider 列表、添加/编辑、默认模型、Skills、Prompts、Sessions、Proxy、Failover、备份恢复、同步和 i18n。 -- 前端 UI/UX 复用 CC Switch 现有组件、视觉语言和交互,不单独做一套 Pi 风格。 -- Provider 需要 CRUD、预设、模型拉取和模型级配置。 -- 如果成熟 Pi switch 工具有本地 proxy/failover/circuit breaker,则 CC Switch 也要有,但不能因此引入协议伪装或跨协议转换。 -- 应频繁对照 Pi 官方文档和官方源码,并参考成熟社区实现,避免根据记忆闭门设计。 - -### 2.2 工程要求 - -- 优先一个 PR,最多两个 PR;双盲审数量不等于 PR 数量。 -- 不允许为了拆 PR 而破坏一个完整的数据不变量。 -- 当前 113 个文件、`+12884/-816` 的实现快照已经偏大;下一轮要主动删掉临时兼容层、重复分支和 app-specific glue。 -- 功能范围已经冻结。除设计重构自然消除的问题外,只处理 blocker/high 和明确的数据一致性问题。 -- 如果继续出现“Anthropic 统一入口 → 多协议上游”的兼容性压力,停止局部修补,回到协议边界设计。 -- 不要修改或删除 `stash@{0}: On pi-support: pi-safety-layer-draft`。 -- 仓库交付物中不得保留 `dist/` 或 `dist-web/`。 -- 开发 renderer 使用 5181;handoff 时 `http://127.0.0.1:5181` 返回 200,但这是临时运行状态,不是持久契约。 - -### 2.3 审查要求 - -当前工作区的 `AGENTS.md` 要求每次非平凡实现完成后执行独立盲审。该文件由开发环境注入并被 `.gitignore` 排除,不属于 Git tree;为避免 clean clone 或 GitHub 阅读者依赖断链,下面完整记录本次交接必须延续的有效契约: - -1. 固定完整审查范围,通常为 `main...HEAD`,并确保所有范围内修改都对 reviewer 可见。 -2. 启动两位 fresh、无对话继承、互相独立、只读的 blind reviewers,让两人都检查完整范围。 -3. 给 reviewer 详细但中立的仓库布局、架构边界、权威契约、精确 range、验证命令和审查维度。 -4. 不透露实现内容、目标 bug、设计理由、已知问题、历史 finding 或另一位 reviewer 的结论。 -5. finding 必须给出 severity、confidence、精确文件与行、具体故障场景、推理和建议方向;没有 finding 时必须明确写出。 -6. 主审必须独立对照源码与测试验证每条 finding,不能机械套用建议。 -7. 确认问题并完成 material fix 后重新双审;明显收敛到小型局部 patch 后,后续轮次可改用一位新的 blind reviewer。 -8. 按底层 invariant/故障场景跟踪问题;出现 review/repair 循环、补丁互相矛盾或同一 invariant 重复失败时,停止补丁并重审 ownership、boundary、state model、data flow、contract 和 test strategy。 -9. 设计级调整仍无法解决循环时,停止并报告未解决条件、历史、冲突约束和可行选项。 -10. 完成条件是无已验证 blocker/high、相关检查全部通过,并由主审审计完整 diff。 -11. 同一 implementation 最多七轮双 reviewer 审查;第七轮后仍有 blocker/high 或数据完整性问题时,禁止第八轮和继续打补丁,必须停止并报告。 - -当前 Pi 实现已经耗尽这七轮额度。后续不能把一次新的 review 称为“第八轮补丁审查”。如果重启开发,必须明确作为一次新的、设计已改变的 implementation,而不是延续现有 patch loop。 - -## 3. 功能边界的判定规则 - -### 3.1 契约优先级 - -每次开始编码前,按以下顺序确认行为: - -1. Pi 官方 `latest` 文档; -2. Pi 官方仓库当前源码和测试,并记录审阅的 commit; -3. Pi 实际配置 schema、资源加载顺序和 CLI 行为; -4. CC Switch 自己的 Claude/Codex/Gemini 既有不变量; -5. 成熟 Pi switch 项目,只作为产品、交互和容灾设计参考; -6. 其他 bridge/extension,只作为互操作性参考。 - -社区工具不是 Pi 原生契约。Pi 文档与源码不一致时,不要自行选一个“更方便”的解释:记录差异,先定义 CC Switch 的 managed/read-only 边界。 - -本 handoff 与本实现统一对照的 Pi 官方源码固定点为: - -```text -earendil-works/pi -implementation pin = ab366ebe94cacd419d986be454f12b1b9913aaca -snapshot date = 2026-07-31 UTC -``` - -Phase 0 在同日重新执行 §10.5 的 `git ls-remote`;刷新时远端 `HEAD` 已前进到 -`977ec833bbb86e245057e9162dbc1443c7b6e707`。该值只记录远端刷新事实,不是第二个 -实现 pin;schema、oracle、源码注释和审查 authority 均继续使用上面的 -`ab366ebe94cacd419d986be454f12b1b9913aaca` 干净源码快照。 - -官方入口: - -- [Custom Models](https://pi.dev/docs/latest/models) -- [Skills](https://pi.dev/docs/latest/skills) -- [Prompt Templates](https://pi.dev/docs/latest/prompt-templates) -- [Sessions](https://pi.dev/docs/latest/sessions) -- [Using Pi / Context Files / Design Principles](https://pi.dev/docs/latest/usage) -- [Pi source](https://github.com/earendil-works/pi) - -### 3.2 “Pi 原生”不等于“本 PR 全做” - -Pi 核心包含 extensions、packages、themes、keybindings、project resources 等能力,但它们会引入 CC Switch 当前没有的 package manager、project trust 或新资源域。 - -本次冻结边界是: - -- 原生能力必须存在,才允许接入; -- 同时还必须落在 CC Switch 已有管理域或用户明确要求的 proxy/failover 域内; -- 不因为 Pi 原生存在某能力,就自动扩大本 PR。 - -因此: - -- Skills、全局 context、prompt templates、sessions:在现有 CC Switch 管理域中,纳入; -- MCP:Pi 核心没有,排除; -- packages/extensions/project trust/themes/keybindings:即使 Pi 原生存在,也不在冻结范围; -- proxy/failover:不是 Pi 核心配置管理能力,但成熟 switch 工具已有,且用户明确要求,作为唯一产品层例外纳入; -- proxy 例外仍受“同协议透明转发”限制。 - -## 4. 功能矩阵 - -| 能力 | Pi 核心状态 | 本次目标 | 当前快照 | 边界说明 | -|---|---|---:|---:|---| -| Custom Provider / Model | `models.json` 原生 | 必须 | 已实现子集 | 必须兼容 Pi 当前 schema,不能静默丢 overlay | -| 默认 Provider / Model | `settings.json` 原生 | 必须 | 已实现 | 多模型 Provider 必须显式选择 model | -| Provider 预设 | CC Switch 产品层 | 必须 | 已实现 | 只生成 Pi 原生配置,不创造私有格式 | -| 拉取可用模型 | CC Switch 产品层 | 必须 | 已实现 | 结果落为原生 model entries | -| 全局 Skills | 原生 | 必须 | 已实现但有 High | 只管理全局;必须有部署所有权 | -| `~/.agents/skills` | 原生共享位置 | 必须兼容 | 已实现 | 注意 Pi 的优先级、frontmatter、ignore 规则 | -| Project Skills | 原生、受 trust 约束 | 不做 | 未做 | 不接管 `.pi/.agents` project resources | -| 全局 `AGENTS.md` | 原生 context file | 必须 | 已实现 | 它是 context,不等同于 `SYSTEM.md` | -| `SYSTEM.md` / `APPEND_SYSTEM.md` | 原生 | 冻结范围外 | 未做 | UI 不得把 `AGENTS.md` 误称为真正 system prompt | -| Prompt Templates | 原生 | 必须 | 已实现 | 全局、非递归、slash-command filename 语义 | -| Sessions | 原生 | 必须 | 已实现但有 Medium | 解析 session tree,而非把 JSONL 当线性日志 | -| 本地 gateway | 产品层 | 必须 | 已实现 | loopback、请求级路由、凭据不可进入 route token | -| Failover / Circuit Breaker | 产品层 | 必须 | 已实现 | 同 model、同 wire family、请求级,不改全局 current | -| SSE streaming | API 原生 | 必须 | 已实现 | 同协议逐段转发 | -| 跨协议转换 | 非必要 | 不做 | 未做 | 禁止 OpenAI ↔ Anthropic 转换 | -| User-Agent disguise | 非必要 | 不做 | 未做 | 禁止伪装 Claude Code/Codex/Gemini | -| MCP | Pi 明确无内置 | 不做 | 未做 | 不通过私有 extension 绕过边界 | -| OAuth / `auth.json` | Pi 原生凭据域 | 不做 | 未做 | 保持 Pi 所有权,CC Switch 不复制凭据 | -| Pi package/extension 管理 | 原生 | 不做 | 未做 | 不建立第二套 package manager | -| Agent 内自动 model switch | 社区 extension | 不做 | 未做 | Pi 已有 `/model`,不是 CC Switch 配置职责 | - -## 5. 当前实现快照包含什么 - -这一节只描述 `041ff113` 中存在的代码,不表示它已经满足发布质量。 - -### 5.1 前端入口 - -- [应用注册与图标](../src/config/appConfig.tsx) -- [应用切换器](../src/components/AppSwitcher.tsx) -- [Pi Provider 预设](../src/config/piProviderPresets.ts) - - 当前包含 OpenAI、OpenRouter、Anthropic、DeepSeek、SiliconFlow; - - 预设只提供 endpoint/API family 起点,模型仍需拉取或显式配置。 -- [Pi Provider 表单](../src/components/providers/forms/PiProviderForm.tsx) -- [默认模型选择](../src/components/providers/PiDefaultModelDialog.tsx) -- [Provider 卡片操作](../src/components/providers/ProviderActions.tsx) -- [Pi prompt templates 面板](../src/components/prompts/PiPromptTemplatesPanel.tsx) -- [共享 Prompt 面板](../src/components/prompts/PromptPanel.tsx) -- [共享 Skills 面板](../src/components/skills/UnifiedSkillsPanel.tsx) -- [共享 Sessions 页面](../src/components/sessions/SessionManagerPage.tsx) -- [共享 Proxy / Failover UI](../src/components/proxy) -- [Pi API 封装](../src/lib/api/pi.ts) -- en / zh / zh-TW / ja 四语 i18n - -UI 采用现有 Card、Dialog、Form、Toggle、Tabs、Toast 和 app icon 体系,没有另造 TUI 或 Pi 专用设计系统。 - -### 5.2 后端入口 - -- [Pi 配置投影](../src-tauri/src/pi_config/mod.rs) - - `models.json` / `settings.json`; - - exact-key projection manifest; - - direct / proxy 两种投影; - - default provider/model; - - 导入已有 Provider; - - 环境变量和 `!command` 值解析。 -- [保留未知 JSON 的文档写入](../src-tauri/src/pi_config/document.rs) -- [运行时配置值解析](../src-tauri/src/pi_config/runtime.rs) -- [Pi API family 与 failover wire profile](../src-tauri/src/proxy/pi_route.rs) -- [不可变 runtime snapshot 与 epoch/generation fencing](../src-tauri/src/proxy/pi_runtime.rs) -- [请求入口](../src-tauri/src/proxy/handlers.rs) -- [路由与熔断选择](../src-tauri/src/proxy/provider_router.rs) -- [共享 forwarder](../src-tauri/src/proxy/forwarder.rs) -- [Proxy 生命周期和投影协调](../src-tauri/src/services/proxy.rs) -- [Provider CRUD / switch / compensation](../src-tauri/src/services/provider/mod.rs) -- [Pi projection DAO](../src-tauri/src/database/dao/pi.rs) -- [Schema / migration / backup restore](../src-tauri/src/database) -- [全局 `AGENTS.md`](../src-tauri/src/prompt_files.rs) -- [Prompt templates service](../src-tauri/src/services/pi_prompt_templates.rs) -- [Skills 集成](../src-tauri/src/services/skill.rs) -- [Pi session adapter](../src-tauri/src/session_manager/providers/pi.rs) - -### 5.3 测试入口 - -- [Pi config Rust tests](../src-tauri/src/pi_config/tests.rs) -- `proxy/pi_route.rs`、`proxy/pi_runtime.rs`、`services/proxy.rs` 内联 Rust tests -- `services/provider/mod.rs`、`services/skill.rs`、`database/backup.rs` 内联回归 tests -- [Pi Provider 表单 tests](../tests/components/PiProviderForm.test.tsx) -- [默认模型 Dialog tests](../tests/components/PiDefaultModelDialog.test.tsx) -- [Prompt Templates tests](../tests/components/PiPromptTemplatesPanel.test.tsx) -- [Skills tests](../tests/components/UnifiedSkillsPanel.test.tsx) -- [前端 Pi API tests](../tests/lib/piApi.test.ts) -- [Pi app config tests](../tests/config/piAppConfig.test.ts) - -## 6. 期望的数据所有权与数据流 - -### 6.1 所有权表 - -| 状态/文件 | 权威所有者 | CC Switch 可以做什么 | 禁止做什么 | -|---|---|---|---| -| SQLite `providers(app_type='pi')` | CC Switch | 管理 CC Switch Provider aggregate | 假设它等同于 Pi 的完整 provider universe | -| `pi_provider_projections` | 本设备 CC Switch | 记录 exact provider key ownership | 通过前缀或内容猜测所有权 | -| `~/.pi/agent/models.json` | Pi + 用户 + 第三方共享 | 只 patch manifest-owned exact keys | 全文件覆盖、删除未拥有 key | -| `~/.pi/agent/settings.json` | Pi + 用户共享 | 只 patch必要默认字段 | 重写未知设置 | -| Pi `auth.json` | Pi | 保留、引用 Pi 的认证存在性 | 导入、导出、覆盖 OAuth/token | -| gateway token | 本设备 CC Switch | 本地生成、只用于 loopback gateway | 进入 portable sync、URL 或日志 | -| proxy runtime snapshot | 当前进程 | 原子发布、request lease | 请求中读取可变全局 Provider | -| `~/.pi/agent/skills` | Pi + 用户共享 | 只删除/替换有 ownership 证据的 deployment | 仅凭目录同名覆盖或删除 | -| `~/.agents/skills` | 用户共享 SSOT | 在 unified 模式复用 Pi 原生 discovery | 制造第二份伪“启用状态” | -| `~/.pi/agent/AGENTS.md` | 用户 + CC Switch 共享 | 有明确 prompt ownership 时合成 | 把任意原生文件当 CC Switch owned | -| `~/.pi/agent/prompts/*.md` | 用户 + CC Switch 共享 | 对用户明确选择的单文件 CRUD | 递归扫描、删未知文件 | -| Pi sessions | Pi | 只读发现/查看;用户显式删除 | 修改 transcript 内容或错误解析 branch | - -所有 `~/.pi/agent` 默认路径都必须经过 -[`get_pi_agent_dir`](../src-tauri/src/pi_config/mod.rs),尊重 -`PI_CODING_AGENT_DIR`。Sessions 还要处理 -`PI_CODING_AGENT_SESSION_DIR` 和 `settings.json.sessionDir`,不能在其他 -模块重新硬编码 home path。 - -### 6.2 Provider direct mode - -```text -CC Switch SQLite Provider aggregate - │ - ├─ validate against supported Pi managed shape - ├─ claim/reuse exact models.json provider key - ├─ atomic patch of only that key - └─ patch defaultProvider/defaultModel when explicitly selected -``` - -重要点: - -- SQLite 是 CC Switch managed Provider 的 SSOT; -- `models.json` 是共享投影,不是数据库镜像; -- unowned Pi entries 必须保留; -- built-in overlay、extension provider 和完整 custom catalog 不是同一种对象,不能都塞进一个必填 `models[]` 的结构。 - -### 6.3 Proxy mode - -```mermaid -flowchart LR - DB["SQLite Provider aggregate"] --> SNAP["Immutable PiRuntimeSnapshot"] - DB --> PROJ["Exact-key models.json projection"] - CFG["Pi proxy/failover config"] --> SNAP - SNAP --> ROUTE["route token + wire family binding"] - PROJ --> PI["Pi native client"] - PI --> GATE["Loopback Pi gateway"] - GATE --> LEASE["Request-local snapshot lease"] - ROUTE --> LEASE - LEASE --> TRY["Compatible provider attempts"] - TRY --> UP["Same-protocol upstream"] - UP --> STREAM["Same-format response/SSE"] - STREAM --> PI - STREAM --> USAGE["Usage attribution + explicit wire token semantics"] -``` - -必须保持的边界: - -- Pi 仍通过原生 `models.json` 看到模型; -- proxy base URL 只指向 loopback listener; -- route token 必须 credential-blind; -- 每个请求绑定一个 immutable catalog epoch; -- failover 只在与请求 wire contract 兼容的候选中进行; -- failover 不改变“当前 Provider”、不写全局 live config; -- 停止 takeover 时先关闭 runtime admission,再恢复 direct projection; -- usage 的产品归属仍是 Pi,但 token 语义由 wire family 决定。 - -### 6.4 Skills - -Pi 当前 discovery 不是“目录存在即启用”: - -- 有明确加载位置和优先级; -- 解析 `SKILL.md` frontmatter; -- `description` 必填; -- effective name 来自 frontmatter `name`,可与目录名不同; -- 重名 first-wins; -- root 直接 `.md` 与递归 `SKILL.md` 的规则不同; -- 遵循 `.gitignore`、`.ignore`、`.fdignore`; -- `~/.pi/agent/skills` 和 `~/.agents/skills` 可能产生 shadow。 - -当前实现修正了 name、description、root file、递归与 first-wins,但仍缺: - -- ignore-file 语义; -- 非 unified 模式的 deployment ownership。 - -### 6.5 Prompts 与 context - -必须区分三个概念: - -- `~/.pi/agent/AGENTS.md`:全局 context file; -- `~/.pi/agent/SYSTEM.md` / `APPEND_SYSTEM.md`:真正替换/追加 system prompt; -- `~/.pi/agent/prompts/*.md`:slash-command prompt templates。 - -当前共享 Prompt UI 把 Pi 的 `AGENTS.md` 放在名为 “system prompt” 的 tab 下。这是术语陷阱;下一轮要么改成“全局上下文”,要么明确扩展范围支持真正的 `SYSTEM.md`,不能继续把两者混称。 - -### 6.6 Sessions - -Pi session JSONL 是树,不是简单线性消息数组: - -- header 包含 session id、cwd、timestamp、version; -- entries 通过 id / parentId 构成分支; -- UI 应展示当前 active branch; -- `/tree`、`/fork`、`/clone` 会产生非线性历史; -- 恢复使用 `pi --session `。 - -全局 Session Manager 无法仅凭一个相对 `sessionDir` 推导所有启动 cwd。禁止在无法解析时悄悄扫描默认目录并表现为“已支持”;应显式提示 project-relative scope,或让用户提供可枚举的绝对 root。 - -## 7. 必须先写成测试的设计不变量 - -### 7.1 协议与计费 - -1. `logical_app_type` 只用于归属、UI 和筛选。 -2. `wire_api_family` 决定 adapter、endpoint、缓存 token 语义和 compatibility。 -3. `input_token_semantics` 必须在写入时显式确定,不能事后从 `app_type` 猜。 -4. Anthropic `input_tokens` 是 fresh input;OpenAI/Gemini 的输入字段包含 cached subset。 -5. Pi 的四个 API family 都需要 cache-heavy 计费和 rollup tests。 - -### 7.2 资源所有权 - -1. 只删除有 durable ownership 证据的路径或 JSON key。 -2. 同名目录、相同内容、指向 SSOT 的 symlink 都不能自动等同于 ownership。 -3. 发现 unowned collision 时,只能拒绝、显式 adopt 或先备份再接管。 -4. disable/uninstall/sync-all/migration 都必须走同一个 ownership 判定。 -5. portable backup/sync 不得转移本设备 projection/gateway ownership。 - -### 7.3 Provider aggregate 与投影 - -1. Provider mutation 的 rollback snapshot 必须覆盖同一 cascade boundary: - - provider row; - - `provider_endpoints`; - - projection claim; - - 其他持久化 child rows。 -2. runtime snapshot、SQLite 和 `models.json` 之间只能发布一个完整 catalog generation。 -3. 文件投影失败后,返回错误时数据库和 runtime 必须收敛到明确的 authoritative state。 -4. 不允许“主行恢复了,所以回滚完成”的假成功。 -5. 导入已有配置必须 side-effect free,直到用户明确采用。 - -### 7.4 并发与生命周期 - -1. Provider CRUD、default switch、failover queue、proxy enable/disable、restore 必须共用同一 Pi mutation boundary。 -2. mutation 开始先把 catalog epoch 标为不可 admission。 -3. 请求只使用 lease 时看到的 immutable Provider clone。 -4. 旧 epoch 的 health/usage writeback 不得污染新 catalog。 -5. listener generation 与投影使用的 host/port 必须来自同一已绑定实例。 - -### 7.5 共享文件 - -1. JSON patch 保留未知字段与未拥有 entries。 -2. 写入使用同目录临时文件和 atomic replace。 -3. symlink、非普通文件、超大文件和 path traversal fail closed。 -4. command-valued secrets 需要完整进程树 timeout,stdout 读取也必须有界。 -5. 错误日志不得包含 resolved secret。 - -## 8. 已踩过的坑与当前未解决问题 - -### 8.1 阻断合并的问题 - -#### H1:产品归属与 wire token 语义混用 - -涉及: - -- [RequestContext::new_for_pi](../src-tauri/src/proxy/handler_context.rs) -- [CostCalculator::calculate_for_app](../src-tauri/src/proxy/usage/calculator.rs) -- [UsageLogger::log_request](../src-tauri/src/proxy/usage/logger.rs) -- [SQL cache semantics helpers](../src-tauri/src/services/sql_helpers.rs) - -故障场景: - -```text -Pi -> OpenAI/Gemini route -input total = 1000 -cached = 800 -fresh should be 200 -``` - -当前写入仍以 `app_type="pi"` 选择 fresh semantics,因此按 `1000 fresh + 800 cached` 计费,并把错误 semantics 持久化到日志和 rollup。 - -为什么是设计问题: - -- `RequestContext` 已知道 `PiApiFamily`; -- forwarder 使用 `wire_app_type` 选择 adapter; -- usage 路径却丢失 wire family,只留下逻辑 app label; -- 给 cache-inclusive whitelist 简单加 `"pi"` 会反过来破坏 Anthropic Pi route。 - -推荐方向: - -- 在 usage event 中加入显式 `WireUsageSemantics` 或 `PiApiFamily`; -- `app_type="pi"` 保留用于归属; -- calculator、logger、backfill、rollup SQL、frontend normalization 共用同一显式 semantics。 - -#### H2:Pi Skill 部署没有 ownership - -涉及: - -- [SkillService::sync_to_app_dir](../src-tauri/src/services/skill.rs) -- `replace_dest_with_copy` -- `remove_from_app` -- storage migration / sync-all / toggle paths - -故障场景: - -1. CC Switch 在自己的 SSOT 管理 `review`; -2. 用户独立拥有 `~/.pi/agent/skills/review`; -3. 在默认 `CcSwitch` storage mode 为 Pi 启用 managed `review`; -4. Auto/Copy/Symlink 路径会删除原生目录并替换; -5. 后续 disable 又会删除当前目标。 - -已有 shadow detection 只覆盖 unified mode 的 effective discovery,不等于 deployment ownership。 - -推荐方向: - -- 引入 deployment manifest,最少记录 app、managed skill id、destination、method、source identity; -- symlink 必须核对 canonical target; -- copy deployment 可记录内容 digest/generation,但 digest 不能单独证明 ownership; -- collision 默认拒绝,adopt 必须显式并有备份; -- remove 只处理 manifest-owned destination。 - -#### D1:Provider 删除补偿丢失 child rows - -涉及: - -- [ProviderService::delete](../src-tauri/src/services/provider/mod.rs) -- [Database::get_provider_by_id](../src-tauri/src/database/dao/providers.rs) -- `provider_endpoints` cascade - -故障场景: - -1. 删除一个非当前 Pi Provider; -2. SQLite delete 成功并 cascade 删除 `provider_endpoints`; -3. `models.json` 投影因权限或文件错误失败; -4. rollback 调用 `save_provider`; -5. snapshot 没有 hydrate 独立 endpoint 表,主行恢复但 endpoint 永久丢失。 - -`provider_health` 也会丢失,但它是可重建 runtime state;`provider_endpoints` 是持久化用户配置,所以这是明确数据一致性问题。 - -推荐方向: - -- 提供 `get_provider_aggregate` / `restore_provider_aggregate`; -- 或把 delete + child snapshot + compensation 放入专门的 Pi mutation transaction object; -- 不要继续在调用点手工拼 `Option` rollback。 - -#### S1:device-local gateway token 被导出到 portable/sync 产物 - -涉及: - -- [database/backup.rs](../src-tauri/src/database/backup.rs) -- [services/sync_protocol.rs](../src-tauri/src/services/sync_protocol.rs) -- `settings.pi_gateway_token` - -当前 token 被存为 `settings` 表中的普通 row。portable export 和 -WebDAV/S3 sync 只支持按整张表跳过数据,没有排除单个 setting key,因此 -`dump_sql()` 会把 `pi_gateway_token` 以明文写入 `db.sql`。导入阶段恢复本机 -token 只能避免目标设备采用远端 token,不能撤回已经写入本地备份或上传到远端的 -副本。 - -这违反第 6.1 节的 device-local ownership 和第 14.2 节的凭据验收约束。即使 -gateway 仅监听 loopback,也不能把该 token 当作可迁移配置。 - -推荐方向: - -- 优先把 gateway token 移到既有 device-local settings 存储,或使用明确的 - local-only 表; -- 如果仍放在通用 `settings` 表,导出层必须按 key 过滤,而不是事后在 import - 覆盖; -- 为 `export_sql_string()` 和 `export_sql_string_for_sync()` 分别增加断言: - 产物不含 token key,也不含 token value; -- 同时检查历史 binary backup/SQL backup 的披露和 token rotation 策略。 - -### 8.2 已验证但因冻结范围未修的 Medium - -#### M1:Pi 原生 provider overlay 被静默跳过 - -当前 `PiProviderShape` 强制: - -- provider-level `baseUrl`; -- 非空 `models[]`; -- 拒绝 model-level `baseUrl`。 - -Pi 当前官方 schema 支持: - -- `models` 可选; -- built-in provider 仅覆盖 `baseUrl`; -- `modelOverrides`-only; -- model-level `baseUrl`; -- provider/model 两级 `api`。 - -导入逻辑对不支持 shape 只写 debug log 并跳过。原文件仍被保留,所以不是数据丢失,但“原生 Pi provider 支持”声明不完整。 - -设计选项: - -1. 完整支持 effective model composition; -2. 把 native overlay 作为 read-only/unmanaged entry 显示; -3. 明确 UI 只管理 full custom catalogs,并对跳过项给出诊断。 - -不要继续让用户看到“nothing imported”却不知道原因。 - -#### M2:`!command` timeout 不覆盖后代进程 - -[runtime.rs](../src-tauri/src/pi_config/runtime.rs) 只 kill 直接 shell,然后无界 `reader.join()`。 - -例如: - -```json -{ "apiKey": "!printf token; sleep 30 &" } -``` - -后台进程继承 stdout 后,shell 已退出但 pipe 不 EOF,读取线程可以超过 10 秒无限等待。 - -需要 process group/job object、完整 tree termination 和有界 stdout collection。Pi 官方本身允许 command-valued config,但 CC Switch 一旦代执行,就必须对自己的 GUI/proxy 线程负责。 - -#### M3:不同 host 的同协议 failover 被阻止 - -[pi_route.rs](../src-tauri/src/proxy/pi_route.rs) 把 endpoint 放进 wire profile equality。两个 API family、model id、request-shaping 都相同但 host 不同的 Provider 无法 failover。 - -这可能是保守安全边界,也可能违背“多 Provider 容灾”的产品承诺。编码前先决定: - -- endpoint 是 transport destination,还是 wire compatibility 的一部分; -- 哪些 headers/compat 字段会使 host 相关; -- 是否需要显式 failover compatibility group。 - -#### M4:relative `sessionDir` 被回退为默认 root - -[Pi session adapter](../src-tauri/src/session_manager/providers/pi.rs) 无法解析相对 cwd 时扫描默认 `~/.pi/agent/sessions`。这会产生“扫描成功但展示错误集合”的假象。 - -建议 fail explicit,或在 UI 让用户选择绝对 root;不要猜 cwd。 - -#### M5:Usage Dashboard 无 Pi filter - -Pi 日志写为 `app_type="pi"`,但 [frontend usage types](../src/types/usage.ts) 的 `AppType` / `KNOWN_APP_TYPES` 没有 Pi。Pi 数据会进入 All,却无法单独筛选。 - -修复时必须先解决 H1,避免把显示 app type 又拿去决定 token semantics。 - -### 8.3 已验证 Low / UX 问题 - -- 多模型 Pi Provider 同时显示“选择默认模型”和通用“启用”;后者调用无 model id 的 switch,必然报错。 -- Skill effective status 不遵循 Pi 的 `.gitignore`、`.ignore`、`.fdignore`。 -- Prompt template slug 允许空白;Pi slash command 只解析第一个 token,生成的模板无法按 UI 名称调用。 -- Pi Prompt UI 把 `AGENTS.md` 标成 system prompt,概念不准确。 - -### 8.4 已经修过、不要回归的坑 - -#### Shared JSON ownership - -- 不能整体覆盖 `models.json` / `settings.json`; -- exact provider key ownership 必须 durable; -- portable sync 不得携带本机 projection manifest; -- stale claim cleanup 失败不能被忽略。 - -#### Restore race - -数据库 restore 在 staging 期间不能提前读取“本地保留表”。当前修复在 commit boundary 持 live DB lock,再把 projection/runtime-local state 复制进 staged DB,最后提交。 - -#### Mutable runtime race - -请求不能在路由后继续读取 mutable DB/global Provider。当前 immutable snapshot、catalog epoch 和 generation fencing 是正确方向,应保留概念但可压缩实现。 - -#### Failover 不能改全局 current - -Pi failover 是单请求行为。不得复用会把 Provider 设为 current、改 live config 或广播 UI state 的旧 switch path。 - -#### Skill shadow 不等于目录同名 - -Pi 通过 frontmatter effective name 判重。目录名、文件名和 Skill name 可能不同;同目录也可能包含不同 effective name。 - -#### Pi session 是 tree - -只读取最后一行或按文件顺序展示会把 abandoned branch 混入 active transcript。 - -#### Restore / proxy 生命周期 - -数据库 restore、proxy start/stop、listener port 更新和 direct/proxy projection 必须使用同一锁与 generation;不能只修正常 CRUD。 - -## 9. 七轮 blind review 历史 - -下面按底层 invariant 记录,而不是逐条复述 reviewer 文案。 - -证据限制:下表是根据本次持续开发会话中的 reviewer 回执和主审裁决重建的 -handoff 摘要,不是仓库内可独立复放的审计日志。已知最终冻结范围是 -`b884595a237931808d4775fb709461141a26f508...041ff113e1e0c6b03c0999a1659121fc5979b62a`; -前六轮各自的 range SHA、两份原始报告和逐项裁决没有作为 Git artifact 保存, -因此接手者不能仅凭下表复放每轮过程,也不应编造缺失证据。七轮硬停止仍是当前 -用户指令与本地审查契约,必须遵守。 - -| 轮次 | 结果 | 处理 | -|---|---|---| -| 1–2 | 早期实现审查与基础修复 | 已进入后续收敛范围;没有把旧结论当作终审豁免 | -| 3 | 发现 DB restore 与 Pi runtime/projection 生命周期 High | 修复 restore 后的 projection/runtime reconcile | -| 4 | 无 blocker/high;剩余 Medium/Low | 冻结可选能力,不扩 protocol compat | -| 5 | immutable runtime snapshot / request-local routing 后无 blocker/high | 保留 epoch/generation fencing | -| 6 | restore preserve race、Skill effective discovery、compensation failure reporting 等数据一致性问题 | 修复并补测试;overlay、cross-host failover、relative sessionDir、slug 等冻结 | -| 7 | Reviewer B:无 blocker/high,报告 Medium/Low;Reviewer A:2 High + 2 Medium | 主审确认 H1、H2 和 D1 成立,触发硬停止 | - -有一次 review 因上游 `main` 移动而取消,审查的是旧基线,不计入正式轮次。 - -第 7 轮后已执行: - -- 停止代码修改; -- 未启动第 8 轮; -- 未 push; -- 未创建 PR; -- 删除一次性验证容器 `cc-switch-pi-validate-20260730`; -- 保留 renderer 5181、stash 和干净工作树。 - -## 10. 参考实现与应该借鉴什么 - -### 10.1 `@cokefenta/pi-switch` - -- GitHub:[CallmeLins/pi-switch](https://github.com/CallmeLins/pi-switch) -- Pi package:[package page](https://pi.dev/packages/%40cokefenta/pi-switch) -- 安装:`pi install npm:@cokefenta/pi-switch` -- 本次审阅 commit:`5cfa2e8d3f6657b0508ee66ea63acdedfb53388d` -- 2026-07-31 快照:34 stars / 2 forks - -高价值参考: - -- CLI / TUI / WebUI 复用同一个 core; -- Provider CRUD、预设、模型 expose/fetch; -- model-name request routing; -- failover chain、circuit breaker、half-open recovery; -- same-format SSE; -- stats、backup、doctor; -- 把“配置管理”和“runtime gateway”拆成不同模块。 - -不要复制: - -- OpenAI ↔ Anthropic conversion; -- User-Agent disguise; -- package management; -- 它自己的私有 profile 配置作为 CC Switch 新 SSOT; -- cross-format 非流式降级。 - -它证明了用户确实需要 gateway/failover,不证明它的协议转换边界适合 CC Switch。 - -### 10.2 `Wing900/Pi-switch` - -- GitHub:[Wing900/Pi-switch](https://github.com/Wing900/Pi-switch) -- 本次审阅 commit:`e2cc7f7837136277d317b742b650f402048ca80d` -- 2026-07-31 快照:47 stars / 4 forks - -可参考: - -- 桌面用户对 Provider/Model 拉取、默认模型和一键启动 Pi 的信息架构; -- 小型 GUI 的操作路径。 - -不要复制: - -- Wails 技术栈; -- 仍在快速变化的内部数据模型; -- 仅凭 star 数判断实现正确。 - -### 10.3 `pi-cc-switch-provider` - -- GitHub:[Ginkgoooo/pi-cc-switch-provider](https://github.com/Ginkgoooo/pi-cc-switch-provider) -- 安装:`pi install git:github.com/Ginkgoooo/pi-cc-switch-provider` -- 本次审阅 commit:`740c06692b15ad891e6d3ac3ee24c053d2626a9a` -- 2026-07-31 快照:8 stars / 1 fork - -高价值参考: - -- 读取 CC Switch Claude/Codex live 配置的路径解析; -- live vs fixed routing; -- Pi provider registration; -- 模型选择、reasoning、auth 和 config-dir override 的一致快照; -- relative path fail-closed; -- 凭据不写入 extension local config。 - -不要把它作为主架构: - -- 它依赖 Pi extension; -- 它桥接 Claude/Codex,不等于 Pi 原生 `models.json` 管理; -- 它的价值是兼容已有重度 cc-switch 用户,而不是替代一等 Pi support。 - -### 10.4 `pi-model-switch` - -- GitHub:[nicobailon/pi-model-switch](https://github.com/nicobailon/pi-model-switch) -- 本次审阅 commit:`e2798ea5dd3daf61ced4bd18a2cd14435cf896cd` -- 2026-07-31 快照:93 stars / 9 forks - -它解决“agent 在 Pi 内自己切 model”,不属于 CC Switch Provider 配置与容灾职责。可以理解用户场景,不应加入冻结范围。 - -### 10.5 参考项目的时效性规则 - -Star、release、README 和默认分支都会变化。开始新设计时重新执行: - -```bash -git ls-remote https://github.com/earendil-works/pi.git HEAD -git ls-remote https://github.com/CallmeLins/pi-switch.git HEAD -git ls-remote https://github.com/Ginkgoooo/pi-cc-switch-provider.git HEAD -git ls-remote https://github.com/Wing900/Pi-switch.git HEAD -``` - -把实际审阅 commit 写进设计说明和测试注释;不要只写“参考最新版本”。 - -## 11. CC Switch 内部优质参考 - -### 11.1 先看代码,再看 PR - -最值得复用的现有边界: - -- Provider CRUD / switch:[services/provider](../src-tauri/src/services/provider) -- live config ownership:[services/provider/live.rs](../src-tauri/src/services/provider/live.rs) -- proxy 生命周期:[services/proxy.rs](../src-tauri/src/services/proxy.rs) -- request adapters:[proxy/providers](../src-tauri/src/proxy/providers) -- request-local forwarding:[proxy/forwarder.rs](../src-tauri/src/proxy/forwarder.rs) -- Codex exact owned artifacts:[codex_config.rs](../src-tauri/src/codex_config.rs) -- session provider adapters:[session_manager/providers](../src-tauri/src/session_manager/providers) -- shared Prompt / Skill UI:[components/prompts](../src/components/prompts)、[components/skills](../src/components/skills) -- usage semantics:[proxy/usage](../src-tauri/src/proxy/usage)、[sql_helpers.rs](../src-tauri/src/services/sql_helpers.rs) - -Claude 的优点是简单的 provider/live 配置映射;Codex 的优点是模型目录、OAuth 所有权、takeover 与 session 的复杂边界已经被大量真实问题打磨。Pi 需要同时借鉴两者,但不能把 Pi 强行归约为 Claude 或 Codex。 - -### 11.2 merged PR 高信号参考集 - -检索方法: - -```bash -gh pr list \ - --repo farion1231/cc-switch \ - --state merged \ - --limit 300 \ - --json number,title,author,mergedAt,url,additions,deletions,changedFiles -``` - -以下计数是 2026-07-31 查询到的最近 300 条 merged PR 中的贡献次数,不是作者终身贡献量。筛选信号依次是:owner/maintainer、重复 merged 贡献、相关领域、review/测试质量;star/follower 只能是弱信号。 - -| PR | 作者与可信信号 | 为什么值得看 | -|---|---|---| -| [#1098](https://github.com/farion1231/cc-switch/pull/1098) partial key-field merging | `farion1231`,仓库 owner | 共享配置不能全量覆盖 | -| [#4076](https://github.com/farion1231/cc-switch/pull/4076) takeover residue recovery | `farion1231` | takeover/restore ownership与残留恢复 | -| [#1724](https://github.com/farion1231/cc-switch/pull/1724) provider key lifecycle | `yovinchen`,最近 300 条中 34 个 merged PR | Provider key ownership、serializer failure | -| [#1714](https://github.com/farion1231/cc-switch/pull/1714) transparent header forwarding | `yovinchen`,34 merged | 共享 forwarder 与 header 边界 | -| [#1561](https://github.com/farion1231/cc-switch/pull/1561) full URL endpoint rewriting | `yovinchen`,34 merged | endpoint 不是简单字符串拼接 | -| [#1918](https://github.com/farion1231/cc-switch/pull/1918) Gemini Native API proxy | `yovinchen`,34 merged | 新 native protocol family 如何贯穿 handler/adapter/UI/test | -| [#5928](https://github.com/farion1231/cc-switch/pull/5928) remove redundant proxy query paths | `SaladDay`,最近 300 条中 9 个 merged PR | 以净减代码量收敛查询边界 | -| [#2349](https://github.com/farion1231/cc-switch/pull/2349) Codex session history | `SaladDay`,9 merged | Provider 切换与 session 可见性 | -| [#2429](https://github.com/farion1231/cc-switch/pull/2429) side-effect-free import | `xwil1`,最近 300 条中 3 个 merged PR | import 阶段不应改 live state | -| [#3360](https://github.com/farion1231/cc-switch/pull/3360) catalog refresh on switch | `Postroggy`,最近 300 条中 2 个 merged PR | Provider 与 model catalog 必须同时收敛 | -| [#3689](https://github.com/farion1231/cc-switch/pull/3689) skip backup/restore for proxy placeholder | `YongmaoLuo`,最近 300 条中 2 个 merged PR | 不把 takeover placeholder 当真实配置备份 | -| [#2791](https://github.com/farion1231/cc-switch/pull/2791) protect skills during copy fallback | `rogerdigital`,最近 300 条中 2 个 merged PR | Skill copy fallback 的数据安全 | -| [#5811](https://github.com/farion1231/cc-switch/pull/5811) Skill/security hardening | `zayokami`,最近 300 条中 2 个 merged PR,该 PR 已 approved | zip-slip、凭据泄漏、panic 等威胁模型 | -| [#2231](https://github.com/farion1231/cc-switch/pull/2231) root-level Skill discovery | `santugege`,merged/approved | Skill discovery 不能只按常见目录猜 | -| [#2774](https://github.com/farion1231/cc-switch/pull/2774) response model/input token logging | `LaoYueHanNi`,最近 300 条中 3 个 merged PR | wire conversion 后 usage 语义易错 | -| [#5071](https://github.com/farion1231/cc-switch/pull/5071) Codex native Anthropic upstream | `yeeyzy`,最近 300 条中 2 个 merged PR,该 PR 已 approved | 可研究多协议代价;不能据此扩大 Pi 跨协议范围 | - -不要机械 cherry-pick 这些 PR。它们提供的是已经在本仓库暴露过的 failure patterns。 - -## 12. 推荐的重设计 - -### 12.1 先写四份小契约,不先改代码 - -建议先在同一设计文档中固定: - -1. `PiManagedProvider` 与 `PiNativeOverlay` 的边界; -2. `LogicalApp`、`WireApiFamily`、`UsageSemantics` 三者关系; -3. `ManagedDeployment` ownership model; -4. `PiMutation` 的 DB aggregate、file projection、runtime publication 顺序。 - -每份契约都应有: - -- authoritative source; -- owned/unowned state; -- success postcondition; -- failure postcondition; -- rollback boundary; -- concurrency boundary; -- 最少三个故障注入测试。 - -### 12.2 Provider 类型不要再用一个松散 JSON shape 承担所有语义 - -建议概念上拆成: - -```text -PiManagedProvider - id - display metadata - auth expression (unresolved) - provider defaults - explicit managed models - model overrides - custom endpoints - projection key - -PiNativeEntry - provider key - raw shared JSON - effective kind: built-in overlay | custom catalog | extension overlay - ownership: unowned | adopted | managed - diagnostics -``` - -不一定要建立两张新表,但代码层必须能区分,避免: - -- 导入时强行要求 `models[]`; -- 为了 proxy 方便而拒绝 Pi 合法配置; -- 把 unproxyable 等同于 invalid; -- 把 read-only native entry 悄悄隐藏。 - -### 12.3 usage 事件显式携带 wire semantics - -推荐最小结构: - -```rust -struct UsageAttribution { - logical_app: AppType, // Pi - wire_family: WireApiFamily, // Anthropic/OpenAI Responses/... - input_semantics: InputTokenSemantics, -} -``` - -不要让 frontend `KNOWN_APP_TYPES`、Rust app whitelist 和 SQL app list 各自推导 semantics。数据库已经有 `input_token_semantics`,应让写入路径成为 SSOT。 - -### 12.4 Skill ownership 复用为通用能力 - -这不是 Pi 特例。当前 Claude/Codex 等 app 也可能存在同名原生目录。 - -建议做一个小而通用的 deployment ledger: - -```text -managed_resource_deployments - resource_type - resource_id - app_type - destination - deployment_kind - source_identity - generation - created_at -``` - -先确认仓库是否已有可复用的 ownership 表;没有再加。删除、迁移、copy fallback、symlink refresh 都通过同一个 service。 - -### 12.5 Provider mutation 使用 aggregate,而不是继续扩充 compensation 参数 - -当前调用点逐步增加 `previous provider`、`rollback result`、`reconcile result`,已经显示边界错误。 - -推荐: - -- 一个函数加载完整 `ProviderAggregate`; -- 一个 mutation guard 负责 epoch、DB snapshot、projection、runtime publish; -- 错误返回中明确 `authoritative_state`; -- compensation 是 aggregate restore,不是 `save_provider`。 - -如果实现继续在每个 CRUD 分支手工加 rollback,立即停止。 - -### 12.6 净减代码量的具体方向 - -- 把 Pi capability 放入 app capability registry,减少 `appId === "pi"` 分支; -- Provider UI 复用通用 model editor,只保留 schema adapter; -- direct/proxy projection 共用一个 pure projection plan; -- default provider/default model 合并为一个明确 command; -- usage 语义使用 typed field,删除跨 Rust/TS 的 app whitelist 推断; -- Skill discovery 与 deployment ownership 分层,状态计算不触碰文件; -- Sessions 复用 provider adapter,不在共享 UI 增加 Pi 条件; -- 删除所有为跨协议可能性预留、但冻结范围永远不会用到的 compatibility glue。 - -## 13. 建议执行顺序 - -### Phase 0:冻结与隔离 - -- 不在 `041ff113` 上继续追加兼容补丁; -- 保留该 commit 供 diff、测试和 UI 原型参考; -- 从最新 `main` 建一个明确命名的新设计分支,或在当前分支先 revert/replace 有问题的层; -- 开始前再次确认 upstream main 和 Pi official commit。 - -### Phase 1:设计与 contract tests - -按顺序完成: - -1. usage attribution contract tests; -2. Skill ownership collision tests; -3. Provider aggregate rollback tests; -4. native overlay classification tests; -5. runtime generation/concurrency tests。 - -这一步不加新 UI。 - -### Phase 2:后端最小闭环 - -只实现: - -- native config classification; -- exact-key projection; -- default model; -- same-family gateway; -- request-local failover; -- explicit usage semantics; -- ownership-safe Skills; -- 完整 Provider aggregate rollback。 - -### Phase 3:复用现有 UI - -- 接回 Provider form/default dialog; -- 接回 Skills/Prompts/Sessions/Proxy 入口; -- 清理重复 `pi` 条件分支; -- 补齐四语 i18n parity; -- 修正 `AGENTS.md` 的 UI 术语。 - -### Phase 4:恢复与故障注入 - -至少覆盖: - -- malformed/unwritable `models.json`; -- DB rollback trigger failure; -- endpoint child rows; -- proxy start/stop 与并发 Provider edit; -- database import/restore; -- stale request writeback; -- native Skill collision; -- background credential command; -- relative session dir; -- cache-heavy four-family usage。 - -### Phase 5:单 PR 收敛 - -- 首选一个 PR; -- PR 内可以有多个便于 review 的提交; -- 不以多 PR 代替清晰架构; -- 如果完整设计仍远超健康审查体量,只允许拆成最多两个独立可验证的 PR,并先征得用户确认。 - -## 14. 验收标准 - -### 14.1 功能 - -- Pi 可在 AppSwitcher 正常显示、隐藏和切换; -- Provider CRUD、duplicate、sort、preset、fetch models 正常; -- 合法 native entry 不被破坏,unsupported/read-only entry 有可见诊断; -- default provider/model 在 direct/proxy 两种模式一致; -- Skills、global `AGENTS.md`、prompt templates、sessions 都走 Pi 原生路径; -- proxy/failover/circuit breaker 只做 same-family request-local routing; -- Pi usage 可单独筛选,费用和 fresh/cache token 正确; -- backup/restore 后 DB、projection、runtime 一致。 - -### 14.2 安全与数据 - -- 无 unowned JSON key 或文件被覆盖/删除; -- 无 credential 进入 URL、route token、log、portable backup; -- command resolver 不可无限挂起; -- path traversal、symlink、非普通文件 fail closed; -- deletion rollback 恢复完整 aggregate; -- stale epoch 不写回 usage/health。 - -### 14.3 UI/UX - -- 使用现有 Provider 卡片、Dialog、Form、Tabs、Toast 和颜色; -- Pi 多模型 Provider 不显示必然失败的通用 Enable; -- 所有入口有 loading/error/empty/disabled 状态; -- en、zh、zh-TW、ja key parity; -- 不把 `AGENTS.md` 称为实际 `SYSTEM.md`; -- 5181 renderer 上手工覆盖所有入口。 - -### 14.4 审查与验证 - -- `git diff --check`; -- Rust fmt、clippy、full lib tests; -- frontend typecheck、unit tests、format、renderer build; -- build 后删除生成的 `dist/` / `dist-web/`; -- 两位 fresh blind reviewers 审完整范围; -- 主审逐条验证; -- 无 validated blocker/high; -- 无明确数据一致性 finding; -- 最终主审检查完整 diff,而不是只看最后一个 patch。 - -## 15. 验证命令与环境注意事项 - -最后一次实现验证通过: - -```text -cargo test --lib: 2319 passed, 0 failed, 2 ignored -frontend unit tests: 89 files, 593 tests passed -cargo fmt --check: passed -cargo clippy --lib -- -D warnings: passed -pnpm typecheck: passed -pnpm format:check: passed -pnpm build:renderer: passed -git diff --check: passed -``` - -建议命令: - -```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 -``` - -注意: - -- 之前的隔离验证容器已经按要求删除,不要假设它仍存在; -- reviewer 曾遇到 host `src-tauri/target` 权限和 OpenSSL dev metadata 问题; -- 如需容器验证,重新创建一次性容器并保证生成文件所有权不会污染宿主; -- renderer build 会生成 `dist/`,验证后必须删除; -- 不要删除或覆盖用户 stash。 - -## 16. 当前交接状态 - -在新增本文档前确认的 feature implementation 状态: - -```text -branch: feat/pi-switch-redesign -implementation snapshot: 041ff113e1e0c6b03c0999a1659121fc5979b62a -tracking before this docs change: origin/main, ahead 1 -origin/main: b884595a237931808d4775fb709461141a26f508 -feature worktree before this docs change: clean -PR: none -push: none -dist/: absent -dist-web/: absent -stash@{0}: On pi-support: pi-safety-layer-draft -renderer: http://127.0.0.1:5181 -> 200 -validation container: removed -``` - -本文档应作为 `041ff113` 之后的独立本地 docs change 交付;不要据此把 -`041ff113` 解释为可合并。上述是 2026-07-31 的运行时事实,下一位接手者必须重新执行: - -```bash -git fetch origin main -git status --short --branch -git log -1 --oneline --decorate -git stash list -test ! -e dist -test ! -e dist-web -curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5181 -``` - -## 17. 不要做的事情 - -- 不要直接 push 或为 `041ff113` 创建 PR。 -- 不要在该实现上启动“第 8 轮双审”。 -- 不要通过把 `"pi"` 加进 cache-inclusive app whitelist 修 H1。 -- 不要只在 Pi toggle 上加一个 `dest.exists()` 判断修 Skill ownership。 -- 不要只给 `Provider` struct 塞更多临时字段修 rollback。 -- 不要把 endpoint 从 wire profile 删除后就宣称 cross-host failover 安全。 -- 不要把所有 Pi native features 自动加入本 PR。 -- 不要加入 MCP extension 来绕开“Pi 无内置 MCP”的边界。 -- 不要复制 pi-switch 的协议转换或 UA disguise。 -- 不要用 star/follower 替代源码、测试和贡献记录审查。 -- 不要让导入已有 Pi 配置产生写文件、claim ownership 或切默认值等副作用。 -- 不要因为全量 tests 通过就忽略未覆盖的数据流不变量。 - -## 18. 接手者的第一天清单 - -1. 阅读本文、当前环境若存在的本地 `AGENTS.md` 和 `041ff113` 完整 diff;可分发的审查契约以第 2.3 节为准。 -2. 更新 Pi 官方源码并记录 commit。 -3. 重跑第 16 节状态检查。 -4. 用最小复现确认 H1、H2、D1、S1,不先修。 -5. 写出第 12.1 节四份契约。 -6. 决定 native overlay 的 managed/read-only 表达。 -7. 评估从 main 重建与在当前分支 replace 两种方案的净代码量。 -8. 向用户汇报设计、预计删改范围和单 PR 边界。 -9. 获得设计确认后才开始新 implementation。 - -最终目标不是“让 Pi 在列表里出现”,而是让 Pi 的原生配置、CC Switch 的状态所有权和本地 gateway 的请求数据流在失败与并发条件下仍然只有一个真相。 diff --git a/docs/pi-support-restructure-zh.md b/docs/pi-support-restructure-zh.md deleted file mode 100644 index 03312544a..000000000 --- a/docs/pi-support-restructure-zh.md +++ /dev/null @@ -1,111 +0,0 @@ -# Pi 支持项目级重启裁决:前置工程结构与测试先行 - -> 文档状态:项目级裁决(用户 2026-08-01 批准),优先级高于既有契约、修正案 1/2 的交付与审查结构条款;契约的技术条款(不变量、类型、义务)继续有效。 -> 触发:R4 未通过,三个 invariant 家族第四次出现,按修正案 2 §G 项目级硬停止。 -> 裁决依据:四轮证据表明问题不再是契约缺失或未落实(R4 时 oracle 已真实、类型屏障已就位),而是**认证单元过大**(12k 行 × 高不变量密度 × 零 High 标准 = 每轮必有新 High)与**实现方稳定的"九成对一成微妙错"率**共同作用。修复对象是认证结构与工作方式,不是再写一份修正案。 - -## 1. 新结构:三个前置工程 + 主工程,认证单元 ≠ PR 数量 - -单体累积认证(检查点 A/B/C)废止。改为: - -| 工程 | 范围 | 认证对象 | 预算 | -|---|---|---|---| -| 前置 A | Provider 类型化写面 + endpoint 所有权(全 app) | `dao/provider_write.rs`、相关 service 入口、扫描器及其测试 | 2–3 轮 | -| 前置 B | Data-only canonical restore(含 R4 新发现:safety backup 并发丢写窗口、binary TOCTOU、incremental auto-vacuum 保留) | `database/backup.rs`、`schema.rs` 的 restore 面及测试 | 2–3 轮 | -| 前置 C | 只读 native inspection(完全由 pinned Pi 语义向量驱动) | `pi_config/raw_schema.rs`、`composer.rs`、`native.rs`、`gateway.rs`(capability 部分)、oracle 夹具及测试 | 2–3 轮 | -| 主工程 | Skills/Prompts/Sessions/UI/i18n + gateway 数据面 | 建立在已认证前置之上,认证结构届时另定 | 另计 | - -- 每个前置工程是独立认证对象:两位 fresh blind reviewer 审**该组件的完整文件与测试**(组件级审计,不是 diff 审计),小到一轮看得透; -- 依据 handoff §2.2"双盲审数量不等于 PR 数量"与规则 8/9"同一 invariant 重复失败 → 重审 boundary 与 test strategy":重构认证结构正是执行该规则,各前置工程作为新认证对象持有独立小预算,这不是绕过七轮纪律,而是其结论; -- **最终交付仍是一个 PR**(用户要求不变);前置工程在同一分支上以 commit 组推进; -- 任一前置工程在自己的预算内不收敛 → 单独停止上报,不牵连其他工程。 - -## 2. 工作方式反转:测试先行 - -四轮共同病根之一是实现方的字面合规:契约文字与测试之间的任何缝隙都会变成缺陷。自本裁决起: - -1. **Claude 先交付认证级测试套件**:把 R1–R4 审出的全部故障场景 + 契约义务固化为可执行测试(含扫描器负向 fixture);测试即契约的字面; -2. **Codex 实现到全绿**:不得修改认证测试(发现测试本身有错时,报告并由 Claude 裁决修订);可以自由增补自己的测试; -3. 全绿 + 内部自审(修正案 2 §F 对应节)后进入该前置工程的盲审; -4. 盲审 finding 若揭示测试套件的缺口,缺口先补进测试,再修实现——测试套件是活的认证资产,逐工程滚动加厚。 - -## 3. R4 finding 的归属(全部并入对应前置工程的测试范围) - -| Finding | 归属 | -|---|---| -| managed DTO 把 thinkingLevelMap 收窄为 `Option`,oracle 证明必须无损 | 前置 C | -| gateway 把 Authorization/x-api-key/x-goog-api-key 列为 protected 并强制 apiKey,header-auth 配置误降 DirectOnly | 前置 C | -| restore 行校验只查 storage class/JSON/decimal,`sort_index=-1` 可发布后生产读取失败 | 前置 B | -| safety backup 与 publish 之间并发丢写窗口;binary restore TOCTOU;canonical stage 丢 incremental auto-vacuum | 前置 B | -| `update_provider_settings_config` 绕过类型化写面且零行静默成功 | 前置 A | -| `reconcile_provider_record` 先查存在再分支,并发 create 冲突退化为覆盖更新 | 前置 A | - -结构教训一并纳入:**DML allowlist 必须缩到 `provider_write.rs` 模块级**(R4 逃逸正是因为按文件豁免了整个旧 DAO);"252/252 字段执行过"不等于下游语义正确,前置 C 必须有 oracle→managed→inspection 的**端到端无损往返**测试。 - -## 4. 盲审材料与验证(每个前置工程) - -- 材料:该组件的规范契约节选(中立重生成)、组件文件清单、oracle/provenance(前置 C)、验证命令、通用审查维度;不含任何轮次历史、finding、自审报告; -- 验证:`cargo fmt --all -- --check`、**`cargo clippy --lib --tests -- -D warnings`(必须带 `--tests`,认证模块是 `#[cfg(test)]`,不带就不编译它)**、`cargo test --lib` 全套 + 组件认证测试全绿 + 扫描器负向 fixture 红名单确认,均在干净 SHA 上; -- 完成条件:零 validated blocker/high/data-integrity finding。 - -> 文档考据说明:修正案 1/2(`pi-support-contracts-amendment-*.md`)的条款已按其自身要求**合并**进 `pi-support-contracts-zh.md` 与 `pi-support-review-contract-zh.md`,独立文件已随合并删除,这是预期状态而非丢失;本文引用的"修正案 2 §F/E1"以合并后的规范文档对应章节为准。 - -## 4.7 协议放宽:契约纲领化(用户裁决,2026-08-02,优先于 §2、§4) - -自本条起,**所有后续契约以纲领为主**:只给目标与验收标准,实现方(编码 Codex) -获得相当大的自由度。§2"测试先行 + 冻结认证套件"的重仪式**不再默认适用**。 - -**保留的最小规则**(全部有实证代价支撑,不是仪式): - -1. **既有测试不得弱化**:测试总数只增不减,盲审核对; -2. **冻结物不动**:pinned 夹具、已重冻的 infra 三文件、restore 面; -3. **行为主张须有证据**:凡断言"pinned Pi 如此行为",须有 oracle 或 - request-capture 实证(`scripts/pi-transport-capture.mjs`)。此条是四轮 - 返工换来的——读源码推断先后写反过 Google header-only 契约与 authHeader 次序; -4. **不 push、不建 PR、不动 `stash@{0}`、不碰 PR #5598**。 - -**取消的**:精确红绿账本与"偏离即硬停止";每工程 2 轮 fresh 双审的固定配额; -实现路径、DTO 形状、测试组织方式的规定;裁决方逐条批准。实现方自行判断何时 -完成,自行组织验证与审查,完成后报终态 SHA。 - -**审查范围(2026-08-02 补充)**:§1 的"组件级审计、不是 diff 审计"改为增量制—— -① 某 SHA 上审干净即记为**已审基线**,之后只审 diff since 基线; -② 但**契约条款一旦新增或修改,其作用域必须全域扫调用点**(grep 定向扫描,不必通读), - 这保住 R3 那类"契约写对而错误旧路径仍在"的漏网面; -③ 发 PR 前做**一次**完整组件通读作保险。 -④ 验证 pinned Pi 行为**不再逐行读上游源码**:跑 oracle 或 - `scripts/pi-transport-capture.mjs` 取实证,快一个数量级且更可靠。 - -**为什么放宽**:前置 A/B/C 的证据表明,重仪式的边际收益在下降而成本在上升—— -后期盲审 finding 多为"防作弊机器"的自我军备竞赛,且契约越长裁决方自身出错越多 -(表名、文件映射、虚假覆盖声称、与上游相反的契约,均为契约复杂化后的产物)。 -实现方在四个工程中零作弊记录(精确硬停、拒改认证文件、顶住 reviewer 的错误建议), -信任已由行为挣得。 - -## 4.6 前置 B 终止关账(用户裁决,2026-08-02,优先于 §4.5) - -§4.5 的缩围执行后,重认证 R1 仍在**存量产品面**出现 3 项 High(配置目录可变 -性竞态、restore 未复用写面校验链、safety backup 可预测文件名)。用户裁决: -**PR 聚焦 Pi 业务,restore 老面放一放,不乱扩张**。据此前置 B **终止关账**: - -- Pi 耦合面已达成且在两套认证套件中持续全绿,Pi 工程期间完成的加固随 PR 保留; -- 5 项未决 finding 与 v1–v15 历史导入移交 `docs/restore-hardening-debt-zh.md` - (独立"Restore 加固工程"债务登记),不在本 PR 范围; -- infra 基线(schema.rs/migration.rs/backup.rs)已在前置 A 套件中按 `3fa6b1f1` - 状态**重冻**:本 PR 内任何再触碰即认证红,机械防止范围回潮; -- 前置 B 不再有重认证轮次;§4.5 的 N/N−1 缩围作为已落地事实保留。 - -## 4.5 范围修订(用户批准,2026-08-02) - -前置 B 的不可信恢复(SQL/binary 双入口)缩围为**仅接受 user_version N 与 N−1** -(当前 16/17):迁移语义 invariant 家族四次复发均位于历史迁移链,其对抗深度与 -"导入远古备份"的产品价值长尾严重不匹配。v1–v15 输入结构化拒绝;**本地升级链 -v1→17 完全不受影响**(LocalUpgrade 就地迁移照旧);升级前备份(N−1)回滚路径 -保留并专测。v1–v15 恢复立项为"历史备份导入"独立未来工程,已建成的 -MigrationSourceSpec 架构留作其地基。 - -## 5. 冻结事实(2026-08-01) - -- 分支 `feat/pi-native-support`,HEAD = 10f2dacb(R4 检查点),工作树干净; -- R1–R4 已耗于已废止的单体认证;前置工程各持新的 2–3 轮预算; -- 未 push、无 PR;`stash@{0}`、`legacy/pi-041ff113`、PR #5598 不触碰。 diff --git a/docs/pi-support-review-contract-zh.md b/docs/pi-support-review-contract-zh.md deleted file mode 100644 index 1f4382cfb..000000000 --- a/docs/pi-support-review-contract-zh.md +++ /dev/null @@ -1,942 +0,0 @@ -# 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 时停止,不进行第八轮。 diff --git a/src-tauri/src/database/backup_restore_certification.rs b/src-tauri/src/database/backup_restore_certification.rs index 2784b3716..2a707207c 100644 --- a/src-tauri/src/database/backup_restore_certification.rs +++ b/src-tauri/src/database/backup_restore_certification.rs @@ -4,7 +4,7 @@ #![allow(clippy::type_complexity)] //! 前置工程 B:Canonical Restore 认证测试套件 v2(测试先行) //! -//! 规则与前置 A 完全一致(docs/pi-support-restructure-zh.md):实现方不得 +//! 规则与前置 A 完全一致:实现方不得 //! 修改本文件;异议上报裁决;全绿是盲审前置条件而非充分条件。 //! //! ## 与前置 A 的衔接(解冻声明) diff --git a/src-tauri/src/database/dao/provider_write_certification.rs b/src-tauri/src/database/dao/provider_write_certification.rs index c571f35c3..cae0b8c40 100644 --- a/src-tauri/src/database/dao/provider_write_certification.rs +++ b/src-tauri/src/database/dao/provider_write_certification.rs @@ -2,7 +2,7 @@ //! 前置工程 A:Provider 写面认证测试套件 v5(测试先行) //! //! 本文件是认证契约的可执行字面,固化 R1–R4 盲审与三轮对抗审查揭示的全部 -//! 写面故障场景。规则(见 docs/pi-support-restructure-zh.md): +//! 写面故障场景。规则: //! - 实现方不得修改本文件;认为某测试有误时,停止并上报裁决,不得绕过; //! - 全绿是进入前置 A 盲审的前置条件,但不是充分条件; //! - 对写面(provider_write.rs)新增任何函数、对 infra 三文件的任何改动、 @@ -820,7 +820,7 @@ fn certify_forbidden_symbols_are_zero_treewide() { #[test] fn certify_infra_files_stay_frozen_after_restore_closeout() { // infra 时序缺口的机械冻结。前置 B 已按用户裁决终止关账(2026-08-02, - // 见 docs/pi-support-restructure-zh.md §4.6 与 restore-hardening-debt-zh.md): + // 见 docs/restore-hardening-debt-zh.md): // 基线以 3fa6b1f1 状态重冻,**本 PR 内这三个文件不再有解冻窗口**, // 任何字节变更都使前置 A 失效并须回裁决方重审。 let root = source_root(); diff --git a/src-tauri/src/pi_config/native_inspection_certification.rs b/src-tauri/src/pi_config/native_inspection_certification.rs index fa919b5c3..4d0158d0d 100644 --- a/src-tauri/src/pi_config/native_inspection_certification.rs +++ b/src-tauri/src/pi_config/native_inspection_certification.rs @@ -56,7 +56,7 @@ //! MissingCredential);deferred 凭证判定期不可知,则**物化期解析出命中值 //! 时必须失败**,绝不发出错误的认证形态。 //! **完整 OAuth 传输(Bearer + oauth beta 值)不在前置 C 范围**——按 -//! docs/pi-support-restructure-zh.md §1,gateway 数据面属主工程,且需要 +//! 项目范围划分,gateway 数据面属主工程,且需要 //! 先补 request-capture oracle。本工程只保证判定诚实、不发错凭证。 //! C6【entry 隔离,2026-08-02 新增】pinned Pi 逐 entry 做 TypeBox 判定, //! 单个 entry 的取值错误(如 `contextWindow: 1e400`)只令该 entry 非法;