Decisions 架构
decisions-client 与 decisions-hello 设计边界;Host 鉴权未知
日期:2026-11-07。状态:设计提案;M1 客户端契约草稿可评审,未完成服务端兼容验证。本文建立在 PR #5 的 docs/frontend-stack.md 之上;脚手架、adapter、示例 App、付费探针、真实 Host 验收均未执行。
1. 核心决策与统一边界
判断层放在独立的 packages/decisions-client(拟包名 @frevana/decisions-client),与 packages/host-sdk 并列。它负责官方 Decisions 形状的运行时校验与 Frevana 薄传输适配,不含 React、Host bridge、业务阈值、Agent 编排或持久化。App 组装 Host 能力、判断请求和业务门控。包独立是为了隔离两个不同的外部契约,并允许浏览器与后续受信执行环境复用同一验证器;不是预建通用 AI 平台。
保留第一 App apps/host-hello:只有 Button + getRootPath、无 Job。第二 App 使用 apps/decisions-hello:同一前端栈与交付工具,演示文本 noul 门控,choice 可选。host-hello 证明 UI、bridge 和资源交付基础;decisions-hello 在同一基础上增加认证传输与判断语义。它们分开验收、分开回退,避免将 API 认证故障混入第一轮 Host 最小测试。
实际推理唯一目标为 POST https://api.frevana.com/openrouter/v1/decisions。公开 OpenRouter catalog 只用于开发阶段选模、pin 和记录能力,不接收 Frevana Bearer 或业务 state;不提供直连 OpenRouter 的推理 fallback。
2. 系统图:运行与构建是一条闭环
图中两条传输路径是待选择方案,不能同时静默尝试;虚线不是已存在的 Host 接口。前端自包含约束适用于交付资源,调用远端 Frevana 业务 API 不等于从 CDN 加载运行时依赖。
3. 包边界、依赖与接口草案
| 路径 | 所有权 | 允许依赖 / 禁止承担 |
|---|---|---|
packages/ui/src/components/button.tsx、样式与 exports | 基础组件 | React/Base UI;不依赖 Host、decisions 或业务 |
packages/host-sdk/src/app.ts | 已有目标 Host 契约 | 首轮仅 getRootPath;不因 decisions 猜测 auth API |
packages/decisions-client/src/types.ts、validation.ts | 官方 Decisions 形状及本地严格子集 | JSON 数据和纯校验;不依赖 UI/Host/Node |
packages/decisions-client/src/client.ts、transport.ts、errors.ts | Frevana 单 endpoint adapter | 校验、超时/取消、头映射、错误归类;不做 catalog 自动选模、业务重试或阈值 |
apps/decisions-hello/web/src/decisions/policy.ts | 业务判断 | questions、criteria、命名阈值、完整真值表一起维护 |
apps/decisions-hello/web/src/runtime/create-transport.ts | 运行环境组合点 | 仅绑定已验证的认证机制;无机制时明确 unavailable |
tooling/decisions/catalog.ts、probe.ts | 开发时选模与探针 | 显式候选和预算;catalog 可公开读取,推理仍只到 Frevana |
tooling/app-build/、.build/<id>/、apps/<id>/skillpack/ | 构建与交付 | 复用 #5 资源清单和逐文件提升,不另建运行时共享服务 |
源码依赖为 App → ui / host-sdk / decisions-client;共享包互不依赖 App,decisions-client 与 host-sdk 互不导入。认证能力将来若成为多个 App 的已证需求,再单独评审 host-sdk 扩展,本任务不改变其首轮范围。
建议应用层接口(全是拟议本地类型,不是 Host API):
// client 内固定 Frevana origin/path;transport 不能由表单指定任意 URL。
interface DecisionTransport {
postDecisions(input: {
body: DecisionsRequest;
attribution?: Attribution;
signal?: AbortSignal;
}): Promise<{ status: number; body: unknown }>;
}
// createDecisionsClient({ transport }).decide(request, { attribution, signal })
// 验证 request → 传输 → 验证 response 与问题键/type → 返回 typed response。归因对象和 signal 在调用选项中,不塞进官方请求 body。transport 在受信侧添加 Authorization;浏览器直发方案仅在安全凭据来源确认后实现,短期 JWT 不写 Vite env、bundle、localStorage、日志或 SkillPack。生产中没有 mock,也没有自动降级到无鉴权或 OpenRouter 推理。
client 将 ValidationError、AuthUnavailable、HttpError(status)、NetworkError、Timeout、Cancelled、ProtocolError 区分;这些是本地类型,不是 Frevana error schema。401/403 不无限重放,429/5xx 保留状态供用户重试;默认不自动重试付费 POST,timeout/abort 不能推断上游未执行或未计费。无已声明幂等语义,不造 idempotency 字段。只返回校验成功的回答,错误绝不补成 noul=0 或“通过”。
4. 鉴权:目前的答案与探测顺序
已知 Frevana endpoint 要求用户/会话 JWT Bearer,不是 OpenRouter key。Host 文档有 auth shim 注入步骤及 frevana-auth-request 分发说明,但未给出本设计可依赖的取 token 签名、代理请求签名、scope 或刷新语义。Bearer 是否可通过公开 Host API 获得 = UNKNOWN;是否必须经 Host 代理 = UNKNOWN。 getRootPath 不提供实例身份或凭据。
首选能力是 Host 在受信侧发请求并添加 Bearer,避免将凭据暴露给页面;这只是架构偏好,不宣称已有此能力。备选为 Host 已明确公开、范围受控的会话凭据 + 浏览器 fetch;必须证明实际 iframe origin、CSP connect-src、CORS/preflight、Authorization 和全部归因头允许通过。都不满足时,M2 只能完成纯客户端和 mock transport 测试,M3 在线集成保持阻塞,交由 Host owner 确认受信接口;不在 App 临时增加裸 token 输入框。
探测步骤(后续执行,不属于本轮已验证结果):
- 只读核对
AppHtmlPreview、auth shim、frevana-auth-requesthandler 的公开方法与消息校验:source/origin、当前项目绑定、endpoint allowlist、凭据生命周期、scope。先取得 Host owner 提供的源码路径或说明,不读账号存储。 - 记录 Host 版本与受支持方法签名;只检查方法存在性和返回类型,凭据本身不打印。实例/run/channel 身份从已核对的受信上下文获取,不能用根路径、仓库名或编造 UUID 替代实例 ID。
- 使用独立隔离 App 验证选择的传输机制。若可直发,先做不带凭据与 state 的 OPTIONS/CORS 能力检查;它只能证明网络预检,不能证明推理、鉴权成功。不得用一个“空 POST”当免费探测。
- 接口与资源能力有证据后,再另行授权一次有预算的最小 Frevana decisions 请求,记录脱敏 request/response 形状、resolved model、归因效果;本轮不做此请求。刷新、退出、过期和跨项目拒绝单独验证。
5. 最小 App 与官方七步的落地
演示判断为“文本是否请求退款”,只显示分类及建议队列,不实际退款、不执行 Agent、不配置 Job。日期、金额、空输入与长度判断留在代码;空/离题与故障各有明确显示。最小 state 只有 message;退款政策咨询、否定退款、到账查询写入排除 criteria。可选 choice 判断所属 payments/account 队列,仅在存在匹配的 noul 为 yes 时读取;候选可能无匹配时另设 presence noul。
| 官方步骤 | 交付内容 |
|---|---|
| 1 拆判断与计算 | policy.ts 列出确定规则与模型判断;空输入直接本地返回 |
| 2 选 primitive | 首轮 noul;互斥才 choice、有序才 score、定位才 box,后两者仅契约覆盖 |
| 3 最小 state | builder 仅挑选问题需要的命名字段;不上传根路径、身份凭据或整份 App 状态 |
| 4 questions | instructions 问事实,criteria 写包含/排除,问题不依赖其它问题的答案 |
| 5 选模/调用 | 开发时公开 catalog 快照、pin;运行时只调用 Frevana |
| 6 代码门控 | 原始概率和命名阈值并存,fallback 为独立分支;没有返回值就是错误 |
| 7 探针后再信阈值 | 有标签样本、原始回答、模型版本、延迟、成本和阈值来源;本轮无实测数值 |
初始演示阈值可设 REFUND_YES_MIN = 0.7、REFUND_NO_MAX = 0.3,明确为未校准示例:p≥0.7 显示退款请求,p≤0.3 显示其它请求,中间显示需人工判断。假阳性会误分流,假阴性会漏分流,均不能触发付款。不是模型正确率或授权界限。探针必须覆盖两个边界和中间区,最终数值由测量冻结;不能拿示例值关闭 M4。
UI 状态:idle → validating → submitting → success / fallback / error;missing auth 为 unavailable,abort 为 cancelled。用户点击触发,提交中禁重入,卸载/隐藏按实际交互取消或停止更新,忽略旧请求回包;不在 mount effect 发付费请求,避免 StrictMode 重复提交。显示 raw noul、分流结果、resolved model 与未校准标记,避免给 confidence 赋予正确率含义。
6. Catalog、模型 pin 与可追踪门控
开发工具读取 GET https://openrouter.ai/api/v1/models?output_modalities=decisions,本轮不运行查询、不选具体生产模型。筛选输出模态、上下文与 provider 上限、价格和可用性,保存带时间戳的原始快照。canonical_slug 或确认版本化 id 写入 apps/decisions-hello/web/src/decisions/model-pin.json;禁止 alias(不只检查 ~,还核对 alias_target 与 canonical_slug)。没有有效 pin 时禁用在线提交,不悄悄选最新模型。
公开 catalog 中存在不证明 Frevana 账户能调用。探针记录 requested pin 与 response.model,不符合预期则显示 ModelMismatch 并停止业务门控;不靠静默接受版本变化恢复服务。更换模型、criteria、state builder 或门控含义均重新探针。探针配置必须带显式候选 allowlist、次数/并发/预算上限,禁止原样运行官方 --compare 扫过所有模型。
7. 与现有文档的增量和合入顺序
当前状态(2026-11-07):#4 与 #5 已合入 main;本设计正文已入库为 docs/decisions-architecture.md,配套契约为 OpenRouter Decisions 契约。入门阅读路径与 docs-site 投影已接通。后续仍建议的小幅增量:
| 文件 | 增量(尚未全部落地) |
|---|---|
docs/frontend-stack.md | 已加 decisions 入口链接;继续保留 host-hello 首轮范围和全部 UNKNOWN 措辞 |
docs/architecture.md | 可在前端交付段之后补判断层与认证传输边界,链接契约;不重写历史证据图 |
docs/monorepo-layout.md | 列出 decisions-client、第二 App、开发工具,标为目标路径 |
docs/index.md | 已增加判断层阅读入口 |
docs-site/ | M1 相关页已按现有 sync-docs.mjs 投影;后续正文变更继续走 sync |
不改 delivery-loop 的逐文件规则,不改任何 video-editor 代码/Job/skills/状态、不引入其它官方 skills、不重新开启 Vibe Editing。
推荐实现顺序:M1 文档已入库 → 分别实现 host-hello 与 M2;M3 依赖 host-hello 基础验收及认证证据;M4 在线探针晚于可用的 Frevana 传输;M5 汇总代码和验收文档。
8. M1–M5 文件级实施计划与退出门槛
| 里程碑 | 建议文件 | 退出门槛与依赖 |
|---|---|---|
| M1 契约冻结 | docs/openrouter-decisions.md、docs/decisions-architecture.md;合法/非法 fixture 暂留文档 | 明确官方/Frevana/本地策略三种来源;评审两例、四 primitive、认证 UNKNOWN;只能冻结客户端子集,不能宣布服务器已兼容 |
| M2 薄 adapter | packages/decisions-client/package.json、src/{types,validation,transport,client,errors,index}.ts、test/{validation,client}.test.ts | 复用或改编官方校验;fetch/Host 传输注入;模拟测试覆盖缺答案、type 错、越界、401、429、超时取消与无重试;无 OpenRouter 推理路径 |
| M3 最小 App | #5 的 workspace/ui/host-sdk/build 基础;apps/decisions-hello/web/{package.json,index.html,vite.config.ts,tsconfig.json}、src/{main,App}.tsx、src/runtime/create-transport.ts、src/decisions/{policy.ts,model-pin.json} | host-hello 单独验收;第二 App 无 Job;已证认证后才在线;生产资源清单、srcdoc 浏览器冒烟和真实 Host 分开记录 |
| M4 探针与门控 | tooling/decisions/{catalog,probe}.ts、apps/decisions-hello/probes/cases.jsonl、src/decisions/policy.test.ts、specs/decisions-hello/verification.md | 离线真值表/边界测试通过;获授权后仅 Frevana 预算探针;明确/模糊/无匹配/空或离题/否定/攻击样本及留出集;阈值可追溯,未运行就保留 OPEN |
| M5 文档与交付凭据 | 上述 docs 入口、apps/decisions-hello/skillpack/ 审查产物;.build/decisions-hello/ 临时凭据 | npm ci/typecheck/build、清单与 SHA、复现检查、独立 Host 身份和资源证据、白名单提升及回退;文档不将 mock 或文件落盘算在线验收 |
适配官方脚本时仅摘取 decisions 所需代码,记录上游 URL、固定 revision/摘要和 license;不整仓 vendoring,不保留官方直连 endpoint 或 requireApiKey 默认行为。当前在线浏览只核对脚本结构,未固定上游 commit,实施时补齐来源锁定。
9. 风险、UNKNOWN 与否决项
| 编号 | 当前缺口 | 关闭证据 / 失败处理 |
|---|---|---|
| D01 | Frevana body/response 无 schema | 经授权的脱敏请求/响应与 server owner 确认;不兼容就阻塞,不能自动当 chat |
| D02 | auth shim 可调用方法、scope、刷新、代理未知 | Host 公开签名/实现与受控验证;无合法传输则 unavailable |
| D03 | iframe 资源基址、sandbox/CSP、外置资源/module/dynamic import 未知 | 同一资源清单在真实 Host 的请求和执行证据;内联只是可选策略 |
| D04 | API 的 CORS/connect-src 与归因头未知 | 真实 iframe 网络证据;浏览器开发服成功不能替代 |
| D05 | Frevana 模型可用性、版本 pin 是否保持未知 | 公开 catalog + Frevana 回答 model;不漂移、无 alias 回退 |
| D06 | 计费/错误体/幂等未声明 | 保留 HTTP 状态,错误体脱敏,不自动重试;usage.cost 不能当 Frevana 账单 |
| D07 | 概率不等于正确率、输入可攻击 | 有标注探针、留出集与人工 fallback;不用于不可逆操作授权 |
| D08 | instance/run 身份来源与归因持久化未知 | 受信上下文及归因记录核验;不造实例或 Job 身份 |
| D09 | 历史 U02/U07 未关 | 本任务不关闭、不借此维护历史 App;新 App 如需清单加载另验 |
否决交付:凭空发明 Host auth/fetch API、把 token 打进前端、将 Frevana JWT 发给 OpenRouter、推理 fallback 到官方域名、使用漂移 alias、丢答案补默认、未经探针称阈值可信、宣称真实 Host 已支持或禁止外置 CSS/JS、整目录覆盖运行实例、重启 Vibe Editing、修改 ai-video-editor 或扩展其它官方 skills。
10. 依据
本地快照:context 中的官方 SKILL、decisions-api、question-design、models、decision-model-limits;docs/frontend-stack.md 与 context 副本 SHA 一致;Frevana 完整 OpenAPI 与 slice 一致。Host 依据为仓库外 app-html-host-api-architecture.md,仅文档证据。
官方脚本只读核对(2026-11-07,相对浮动 main,revision 未 pin;实施 M2 时须锁 commit/license):lib.ts、decide.ts、models.ts。具体契约与样例见 OpenRouter Decisions 契约。
冻结依据快照(可追溯,不含凭据)
| 依据 | 标识 |
|---|---|
| Owner 本地官方 skill 快照目录 | /tmp/codex-arch-decisions/context/,打包日 2026-11-07 |
openrouter-decisions-SKILL.md | sha256 32e7f139e70b…0b65fc |
decisions-api.md | sha256 b9dba95c2a2a…9d1071d |
question-design.md | sha256 046243042511…2d17c2 |
models.md | sha256 64f6083b79cb…8e24b39 |
decision-model-limits.md | sha256 7981f8a7cb63…7495804 |
| Frevana decisions OpenAPI slice | sha256 6a3a1e355221…fe8b219(path=/openrouter/v1/decisions,无 requestBody schema) |
| Frevana 完整 OpenAPI 本地副本 | .tmp-verify/frevana-openapi-full.json,sha256 前缀 454d5f6fe702bff8248f(本轮未 Basic 登录重拉) |