chore: checkpoint web Blender migration source, tools and docs
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
mes123456
2026-11-10 11:18:01 +08:00
parent 122142c26c
commit f3e21c6b98
6887 changed files with 257154 additions and 12294 deletions

View File

@@ -8,7 +8,7 @@ when every response, including errors and SPA fallbacks, preserves these headers
| `Cross-Origin-Opener-Policy` | `same-origin` |
| `Cross-Origin-Embedder-Policy` | `require-corp` |
| `Cross-Origin-Resource-Policy` | `same-origin` |
| `Content-Security-Policy` | `default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; worker-src 'self'; connect-src 'self'; font-src 'self'; img-src 'self'; media-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'` |
| `Content-Security-Policy` | `default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; worker-src 'self'; connect-src 'self'; font-src 'self'; img-src 'self' blob:; media-src 'self' blob:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'` |
Production uses HTTPS. Loopback development may use `http://127.0.0.1`; `file://` is unsupported.
Runtime assets are same-origin. A reverse proxy must not strip the isolation, range, cache, MIME or

View File

@@ -0,0 +1,16 @@
# B0:可信基线与当前证据
运行 `node tools/web/evaluate-wbv2-completion.mjs --case-manifest <案例manifest>`。不提供案例参数时只评估仓库已有输入;不会补造案例通过记录。
`tests/golden/WBV2/completion-baseline-B0.json` 保存升级前的原始评估,创建后不覆盖。最新 JSON/Markdown 位于 `completion-evaluation.*`;其 `baseline.path` 指向按内容 hash 保存的不可变基线。相同输入的 `inputSummarySha256` 与基线文件 hash 必须相同,生成时间不参与摘要。
案例清单参数和实际文件观察均先规范化路径,因此同一文件使用相对或绝对路径不会改变输入摘要。
当前分母取目录与 inventory 的单元 ID 并集;遗漏单元仍在分母和残余中。原动态覆盖快照保留为历史,旧分母警告不会被隐藏;当前 `coverage.reconciliation` 给出两侧集合和统一分母。
基线分别记录历史 VERIFIED、当前引用匹配、当前源码版本匹配。两个证据引用与源码版本都有效时才计作当前行为覆盖。漂移、缺失、未绑定和源码变化逐项保留,不能通过改 hash 或删缺口恢复通过。
源码 hash 复用 inventory 生成器的算法,范围明确为 inventory 声明的源文件;这里不新增全库 inventory,也不宣称覆盖了未登记文件。原 inventory hash、当前范围 hash 和变化文件列表同时保存。
所选案例只有在真实数值类型、进程完成、输入/产物 hash 都有效时才记录检查通过;不自动升级批次 acceptance。全局分数采用当前绑定条件,原评分仍可读,新旧评分不能直接解释为功能退步或进步。
读取期间或发布前发现输入变化会中止发布。JSON、Markdown 和基线产物使用原子写入;失败时先定位具体变化文件,保留现有证据,不盲目重跑下游。

View File

@@ -2,6 +2,7 @@
"schemaVersion": 1,
"product": "Web Blender Modeler V1",
"browserScope": "Chromium",
"privateDeployment": { "scope": "current Linux host, single authorized user", "usernameEnvironment": "WEB_BLENDER_USERNAME", "defaultUsername": "owner", "passwordEnvironment": "WEB_BLENDER_PASSWORD", "authentication": "HTTP Basic over HTTPS", "productionIngress": "HTTPS reverse proxy preserving Host and Authorization; HTTP listener remains loopback by default", "unauthenticatedExternalBinding": "reject", "authenticatedCacheControl": "private, no-store", "crossOriginMutation": "reject", "validation": "NOT_RUN", "acceptance": "NOT_CLAIMED" },
"transport": {
"production": "https",
"localDevelopment": "http://127.0.0.1",
@@ -9,12 +10,19 @@
"sameOriginRuntimeAssets": true
},
"methods": ["GET", "HEAD"],
"serverOperator": {
"path": "/api/server-operator-job",
"methods": ["GET", "POST"],
"actions": { "describe": "POST", "submit": "POST", "read": "GET|POST", "cancel": "POST" },
"cacheControl": "no-store",
"configuration": ["BLENDER_BIN", "PYTHON_BIN", "SERVER_OPERATOR_WORKSPACE_ROOT"]
},
"responseHeaders": {
"allResponses": {
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "require-corp",
"Cross-Origin-Resource-Policy": "same-origin",
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; worker-src 'self'; connect-src 'self'; font-src 'self'; img-src 'self'; media-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'"
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; worker-src 'self'; connect-src 'self'; font-src 'self'; img-src 'self' blob:; media-src 'self' blob:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'"
},
"routes": [
{

View File

@@ -3,7 +3,7 @@
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "blender-web-editor-sbom",
"documentNamespace": "https://blender-web.local/spdx/5a2256c6a1d20a2a771eeca379c81a0dbad314d941de452b7e301e7daaae36e9",
"documentNamespace": "https://blender-web.local/spdx/9b3d83dd2e26952b7e23ed5b2026dcb2b5ebb226f1584a86746bbfdde3167bfe",
"creationInfo": {
"created": "1970-01-01T00:00:00Z",
"creators": [
@@ -1348,6 +1348,29 @@
}
]
},
{
"SPDXID": "SPDXRef-npm-node-modules--webgpu-glslang-0d1429650c2b",
"name": "@webgpu/glslang",
"versionInfo": "0.0.15",
"downloadLocation": "https://registry.npmjs.org/@webgpu/glslang/-/glslang-0.0.15.tgz",
"filesAnalyzed": false,
"licenseConcluded": "NOASSERTION",
"licenseDeclared": "NOASSERTION",
"copyrightText": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:npm/%40webgpu%2Fglslang@0.0.15"
}
],
"checksums": [
{
"algorithm": "SHA512",
"checksumValue": "9e24fe3eb87701f7fc51fd4c541554b1a363163f6b2402835eea0720a8906c1fba2143ffdc9dc922104dc99ee50e1cadbd7c70ea9a609f0299743274f14323e1"
}
]
},
{
"SPDXID": "SPDXRef-npm-node-modules-acorn-2126d38c42e9",
"name": "acorn",
@@ -2659,6 +2682,29 @@
}
]
},
{
"SPDXID": "SPDXRef-npm-node-modules-naga-wasm-932e3101d463",
"name": "naga-wasm",
"versionInfo": "30.1.0",
"downloadLocation": "https://registry.npmjs.org/naga-wasm/-/naga-wasm-30.1.0.tgz",
"filesAnalyzed": false,
"licenseConcluded": "NOASSERTION",
"licenseDeclared": "NOASSERTION",
"copyrightText": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:npm/naga-wasm@30.1.0"
}
],
"checksums": [
{
"algorithm": "SHA512",
"checksumValue": "b9138a4651cfeb564805a6f4ced04814df1d5a5a039be7dd80ef9c83903b9bb28ae741de1c40a25740b61ef5756be5cd6221d5419e9557b1181966d6ecb4b752"
}
]
},
{
"SPDXID": "SPDXRef-npm-node-modules-nanoid-2e287a9dc6ed",
"name": "nanoid",
@@ -3430,7 +3476,7 @@
"checksums": [
{
"algorithm": "SHA256",
"checksumValue": "0f7efd3052b12d495a5dfd514e701b2ea48db1e0b8f1b651ae63ed94786f0cf4"
"checksumValue": "ff12f7b4cfc83fe2cfbb86ed9fa4af6d0eeb8053fdf0cc5039d5d30519df058e"
}
]
},
@@ -3803,6 +3849,11 @@
"relationshipType": "DEPENDS_ON",
"relatedSpdxElement": "SPDXRef-npm-node-modules--vitejs-plugin-react-b9446a9ed237"
},
{
"spdxElementId": "SPDXRef-Package-blender-web-editor",
"relationshipType": "DEPENDS_ON",
"relatedSpdxElement": "SPDXRef-npm-node-modules--webgpu-glslang-0d1429650c2b"
},
{
"spdxElementId": "SPDXRef-Package-blender-web-editor",
"relationshipType": "DEPENDS_ON",
@@ -4088,6 +4139,11 @@
"relationshipType": "DEPENDS_ON",
"relatedSpdxElement": "SPDXRef-npm-node-modules-ms-1ac579b29d00"
},
{
"spdxElementId": "SPDXRef-Package-blender-web-editor",
"relationshipType": "DEPENDS_ON",
"relatedSpdxElement": "SPDXRef-npm-node-modules-naga-wasm-932e3101d463"
},
{
"spdxElementId": "SPDXRef-Package-blender-web-editor",
"relationshipType": "DEPENDS_ON",

View File

@@ -0,0 +1,41 @@
# 可恢复的案例验收工具
入口复用 B1 的真实 Playwright 用例、原生 session、Vite 隔离头和候选 WASM。它不编译、不下载浏览器、不推进队列。运行环境需要 Linux `flock`、本机 Chromium、现有 Node 依赖;B1 还需要 Blender 可执行文件。
```bash
node tools/web/run-workflow-case.mjs --plan tools/web/workflow-cases/B1.json --candidate build_web_main_desktop/b1-context-readback-20261023 --out tests/golden/WBV2-P1424-001/my-b1-case --port 5288 --packaged
```
- `--check` 只检查本地构件、可执行程序和两个 fixture;不启动服务,不代表 HTTP 或业务通过。
- `--production <file.blend>` 替换生产片段;`--blender <path>`、`CHROME_PATH` 指定本机程序。
- `--packaged` 验证候选的实际 `app` 页面;不加此参数时使用当前前端源码和指定原生构件。两种模式及打包文件分别绑定输入身份,不互相冒充验收证据。
- 原命令可直接续跑:输入、浏览器版本和产物 hash 一致的 PASS 自动复用;失败后的相同输入必须先查看原因,再显式使用 `--retry-failed`。
- 每个 fixture 使用独立 `attempt-*` 目录,保存命令、状态、HTTP hash、Playwright JSON、日志、截图、trace 和下载文件。不要把需长期保留的数据放在 `web/test-results`。
- 旧进程仍活着时拒绝重复运行;进程已经退出的中断案例保留旧文件,从新浏览器和原始 fixture 开始该案例。成功的前置案例不会随之重跑。
- 顺序固定为 candidate/资源 → 本机运行环境 → 服务/HTTP 隔离与真实 JS/WASM hash → fixture 操作;最小案例失败即停止生产案例。
- `PASS_SELECTED_TESTS` 仅表示配置选中的断言通过。`remainingGates` 保留未覆盖项目,`acceptance` 始终是 `NOT_CLAIMED`;仍由项目评估器和完整案例门禁裁定验收。
- 实际案例批次结束后会自动调用 `evaluate-wbv2-completion.mjs --case-manifest <本批manifest>`;失败也会记录,日志在 `evaluation.log`。评估失败时保留案例产物并返回失败码,不能当作进度已发布。
## 扩展 B2–B6
B2 已提供 `tools/web/workflow-cases/B2.json`,覆盖现有 Canvas 确认/取消断言,其余门禁保持显式未覆盖。B3–B6 复制此配置,设置批次、业务描述、实际 operator/RNA/Main 源码路径与当前用例选择器;保留一份最小 fixture 和一份真实生产片段。
用例必须从 `process.env.WBV2_CASE_FIXTURE` 读取输入,以 `testInfo.outputPath()` 保存输出。复用 `web/tests/fixtures/native-ui-workflow.ts` 观察 Worker 请求、读回和操作耗时;用源码定义的业务字段做断言。新增配置不会自动扩大验收范围,也不能用 `scope` 文案代替断言。
重开脏工程时使用 `resolveProjectReplacement(page, 'cancel' | 'discard' | 'save')` 操作真实 React 对话框;浏览器 `dialog` 事件不能代替此步骤。重开后必须等到新的 Worker snapshot,再比较 Main 值,不能仅匹配原本就存在的 `SceneIR` 文本。
Console 使用 `console-main-workflow.ts` 的最新结构化回执和递增序号;避免在累积输出中搜索旧坐标。对象名来自实际 fixture,先检查 location/translation 为有限数值三元组,再按明确容差比较 native/WASM 读回。
原生 WASM Console 复用 `wasm-console-workflow.ts`:先等新 `pythonExec` 请求(10 秒),再按同一 requestId 等结构化返回(60 秒);前端明确报错立即失败。`switchNativeWorkspace` 验证实际 Main 的 workspaceId 后才继续操作,禁止把按钮 click 完成当作原生切换完成。失败时用 `captureNativeWorkflowState` 保存最近请求、响应、编辑器上下文及 Console 输入/状态,不重放失败命令。
属性读回先用 `showNativeObjectProperties` 准备对象上下文,再用 `expectUILocation` 检查实际 HTML 数字输入的 `valueAsNumber`、有限性和容差;Worker 坐标或 UI revision 不能替代可见字段。`performNativeAction` 按动作/操作/上下文匹配新请求并绑定回复,失败直接报告原生错误;观察器保留有界 `errorDetail` 堆栈,避免只剩笼统错误码。
评估器输出 `currentCasesEligible` 表示本次案例的当前 hash 绑定通过数量;`workflow` 是原有整体工作流评估,不能把本次两案例通过等同于全局通过。
计划可用 `sourceReview` 绑定经阅读确认的 operator/RNA/Main 契约。执行前检查每个源码/用例绑定 hash、契约来源和数据类型,漂移立即阻断;评审文件及绑定文件一并进入案例输入摘要。该记录只覆盖声明的案例行为,`verifiedUnitDelta` 固定为 0,不能据此增加整单元 parity 数量。数值 UI 断言使用 `spinbutton` 定位真实输入,预期值对应 RNA 的局部 location。
原生 Canvas 案例继承共享 Chromium 的 WebGPU 参数;画布出现重试告警即停止依赖步骤。`native-modeling-workflow.ts` 观察真实模态起止并读取 bpy/BMesh;取消比较数据和撤销历史,帧 revision 可以因预览/回滚递增。`captureNativeWorkflowState` 同时保存有界浏览器错误。
序列化差异可用 `compare-blend-id-fields.py before.blend after.blend --output report.json`;依赖 Blender 自带读取器及 python3-zstandard,可用 `--dependency-path` 指向本地依赖。工具按实际 DNAStruct 解析 ID 字段,报告块数量及输入 hash;DATA 地址不作为语义身份,结果不等于完整 Main 相等证明。
fixture、源码、公共资源、用例、共享辅助代码、依赖锁、工具、浏览器版本任一变化,旧 PASS 不复用。需要新增外部依赖时,把相应实现/数据路径加入 `sourcePaths`。服务使用新实例和严格端口,避免连接旧候选。
## 先确认真实类型,再比较
Playwright 的 `stats.expected` 等字段是非负整数,`results[].status` 是字符串,`suites/specs/tests/results` 是数组。工具逐层检查类型;`"1"` 不会被当成 `1`,空报告、跳过、预期失败和重试不能算通过。
Main 坐标先检查 `number` 与有限值,再按源码行为选择精确比较或明确容差;不能对所有浮点结果默认四舍五入。DOM 属性返回 `string | null`,版本号保持字符串比较,缺失属性应报错。字节数据识别为 Buffer/ArrayBuffer 后按字节或 SHA-256 比较。结构化状态使用深严格比较或显式业务字段比较,不用字符串包含关系判断 Main 是否恢复。
工具测试:`node --test web/tests/unit/workflow-case.test.mjs`。这组测试验证工具的失败边界,不替代 Blender 功能验收。