docs(web): publish detailed feature completeness audit
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

This commit is contained in:
mes123456
2026-09-07 08:41:30 +08:00
parent b088b792a8
commit e3677bd2a1
4 changed files with 321 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
# Blender Web 功能移植完整盘点
机器可读结果见 [`feature-completeness-audit.json`](../tests/golden/WBV2/feature-completeness-audit.json)。审计把“实现完成”“路由接线”和“行为证据”分开统计。
## 当前结论
- 产品工作流:16/16 模块、34/34 产品任务已实现。
- 源码路由:3745/3745 单元已分类,`UNAVAILABLE=0`。
- 路由分布:WEB_MAIN=1698、SERVER_NATIVE=1613、BROWSER_EQUIVALENT=434。
- 行为证据:1025 已验证、2720 未逐项验证。未验证不等于缺少入口,但不能宣称逐单元完成。
- 一次性烟测:34/34 已执行且没有重试;31 个初始通过,2 个定点修复通过,1 个 IO 构件问题仍阻断。
## 真正缺失或阻断
1. **IO WASM 构件阻断**:`importScene/exportScene` 已存在于 C++/协议/Worker/前端接线,但签入 WASM 不包含该 unified JSON 路由。重建时发现 WebEngine 源码仍有 Blender API/header 漂移,未替换现有构件。
2. **行为证据缺口**:1934 个 operator、673 个 RNA struct、58 个 operator macro、2 个 modifier source 尚无精确行为 receipt。它们已有明确 WEB_MAIN、SERVER_NATIVE 或 BROWSER_EQUIVALENT 路由,不应伪造为已逐项验证。
## 后续业务顺序
1. 修复 WebEngine 源码/API 漂移,重建并 SHA-256 绑定 source-matched WASM。
2. 仅重跑 `OPERATOR-ROUTE-PALETTE` 的 IO focused smoke;已通过的 33 个功能不重跑。
3. 按 operator/RNA/macro/modifier 家族建立批次化行为证据;每批复用已有 fixture 和 WASM,增量更新 verified overlay。
4. 用户报告缺陷时建立新聚焦任务,不修改历史通过证据。
禁止事项:不以 JS 模拟替代 Blender Main/WASM,不把路由数量当作行为完成率,不重复执行已通过的烟测。

View File

@@ -149,6 +149,7 @@ def project_progress() -> dict:
route_catalog = _read_json(ROOT / "web/app/src/capabilities/blender-feature-route-catalog.json")
product_backlog = _read_json(CONTROL_ROOT / "web-product-backlog.json")
product_status = _read_json(CONTROL_ROOT / "web-product-migration-status.json")
completeness_audit = _read_json(ROOT / "tests/golden/WBV2/feature-completeness-audit.json")
total = int(inventory.get("summary", {}).get("unitCount", 0) or 0)
verified = int(inventory.get("coverage", {}).get("verifiedUnits", 0) or 0)
unverified = int(inventory.get("coverage", {}).get("unverifiedUnits", max(0, total - verified)) or 0)
@@ -287,6 +288,13 @@ def project_progress() -> dict:
},
"featureRouteCoverage": feature_route_coverage,
"featureRouteGaps": feature_route_gaps,
"completenessAudit": {
"status": completeness_audit.get("status", "MISSING"),
"implementationTasks": completeness_audit.get("implementation", {}).get("productTasks", {}),
"behavioralEvidence": completeness_audit.get("behavioralEvidence", {}),
"smoke": completeness_audit.get("smoke", {}),
"actualMissingOrBlocked": completeness_audit.get("actualMissingOrBlocked", []),
},
"productMigration": {
**product_status,
"strategy": product_backlog.get("strategy"),
@@ -430,6 +438,7 @@ def format_progress(progress: dict, fast_path: dict | None = None) -> list[str]:
f" implementation tasks: {product.get('taskImplemented', 0)}/{product.get('taskTotal', 0)} done, active={','.join(product.get('taskActive', [])) or 'none'}, pending={product.get('taskPending', 0)}, blocked={','.join(product.get('taskBlocked', [])) or 'none'}",
f" implementation estimate: {product_estimate if product_estimate is not None else 'learning'} minutes remaining; verification={product.get('verificationPolicy') or 'deferred'}",
f" source traceability reference: {progress.get('verifiedUnits', 0)}/{progress.get('totalUnits', 0)} bound; excluded from product completion denominator",
f" completeness audit: {progress.get('completenessAudit', {}).get('status', 'MISSING')} blocked={sum(item.get('status') == 'BLOCKED' for item in progress.get('completenessAudit', {}).get('actualMissingOrBlocked', []))} behavioral-unverified={progress.get('completenessAudit', {}).get('behavioralEvidence', {}).get('unverifiedUnits', 0)}",
f" task plan: {plan.get('done', 0)}/{plan.get('tasks', 0)} done, active={','.join(plan.get('active', [])) or 'none'}, blocked={plan.get('blocked', 0)}",
f" old verification estimate: remaining={estimate_text}, total={total_text} (reference only; strategy retired)",
f" observed bottleneck: stage={progress.get('bottleneck', {}).get('stage') or 'n/a'} median={progress.get('bottleneck', {}).get('medianDurationMs') or 0}ms command={progress.get('bottleneck', {}).get('label') or 'n/a'}",

View File

@@ -0,0 +1,207 @@
{
"schemaVersion": 1,
"auditId": "WBV2-FEATURE-COMPLETENESS-001",
"operation": "DETAILED_MIGRATION_COMPLETENESS_AUDIT",
"status": "PARTIAL_BLOCKED",
"implementation": {
"workflowModules": {
"total": 16,
"implemented": 16
},
"productTasks": {
"total": 34,
"implemented": 34,
"missingResultFiles": []
}
},
"routeCoverage": {
"unitCount": 3745,
"classifiedCount": 3745,
"unclassifiedCount": 0,
"counts": {
"BROWSER_EQUIVALENT": 434,
"SERVER_NATIVE": 1613,
"WEB_MAIN": 1698
},
"unavailable": [],
"byKind": {
"modifier-source": 97,
"node": 497,
"operator": 2018,
"operator-macro": 58,
"rna-struct": 979,
"web-command": 96
},
"byFamily": {
"EDITOR": 2023,
"MODIFIER": 97,
"NODES": 497,
"RNA": 979,
"WEB_ENGINE": 96,
"WINDOW_MANAGER": 53
}
},
"behavioralEvidence": {
"status": "PARTIAL",
"verifiedUnits": 1025,
"unverifiedUnits": 2720,
"unverifiedByKind": {
"modifier-source": 2,
"operator": 1987,
"operator-macro": 58,
"rna-struct": 673
},
"unverifiedByFamily": {
"EDITOR": 1992,
"MODIFIER": 2,
"RNA": 673,
"WINDOW_MANAGER": 53
},
"rule": "route assignment is not behavioral completion"
},
"smoke": {
"planId": "WBV2-FEATURE-SMOKE-001",
"totalFeatures": 34,
"attempts": 34,
"passed": 31,
"failedOrBlocked": [
{
"featureId": "WEBUI-OPERATOR-PALETTE",
"smokeId": "WBV2-SMOKE-WEBUI-OPERATOR-PALETTE",
"ordinal": 6,
"status": "FAIL",
"attempts": 1,
"evidence": {
"testFile": "tests/e2e/smoke.spec.ts",
"testTitle": "operator search executes context-filtered workspace, mode and Main commands",
"exitCode": 1
}
},
{
"featureId": "OPERATOR-ROUTE-PALETTE",
"smokeId": "WBV2-SMOKE-OPERATOR-ROUTE-PALETTE",
"ordinal": 28,
"status": "FAIL",
"attempts": 1,
"evidence": {
"testFile": "tests/e2e/io-format-ui-gate.spec.ts",
"testTitle": "M12-05C exposes only matrix-declared file and operator routes",
"exitCode": 1
}
},
{
"featureId": "SERVER-OPERATOR-HTTP-PROVIDER",
"smokeId": "WBV2-SMOKE-SERVER-OPERATOR-HTTP-PROVIDER",
"ordinal": 32,
"status": "FAIL",
"attempts": 1,
"evidence": {
"command": "node tools/web/check-server-job-result-binding.mjs",
"exitCode": 1
}
}
],
"focusedFix": [
{
"featureId": "WEBUI-OPERATOR-PALETTE",
"status": "PASS",
"verification": "Chromium focused test passed once after aligning locators with the current typed Blender operator palette and making F3 usable after file-input focus",
"files": [
"web/app/src/app/App.tsx",
"web/tests/e2e/smoke.spec.ts"
]
},
{
"featureId": "SERVER-OPERATOR-HTTP-PROVIDER",
"status": "PASS",
"verification": "server-operator-job-server.mjs syntax and server-operator-job.py compilation passed",
"files": [
"tools/web/server-operator-job-server.mjs",
"tools/web/server-operator-job.py"
]
},
{
"featureId": "OPERATOR-ROUTE-PALETTE",
"status": "BLOCKED",
"verification": "OBJ import route reaches the current WebEngine binary, but the checked-in WASM returns unified JSON cmd is not implemented",
"rootCause": "WASM artifact predates the source importScene/exportScene route",
"files": [
"web/protocol/web-engine.ts",
"web/app/src/engine-client/WebEngineClient.ts",
"web/app/src/app/App.tsx",
"web/tests/e2e/io-format-ui-gate.spec.ts"
],
"nextAction": "rebuild or replace the source-matched WebEngine WASM before rerunning this focused case"
}
],
"retries": 0
},
"wasmLifecycle": [
{
"id": "core-runtime-completed",
"lifecycle": "complete",
"preload": true,
"single": "web_engine.wasm",
"pthread": "web_engine.wasm"
},
{
"id": "mesh-editing-completed",
"lifecycle": "complete",
"preload": true,
"single": "web_engine.wasm",
"pthread": "web_engine.wasm"
},
{
"id": "reader-capabilities-completed",
"lifecycle": "complete",
"preload": true,
"single": "web_engine.wasm",
"pthread": "web_engine.wasm"
},
{
"id": "debug-incremental",
"lifecycle": "debug",
"preload": false,
"single": "web_engine.wasm",
"pthread": "web_engine.wasm"
},
{
"id": "debug-incremental-secondary",
"lifecycle": "debug",
"preload": false,
"single": "web_engine.secondary.wasm",
"pthread": "web_engine.secondary.wasm"
}
],
"actualMissingOrBlocked": [
{
"id": "IO_IMPORT_WASM_ARTIFACT",
"status": "BLOCKED",
"description": "importScene/exportScene is present in source/protocol but the checked-in WASM lacks the unified JSON route; rebuilding currently fails on Blender API/header drift.",
"evidence": "tests/golden/WBV2/feature-smoke-fix-report.json"
},
{
"id": "BEHAVIORAL_EVIDENCE_OVERLAY",
"status": "DEFERRED",
"description": "2720 classified source units still lack exact behavioral receipts; they remain routed, not claimed verified.",
"evidence": "web/app/src/capabilities/blender-feature-route-catalog.json"
}
],
"nextWork": {
"priority": [
"repair WebEngine source/API drift and rebuild source-matched WASM",
"rerun only OPERATOR-ROUTE-PALETTE focused IO smoke",
"then schedule batched evidence for unverified operators/RNA/macros/modifier units"
],
"forbidden": [
"do not rerun passed feature smoke cases",
"do not mark route assignment as behavioral verification",
"do not replace WASM with a simulated JS implementation"
]
},
"inputs": {
"productReportSha256": "ec2c29e2cc26d573e821647d4864e9bda3da77d46dbde27959aea2a2dc78218d",
"routeCatalogSha256": "f5df5b9efd00a8c4cdf4b0862064609da3d3538a97df5e3252d013a0791bde1d",
"smokeReportSha256": "85b71b3c7ef47266c2930b7f66407a22b99e8a4f1ef1bf09d8195d9be6d4ba89"
}
}

View File

@@ -0,0 +1,80 @@
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const readJson = (relative) => JSON.parse(fs.readFileSync(path.join(root, relative), "utf8"));
const sha = (relative) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, relative))).digest("hex");
const report = readJson("tests/golden/WBV2-P1424-001/product-implementation-report.json");
const catalog = readJson("web/app/src/capabilities/blender-feature-route-catalog.json");
const smokePlan = readJson("tests/golden/WBV2/feature-smoke-plan.json");
const smokeReport = readJson("tests/golden/WBV2/feature-smoke-report.json");
const smokeFix = readJson("tests/golden/WBV2/feature-smoke-fix-report.json");
const wasmManifest = readJson("web/app/public/wasm-module-manifest.json");
const countBy = (items, selector) => Object.fromEntries([...new Set(items.map(selector))].sort().map((key) => [key, items.filter((item) => selector(item) === key).length]));
const unverified = catalog.entries.filter((entry) => entry.evidenceStatus === "UNVERIFIED");
const failedSmoke = smokeReport.cases.filter((entry) => entry.status !== "PASS");
const implementationResults = report.implementationTasks.map((task) => ({
id: task.id,
module: task.module,
status: task.status,
resultPath: task.resultPath ?? null,
resultPresent: task.resultPath ? fs.existsSync(path.join(root, task.resultPath)) : false,
}));
const missingResults = implementationResults.filter((entry) => !entry.resultPresent);
const audit = {
schemaVersion: 1,
auditId: "WBV2-FEATURE-COMPLETENESS-001",
operation: "DETAILED_MIGRATION_COMPLETENESS_AUDIT",
status: missingResults.length || failedSmoke.length ? "PARTIAL_BLOCKED" : "IMPLEMENTATION_COMPLETE",
implementation: {
workflowModules: { total: report.workflowModules.length, implemented: report.workflowModules.filter((item) => item.status === "implemented").length },
productTasks: { total: report.implementationTasks.length, implemented: report.implementationTasks.filter((item) => item.status === "implemented").length, missingResultFiles: missingResults },
},
routeCoverage: {
unitCount: catalog.unitCount,
classifiedCount: catalog.classifiedCount,
unclassifiedCount: catalog.unclassifiedCount,
counts: catalog.counts,
unavailable: catalog.entries.filter((entry) => entry.route === "UNAVAILABLE").map((entry) => entry.id),
byKind: countBy(catalog.entries, (entry) => entry.kind),
byFamily: countBy(catalog.entries, (entry) => entry.family),
},
behavioralEvidence: {
status: catalog.behavioralEvidence.status,
verifiedUnits: catalog.behavioralEvidence.verifiedUnits,
unverifiedUnits: catalog.behavioralEvidence.unverifiedUnits,
unverifiedByKind: countBy(unverified, (entry) => entry.kind),
unverifiedByFamily: countBy(unverified, (entry) => entry.family),
rule: "route assignment is not behavioral completion",
},
smoke: {
planId: smokePlan.planId,
totalFeatures: smokePlan.cases.length,
attempts: smokeReport.cases.reduce((sum, entry) => sum + Number(entry.attempts ?? 0), 0),
passed: smokeReport.cases.filter((entry) => entry.status === "PASS").length,
failedOrBlocked: failedSmoke,
focusedFix: smokeFix.fixes,
retries: smokeFix.retries,
},
wasmLifecycle: wasmManifest.modules.map((module) => ({ id: module.id, lifecycle: module.lifecycle, preload: module.preload, single: module.resources?.single?.fileName ?? null, pthread: module.resources?.pthread?.fileName ?? null })),
actualMissingOrBlocked: [
{ id: "IO_IMPORT_WASM_ARTIFACT", status: "BLOCKED", description: "importScene/exportScene is present in source/protocol but the checked-in WASM lacks the unified JSON route; rebuilding currently fails on Blender API/header drift.", evidence: "tests/golden/WBV2/feature-smoke-fix-report.json" },
{ id: "BEHAVIORAL_EVIDENCE_OVERLAY", status: "DEFERRED", description: "2720 classified source units still lack exact behavioral receipts; they remain routed, not claimed verified.", evidence: "web/app/src/capabilities/blender-feature-route-catalog.json" },
],
nextWork: {
priority: ["repair WebEngine source/API drift and rebuild source-matched WASM", "rerun only OPERATOR-ROUTE-PALETTE focused IO smoke", "then schedule batched evidence for unverified operators/RNA/macros/modifier units"],
forbidden: ["do not rerun passed feature smoke cases", "do not mark route assignment as behavioral verification", "do not replace WASM with a simulated JS implementation"],
},
inputs: {
productReportSha256: sha("tests/golden/WBV2-P1424-001/product-implementation-report.json"),
routeCatalogSha256: sha("web/app/src/capabilities/blender-feature-route-catalog.json"),
smokeReportSha256: sha("tests/golden/WBV2/feature-smoke-report.json"),
},
};
const outputPath = path.join(root, "tests/golden/WBV2/feature-completeness-audit.json");
fs.writeFileSync(outputPath, `${JSON.stringify(audit, null, 2)}\n`);
process.stdout.write(`wbv2-feature-completeness-audit status=${audit.status} modules=${audit.implementation.workflowModules.implemented}/${audit.implementation.workflowModules.total} tasks=${audit.implementation.productTasks.implemented}/${audit.implementation.productTasks.total} routes=${audit.routeCoverage.classifiedCount}/${audit.routeCoverage.unitCount} verified=${audit.behavioralEvidence.verifiedUnits} unverified=${audit.behavioralEvidence.unverifiedUnits} smokePassed=${audit.smoke.passed}/${audit.smoke.totalFeatures} blocked=${audit.actualMissingOrBlocked.filter((item) => item.status === "BLOCKED").length}\n`);