Files
Endoleap/headless/FINAL_ACCEPTANCE.md
T

103 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Headless Canvas:完整验收矩阵
2026-11-11。T01–T36 的实现与直接断言已逐项审计,当前版本的浏览器、存储、代理和270文件打包验收均已通过。本文随 SDK 打包,避免验收依据只存在于被忽略的研究 Draft 中。它记录已实现的第一版合同及证据范围,不表示代码已合并或生产环境已验证。
## 版本与复核入口
前端产品实现基线为 `15ee917`,此前包含 `b05058a` 的迁移数据校验修复;真实服务 consumer 固定为 `dcbbdad25de7c3b1d6bee317df6074da2c0642eb`。后续 `d3eef78` 和本次修改增加测试与交付文档,不修改产品实现。后端为 `506f778ac33d47fbd6f38412995a10218d59fe0a`。
后端现有 ces-control CI [#1186](https://gitea.entroia.com/Zayden-Jung/foundry-core/actions/runs/1186) 与 [#1187](https://gitea.entroia.com/Zayden-Jung/foundry-core/actions/runs/1187) 的七个阶段全部成功,包括实际认证 HTTP、PostgreSQL、资源与 RunPort consumer。两次报告全部功能标志为 true,生成执行次数均为 3。轮询次数随调度变化,不能用总 HTTP 次数代替“不重复受理”的独立断言。详见 [SERVICE_ACCEPTANCE.md](SERVICE_ACCEPTANCE.md)。
下面命令从 Endoleap 源码仓库根目录执行。Node 测试通过不自动替代浏览器或真实后端证据。用户允许本地前端编译与测试;后端编译、容器和数据库验收只在既有远程 CI 执行。
| 检查 | 命令 | 本次结果 |
|---|---|---|
| 全量源码单元/协议 fixture | `npm run test:headless` | 822 项通过,无失败或跳过 |
| 类型 | `npm run typecheck` | 通过 |
| 实际页面 webpack | `node tests/headless/renderer.integration.mjs` | 51 项通过 |
| 实际页面 Turbopack | `node tests/headless/renderer.integration.mjs --turbopack` | 51 项通过 |
| 公共 hooks / StrictMode | `node tests/headless/renderer.integration.mjs --hooks-only` | 10 项通过 |
| 实际 IndexedDB / Web Locks | `npm run test:headless:browser` | 57 组通过 |
| 实际 Next 代理 | `npm run test:headless:proxy` | 6 项通过 |
| 原编辑器升级交接 | `node tests/headless/renderer.integration.mjs --migration-only`,另加 `--turbopack` | webpack 与 Turbopack 各 5 项通过 |
| 独立解包、exports、类型、可选 peers、编译后浏览器/Worker | `npm run test:headless:package` | 270 文件解包、所有列出检查通过 |
## 逐项验收
负责组与 Draft 一致:A 模型/registry,B editor/history/交互,C runtime/同步,D storage/assets,E 导入/复制/loader,F bindings/renderer,G 后端/service。文件名默认位于 `tests/headless/`;后端 Go 用例位于 Foundry `internal/platform/`。每行“通过”限于列出的实现模式与断言,限制见后文。
| ID / 负责组 | 场景与实现边界 | 直接测试入口与已检查断言 |
|---|---|---|
| T01 A/B/E | 项目 ID 唯一;复制新 ID;恢复新 incarnation | `core.test.mjs`、`duplicate.test.mjs`;冲突拒绝、稳定映射、delete/undo 后旧 target 无效;实际页面旧编辑回调不能修改恢复对象。 |
| T02 A/B/F/G | 重挂/循环/解散 canvas | `dissolve.test.mjs` 六项;直属成员提升、入口保留、孙 canvas 仅改直接 parent、世界坐标/单位/曲线转换、声明引用重写、权限/opaque/partial 拒绝;真实 consumer 丢 ACK、原 key 恢复及 undo/redo。 |
| T03 A/G | 跨兄弟 canvas 的边归共同上级 | `core.test.mjs`、`queries.test.mjs` 与真实 owner 并发用例;边 owner 与端点 owner 分开验证,不能为存边篡改端点归属。 |
| T04 A/F | 未知 component/未来版本保留 | `core.test.mjs`、`renderers.test.mjs` 与实际页面 opaque 恢复;编辑其它内容不丢 payload,不把未知资源字段当成可加载图片。 |
| T05 A/F | 同模型多 renderer;重复 handler 拒绝 | `core.test.mjs`、`renderers.test.mjs`;两个宿主 catalog 投影同一 record,共享行为;同 capability 冲突和重复 component 注册明确失败。 |
| T06 A/B | schema/引用迁移失败零发布 | `core.test.mjs`;缺 converter、引用转换抛错、多步后续失败、schema 失败、非法 JSON、getter 单次捕获;原数据/身份/版本和 Root/history/ledger/index 保留。 |
| T07 A/B/C | 第二命令失败、async、重入 | `core.test.mjs`、`publication-guard.test.mjs`;dispatch/transaction/history/updateRuntime/reconcile 嵌套拒绝,不执行 reducer 或 observer;异步事务关闭后不能继续提交。 |
| T08 B/D | 双栈、新编辑清 redo、容量与 pin | `history-transition.test.mjs`、`resources.test.mjs`;no-op 不造历史,容量淘汰释放 pin,删除后的 undo 资产在历史保留期内不回收。 |
| T09 B/C | 同字段冲突与未知外部结果 | `conflicts.test.mjs`、`applied-recovery.test.mjs`、`engine.test.mjs`;冲突不提前移动栈;本地补偿可见,外部 unknown 先协调原 key,不冒称服务器确认。 |
| T10 B/D | upload ACK 后 Escape | `core.test.mjs`、`resources.test.mjs` 与实际页面;清 session preview,保留 committed stored ref,不用手势起点的旧整对象恢复图片。 |
| T11 B/F | pointerup 包含节流尾帧 | 实际 `renderer.integration.mjs` 的 resize/曲线原生输入;最终 300×220、曲线 440/265,一个 POST;core 对应一个 history 单元。 |
| T12 B/F | owner/权限/删除使手势失效 | `gesture-placement.test.mjs`、`pointer-capture.test.mjs`、core 与实际页面;祖先移动取消,迟到 pointerup/lost-capture 不复活或覆盖新坐标。 |
| T13 B/F/C | IME/Portal/detach/多 view | `input-router.test.mjs`、`session-sources.test.mjs`、bindings 与实际页面;输入只 claim 一层,旧 detach 不删除新 view,engine 任务独立于挂载生命周期。 |
| T14 C/D | 未发 create→rename→delete 合并 | `ledger.test.mjs`、`batch-projection.test.mjs`、`elision.test.mjs`;absent/exists/unknown 基线和完整 logical coverage,position-only 不回退 title;冻结/possibly-applied 不抵消。 |
| T15 C/G | create 已提交丢 ACK,随后 delete | `engine.test.mjs` 的 reopen 用例;submit→query 原 body/key→独立 delete,创建不再 POST,服务器无残留且两项历史保留;幂等服务合同另由真实 DB 覆盖。 |
| T16 C/G | accepted/cancel/failed/404 | `service.test.mjs`、`runs.test.mjs` 与真实 RunPort consumer;没有 terminal identity proof 不判零写入,取消/迟到 poll 保留控制 epoch,已取消 queued run 未调用生成。 |
| T17 C/D | 多 canvas claim 与跨标签 fencing | `scheduler.test.mjs`、实际 browser-storage/migration;全部参与者一起认领,unknown 保留 claim,过期 handle 不能写新 fence,shared legacy lease 排斥独占升级。 |
| T18 C | heap 到期、时钟变化、饥饿 | `scheduler.test.mjs`、`ledger.test.mjs`;wall/elapsed 分开,前跳/回退/重开剩余等待/旧 timer 回调,依赖和容量 lane 不被越过,无无界忙轮询。 |
| T19 C/G | ACK 缺失/乱序/后续 pending | `service.test.mjs`、`engine.test.mjs`、`loading-engine.test.mjs`;缺失/多余参与者与 logical coverage 拒绝;不发布半份基线,旧 ACK/tail 不清新编辑。 |
| T20 C/D/G | 旧冻结请求/receipt 过期 | `scheduler.test.mjs`、`applied-recovery.test.mjs`;无解释器阻塞而保留原 body/key,已有 applied proof 后 receipt 过期走 fresh read,不重发旧 POST。 |
| T21 C/D | Root/journal/文件提交失败 | `durability.test.mjs`、`engine.test.mjs` 与实际 IDB;quota 不虚报 durable、不发送,后续 put/delete 失败整事务回滚,旧 revision 的完成不能确认新 dirty 状态。 |
| T22 C/D/G | 全部声明的恢复窗口 | `crash-windows.test.mjs` 的六个切点场景;原逻辑 ID/body/key、历史、POST/query 次数及最终 durability;实际 IDB 另验 checksum/journal gap/fence,非物理 kill 全组合测试。 |
| T23 D/B/E | 图片复制/替换/删除/undo | `resources.test.mjs` 32 项、实际浏览器与真实服务;资源共享,replacement 阻塞仍保留旧 ref,undo 挡迟到替换,redo/reopen 不重复上传或更换元素 incarnation。 |
| T24 D/A | 索引滞后/opaque/历史保留 pin | `resource-pin-scan.test.mjs`、`asset-depot.test.mjs`、实际 IDB;working/ACK/history owners 独立,coverage 不足/过期 revision/未知引用拒绝永久回收,repin 重启计时。 |
| T25 E/F | 边解析/边加入/边显示及编辑 | `imports.test.mjs`、`imports-engine.test.mjs`、budget 系列与实际页面;源尾部阻塞时首记录已可编辑,后续导入不覆盖编辑;完整 SHA 未验证前不发送。 |
| T26 E/C | 第 101 项失败/取消 | `imports.test.mjs` 的 100 valid→bad JSON→valid tail→missing edge;101 个合法元素保留、两个明确失败、未闭合边不发;取消成功前缀可恢复且不自动继续。 |
| T27 E/A/G | 深复制 F 与内部 A 同选 | `duplicate.test.mjs`、`imports-engine.test.mjs` 与真实 consumer;深层树只复制一次,P 保留 C→F 并增 C→F′,资源复用,无新 run;字面字段不误做 ID 重写。 |
| T28 A/E | 旧格式/未知 envelope | `entry.test.mjs`、`import-document.test.mjs`;只走明确协商 fallback/codec,403 不回退,读取不隐式迁移,未知格式/版本拒绝,不猜 owner。 |
| T29 B/E | import 与手动编辑历史穿插 | `imports-engine.test.mjs` 新增 reopen 测试;导入前缀→手动改标题→导入尾批保持三个 undo 单元,依次撤销再重做全部三项,标题与内容均正确。 |
| T30 C/F | 回放/重试/observer 重挂 | ledger/scheduler、`loader.test.mjs`、`session-sources.test.mjs`、公共 StrictMode hooks;queued Set 与 ledger 同源,旧回调不删新句柄,异常 observer 不阻止其它观察者,无历史/任务重复。 |
| T31 C/D/F | logout/账号切换/升级/配额 | `model-loader.test.mjs`、host/workspace 用例、实际 IDB 与 logout 页面;新 runtime 的 account/workspace/authEpoch scope、retire fence、DB versionchange、dirty 风险可见。旧兼容编辑器备份 key 的范围见限制。 |
| T32 E/C | loader 尾块失败/查询变化/晚导航 | `loader.test.mjs`、`model-preview.test.mjs`、`model-loader.test.mjs` 与实际页面;partial 不冒充 complete,fingerprint/epoch 分离,旧请求即使无视 abort 也不能删或完成新请求。 |
| T33 A/F | rotate/细线/zoom/展示路径 | `hit-query.test.mjs`、geometry/connection paint 与实际页面;两级旋转和单位换算,zoom .1/1/4 的屏幕容差,Bezier 精判非直线近似,折叠端点展示与原引用分开。 |
| T34 A/F | 只改标题/只 ACK 的局部计算 | `queries.test.mjs`、`projection-cache.test.mjs`、`scene-projection.test.mjs` 与实际 renderer 计数;无关几何/节点投影复用,listener 看到同 revision 的模型与索引。 |
| T35 A/E/F | 停放 view/导出/引用递归 | `bindings.test.mjs`、`import-document.test.mjs` 与实际 toolbar 导出/重导入;detach 不关闭项目任务,导出从完整模型读取所有 canvas/elements/tombstones,不遍历 DOM。 |
| T36 G/C | 幂等/版本/跨图原子 | `TestCanonicalConcurrentMigrationAndOwnershipWrites`、`TestCanonicalModelMigrationAndIncrementalLifecycle`、迁移 guards/late-cancel 用例及真实 consumer;同 key 同 bytes 只一次版本推进,改 bytes 拒绝,竞争端点重挂只有一个赢,失败与 late cancel 整体回滚。 |
## 恢复切点:本地事实与服务事实匹配
不能拿任意旧 checkpoint 配任意服务状态,再从结果声称完整恢复。`crash-windows.test.mjs` 捕获实际 engine 产生的不可变保存图像,逐个搭配对应服务状态:
| 窗口 | 重开时必须保持的行为 |
|---|---|
| 可见编辑尚未 durable | 从最后 durable Root 重开;可能丢失该编辑;关闭前 dirty 状态不能声称已保存。 |
| pending 意图已 durable、尚未冻结 | 原 logical operation ID 保留;生成一次请求,不能重复 history。 |
| frozen 已 durable、尚未发送 | 原 body/key 保留;恢复可保守先 query,再原样 retry,不能换请求身份。 |
| dispatch-marked,服务尚未提交 | query 缺失不能当可靠零写入终态;按协议原 body/key retry。 |
| 服务已提交,本地 receipt 尚未 durable | query 原请求取回成功,不再 POST;之后才推进本地基线。 |
| receipt/基线已 durable | 重开不再执行或重复历史/副作用。 |
六种测试分别覆盖 Draft 的提交前、durable 意图、冻结/发送、服务提交、本地确认边界。文件字节、Root 与 journal 的实际事务回滚另由真实 IndexedDB 用例证明;AI、上传与删除依赖分别有身份/丢 ACK 用例。没有声称“每种任务 × 每个故障 × 每个浏览器”全部做了进程强杀测试。
## 分工与唯一事实源的交付边界
A/B 维护 immutable Registry、规范化文档、命令/事务、history 双栈;C/D 维护 ledger、由其派生的 ready 集合/调度队列和到期 heap,以及同一恢复 Root 和字节仓库;E 维护受该 Root 管理的导入/复制检查点、稳定映射与 loader 捕获;F 只拥有 ViewSession 和投影/输入绑定;G 负责授权、版本、幂等、跨图事务和可查询 receipt。不同组通过公共 exports、Change 与端口合同连接,不能再维护另一份业务文档或任务状态。
结构、包含关系、生命周期、集成入口和示例见随包 [README.md](README.md)。源码测试覆盖各组内部纯逻辑和端口故障;真实页面覆盖连线后的生命周期;真实 CI 覆盖前后端 wire/DB,三个证据层分别保留。
## 已知边界
- 第一版的 dissolve 保留原入口元素和原线,只解除其分区入口关系;公开 headless 命令已实现,没有新增产品按钮或另一种“删除入口”政策。
- 支持记录/依赖闭合后的渐进显示;完整源 SHA 和恢复身份未可靠保存前不得发请求。任意数据捕获、原子发布、native 与 GC 不能全部抢占。
- 性能验收是合作调度软目标。10,000-child 生产复制的首次副本呈现 784.1ms、完成帧约 27.95s、最大 long task 386ms;保留此限制,不能称为完全无卡顿或生产 SLA。环境、命令和原生中文输入记录见 [STEP4_ACCEPTANCE.md](STEP4_ACCEPTANCE.md)。本次没有产品性能代码变化,未重复该规模测量。
- 新 canonical runtime 和迁移记录按 account/workspace/authEpoch 隔离;旧兼容编辑器原有的本地草稿备份 key 仍按 workspace/board 范围。T31 不声称已把全部旧备份迁移为账号命名空间。
- 未知/未来版本的 payload 保留并限制编辑;不承诺自动理解所有未来 schema。无解释器的冻结请求明确阻塞,不重编码或换 key。
- 本地资产回收基于完整 pin/coverage;跨客户端永久服务 GC、完整 CRDT、多处 notebook 阅读模型仍属原稿后续范围。
- 远程 deterministic worker fixture 替换生成输出。实际授权、HTTP、DB、事件与文件流程经过验证,外部模型可用性、计费、CLI 认证和生产后台队列不在该证据范围,付费 real-worker 用例仍 opt-in。
- 包验收、工作分支推送、PR 可评审、人工合并、部署是不同状态。最终提交/推送状态以交付报告和 Git 为准;Foundry 等待用户手动合并,不以全绿测试声称已合并或部署。
## 最终交付状态(2026-11-11)
T01–T36矩阵、当前前端回归、远程服务验收及独立SDK消费检查完成,原七步实现/验收交付完成。前端验收提交 `14f7475` 已推送 `codex/headless-canvas-v1`;后续只有交付状态记录更新。前后端相对最新main的merge-tree演算无冲突;后端506f778状态检查success。Foundry [PR #19](https://gitea.entroia.com/Zayden-Jung/foundry-core/pulls/19)已由Draft转为可评审,API返回draft=false、mergeable=true,等待用户手动合并。前端遵循当前不新建PR的项目约定保留工作分支。两仓库均未由bot合并main或部署。