feat: extend FreeCAD parity queue and pair diagnostics
Some checks failed
real-verification / chrome (push) Has been cancelled
real-verification / freecad-oracle (push) Has been cancelled
real-verification / wasm (push) Has been cancelled

This commit is contained in:
2026-08-15 06:29:20 -04:00
parent 83cbc80971
commit 378a6e7c0a
13 changed files with 21979 additions and 84 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
{
"schemaVersion": 1,
"baseline": {
"freecadVersion": "1.1.1",
"commit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d"
},
"policy": {
"ordering": "completed task IDs must form a contiguous prefix of the generated follow-up queue",
"evidence": "every completed task must name at least one machine-checkable artifact or focused check"
},
"completedTasks": []
}

File diff suppressed because it is too large Load Diff

View File

@@ -34,7 +34,7 @@
{ "id": "EX-TSN-01", "title": "Capture native stage Shape and Generated/Modified/Deleted for every builder", "priority": "P0", "status": "completed", "dependencies": ["EX-KER-01", "EX-TSN-00"], "deliverables": ["All builder-stage captures", "Multi-input and one-to-many lineage", "Local feature recompute history"], "acceptance": ["Every feature result relation has a producing native stage", "No final-result index is applied to an intermediate Shape"], "evidence": ["check:freecad-composite-history-elementmap", "check:freecad-tsn-stage-evidence"], "exactBlockedBy": [] }, { "id": "EX-TSN-01", "title": "Capture native stage Shape and Generated/Modified/Deleted for every builder", "priority": "P0", "status": "completed", "dependencies": ["EX-KER-01", "EX-TSN-00"], "deliverables": ["All builder-stage captures", "Multi-input and one-to-many lineage", "Local feature recompute history"], "acceptance": ["Every feature result relation has a producing native stage", "No final-result index is applied to an intermediate Shape"], "evidence": ["check:freecad-composite-history-elementmap", "check:freecad-tsn-stage-evidence"], "exactBlockedBy": [] },
{ "id": "EX-TSN-02", "title": "Expose native MappedNameRef and StringHasher decisions for every feature", "priority": "P0", "status": "completed", "dependencies": ["EX-TSN-01"], "deliverables": ["Native naming callback ABI", "Per-stage StringHasher evidence", "Lossless ElementMap2 token generation"], "acceptance": ["All new tokens originate in native evidence", "FreeCAD round-trip names remain byte and semantic stable"], "evidence": ["check:freecad-native-naming-evidence", "check:freecad-sketcher-partdesign-abi", "check:freecad-naming-sdk", "check:freecad-naming-production", "check:chrome-freecad-naming-production", "check:freecad-private-naming-boundary", "check:occt-history-artifact"], "exactBlockedBy": [] }, { "id": "EX-TSN-02", "title": "Expose native MappedNameRef and StringHasher decisions for every feature", "priority": "P0", "status": "completed", "dependencies": ["EX-TSN-01"], "deliverables": ["Native naming callback ABI", "Per-stage StringHasher evidence", "Lossless ElementMap2 token generation"], "acceptance": ["All new tokens originate in native evidence", "FreeCAD round-trip names remain byte and semantic stable"], "evidence": ["check:freecad-native-naming-evidence", "check:freecad-sketcher-partdesign-abi", "check:freecad-naming-sdk", "check:freecad-naming-production", "check:chrome-freecad-naming-production", "check:freecad-private-naming-boundary", "check:occt-history-artifact"], "exactBlockedBy": [] },
{ "id": "EX-TSN-03", "title": "Resolve isomorphic topology only from unique native provenance", "priority": "P0", "status": "completed", "dependencies": ["EX-TSN-01", "EX-TSN-02"], "deliverables": ["Symmetric Boolean corpus", "Candidate provenance graph", "Ambiguity repair lifecycle"], "acceptance": ["Unique native sources resolve deterministically", "Non-unique sources remain ambiguous across save, undo and recompute"], "evidence": ["test:topology-replay", "test:facade", "check:freecad-isomorphic-provenance", "check:freecad-naming-production", "check:chrome-freecad-naming-production", "check:freecad-exact-history-elementmap-gate"], "exactBlockedBy": [] }, { "id": "EX-TSN-03", "title": "Resolve isomorphic topology only from unique native provenance", "priority": "P0", "status": "completed", "dependencies": ["EX-TSN-01", "EX-TSN-02"], "deliverables": ["Symmetric Boolean corpus", "Candidate provenance graph", "Ambiguity repair lifecycle"], "acceptance": ["Unique native sources resolve deterministically", "Non-unique sources remain ambiguous across save, undo and recompute"], "evidence": ["test:topology-replay", "test:facade", "check:freecad-isomorphic-provenance", "check:freecad-naming-production", "check:chrome-freecad-naming-production", "check:freecad-exact-history-elementmap-gate"], "exactBlockedBy": [] },
{ "id": "EX-TSN-04", "title": "Close topology naming across all feature edits and FCStd round-trips", "priority": "P0", "status": "in_progress", "dependencies": ["EX-TSN-02", "EX-TSN-03"], "deliverables": ["Cross-feature mutation matrix", "ElementMap2 history migration", "Long-chain LinkSub stability report"], "acceptance": ["Wrong bindings, unexplained relations and name drift are zero for the exhaustive corpus"], "evidence": ["check:freecad-isomorphic-provenance", "check:freecad-composite-history-elementmap", "check:freecad-tsn-stage-correlation", "check:freecad-fcstd-roundtrip", "check:freecad-exact-history-elementmap-gate", "check:freecad-recovered-naming-classification", "check:freecad-production-drift-classification", "check:freecad-ordered-operation-pairs"], "exactBlockedBy": ["The locked 30-case corpus passes 30/30 native property mutations, final-shape propagation and 219/219 geometry-stage restores; all 11 PartDesign recovered-naming cases/28 stages and both production restore-topology cases/5 stages are classified as allowed native evolution from independent deterministic replays with stable resave; the five-stage chain still has 3/5 generic Shape proxies and no registered rotate mutation family, while only 8/361 ordered operation pairs are classified and replayed"] } { "id": "EX-TSN-04", "title": "Close topology naming across all feature edits and FCStd round-trips", "priority": "P0", "status": "in_progress", "dependencies": ["EX-TSN-02", "EX-TSN-03"], "deliverables": ["Cross-feature mutation matrix", "ElementMap2 history migration", "Long-chain LinkSub stability report"], "acceptance": ["Wrong bindings, unexplained relations and name drift are zero for the exhaustive corpus"], "evidence": ["check:freecad-isomorphic-provenance", "check:freecad-composite-history-elementmap", "check:freecad-tsn-stage-correlation", "check:freecad-fcstd-roundtrip", "check:freecad-exact-history-elementmap-gate", "check:freecad-recovered-naming-classification", "check:freecad-production-drift-classification", "check:freecad-ordered-operation-pairs"], "exactBlockedBy": ["The locked mutation and recovery corpora have zero unclassified drift, but the five-stage chain still contains generic Shape proxies, the registered rotate mutation family is open, and the ordered operation-pair matrix remains incomplete; current counts and the unique next task are generated from config/freecad-active-work-queue.json"] }
] ]
}, },
{ {

View File

@@ -113,14 +113,9 @@ M3 不要求把 FreeCAD 本身拒绝的组合强行实现为顺序特征链。
### 6.1 微任务队列 ### 6.1 微任务队列
机器真值为 `config/freecad-active-work-queue.json`,由 `generate:freecad-active-work-queue` 从原生报告生成,并由 `check:freecad-active-work-queue` fail-closed 校验。当前共 487 个微任务134 `completed`、1 `in_progress`、352 `pending``maxInProgress=1`,唯一下一任务是 `TSN-PAIR-fuse-pad` 机器真值为 `config/freecad-active-work-queue.json`,由 `generate:freecad-active-work-queue` 从原生报告、Property 语义报告、exact 计划和后续进度账本生成,并由 `check:freecad-active-work-queue` fail-closed 校验。实时任务数、里程碑状态和唯一下一任务只发布在 `docs/freecad-active-work-status.generated.zh-CN.md`,本手写文档不再复制动态数字
| 微里程碑 | 任务粒度 | 数量 | 状态 | 下一退出条件 | 队列现在覆盖 GUI oracle、TSN 漂移、全部有序操作对、逐 Property codec、Document 语义、核心建模、FCStd/格式、GUI 全量、文档/工程工作台、平台/脚本/代理和 QA/发布。每个后续能力拆为 A-I 九个串行任务;完成证据记录在 `config/freecad-follow-up-task-progress.json`,跳号、未知任务、空证据和第二个活动任务都会使生成器失败。
| --- | --- | ---: | --- | --- |
| `ORA-GUI-BASELINE` | 1 个配置边界或 1 个 GUI 命令分片 | 106 | 106 completed | 已关闭100/100 分片、1111/1111 命令状态、2 个上下文延迟命令均有证据 |
| `ORA-GUI-WORKFLOWS` | 1 个命令族的 1 种工作流状态 | 7 | 7 completed | `PartDesign_Pad` 五态原生工作流、聚焦断言和 exact blocker 更新已关闭 |
| `TSN-RECOVERY-DRIFT` | 1 个原生 case 或 1 个生产 mutation | 13 | 13 completed | 11 个 PartDesign case 与生产 `cut`/`rotate` 漂移均已分类为允许演进 |
| `TSN-ORDERED-PAIRS` | 1 个有序操作对 | 361 | 8 completed / 1 in_progress / 352 pending | `fuse->rotate` 已关闭;当前只分类 `fuse->pad` |
`ORA-GUI-WORKFLOWS` 已按七步关闭:选定命令族、成功、禁用、失败、取消、恢复、更新 blocker/门禁。一步只新增一种原生状态证据,前一步未通过时不启动后一步。 `ORA-GUI-WORKFLOWS` 已按七步关闭:选定命令族、成功、禁用、失败、取消、恢复、更新 blocker/门禁。一步只新增一种原生状态证据,前一步未通过时不启动后一步。

View File

@@ -0,0 +1,28 @@
<!-- Generated by scripts/generate-freecad-active-work-queue.mjs. Do not edit. -->
# FreeCAD 后续工作机器状态
生成日期2026-08-15。基线FreeCAD 1.1.1 / `0108fd4b4850cc46e625b60e53cea7a7bbe69f8d`
- 唯一活动任务:`TSN-PAIR-fuse-revolution`
- 微任务1756 total / 138 completed / 1 in_progress / 1617 pending
- 有序操作对12/361 classified / 349 unknown
- Property27/85 native types / 58 opaque types
- exact 任务5/52 completed
- 模块能力0 exact / 26 compatible / 5 proxy / 3 development
| 里程碑 | 任务数 | completed | in_progress | pending | 当前任务 |
| --- | ---: | ---: | ---: | ---: | --- |
| `ORA-GUI-BASELINE` | 106 | 106 | 0 | 0 | `-` |
| `ORA-GUI-WORKFLOWS` | 7 | 7 | 0 | 0 | `-` |
| `TSN-RECOVERY-DRIFT` | 13 | 13 | 0 | 0 | `-` |
| `TSN-ORDERED-PAIRS` | 361 | 12 | 1 | 348 | `TSN-PAIR-fuse-revolution` |
| `PROPERTY-CODECS` | 522 | 0 | 0 | 522 | `-` |
| `DOCUMENT-SEMANTICS` | 90 | 0 | 0 | 90 | `-` |
| `CORE-MODELING` | 171 | 0 | 0 | 171 | `-` |
| `FCSTD-AND-FORMATS` | 72 | 0 | 0 | 72 | `-` |
| `GUI-CLOSURE` | 135 | 0 | 0 | 135 | `-` |
| `DOCUMENT-ENGINEERING-WORKBENCHES` | 90 | 0 | 0 | 90 | `-` |
| `PLATFORM-SCRIPT-PROXY` | 117 | 0 | 0 | 117 | `-` |
| `QA-RELEASE` | 72 | 0 | 0 | 72 | `-` |
详细任务、依赖、阶段、证据要求和退出条件见 `config/freecad-active-work-queue.json`。完成证据只能追加到 `config/freecad-follow-up-task-progress.json`,生成器会拒绝跳号、未知任务和无证据完成项。

View File

@@ -0,0 +1,55 @@
# FreeCAD 后续工作细粒度执行计划
## 1. 机器真值
- `config/freecad-active-work-queue.json`:全部微任务、依赖、阶段、证据要求、退出条件和唯一活动任务。
- `docs/freecad-active-work-status.generated.zh-CN.md`:由同一生成器产生的实时摘要;禁止手工编辑或在其他计划中复制动态数字。
- `config/freecad-follow-up-task-progress.json`A-I 后续任务的完成证据账本;只能追加连续前缀。
- `config/freecad-ordered-operation-pair-classification.json`19×19 操作对的原生接受/拒绝事实。
`./npmw run generate:freecad-active-work-queue` 同时生成队列和状态页;`./npmw run check:freecad-active-work-queue` 对两个产物 fail-closed。任何时刻必须恰好有一个 `in_progress` 任务。
## 2. 里程碑顺序
1. `ORA-GUI-BASELINE`:原生 GUI 命令清册和隔离探针。
2. `ORA-GUI-WORKFLOWS`:命令族成功、禁用、失败、取消和恢复。
3. `TSN-RECOVERY-DRIFT`:恢复命名与生产拓扑漂移分类。
4. `TSN-ORDERED-PAIRS`19×19 操作对逐对接受/拒绝、变异、命名和往返。
5. `PROPERTY-CODECS`:从原生语义报告自动枚举所有 opaque Property 类型。
6. `DOCUMENT-SEMANTICS`observer、事务、DAG、失败恢复、Extension、FeaturePython 和多文档。
7. `CORE-MODELING`19 个生产建模操作的纵向闭环。
8. `FCSTD-AND-FORMATS`FCStd 资源、对抗输入和 STEP/IGES/BREP 双向往返。
9. `GUI-CLOSURE`应用壳、MDI、树、Task、属性编辑器、对话框、焦点、选择和响应式无障碍。
10. `DOCUMENT-ENGINEERING-WORKBENCHES`Draft、Spreadsheet、TechDraw、Plot、Assembly、BIM、Surface、Mesh 和 Material。
11. `PLATFORM-SCRIPT-PROXY`FEM、CAM、Robot、数据模块、Python/Macro、Addon 及正式代理边界。
12. `QA-RELEASE`:浏览器/OS/GPU、无障碍/locale、fuzz/长稳、资源、签名发布和 exact promotion。
前一里程碑未完成时,后一里程碑全部保持 `pending`。只读 oracle 可以准备,但不能写入第二个活动任务或提前提升能力。
## 3. 每个能力的九个最小任务
| 阶段 | 单一交付物 | 退出条件 |
| --- | --- | --- |
| A | 原生清册与前置 | TypeId、输入、默认值、依赖和适用性机器可读 |
| B | 原生成功证据 | 名义值和边界值的值、Shape、拓扑、状态与诊断齐全 |
| C | 原生失败/取消证据 | 无效输入、禁用、取消和文档未污染可复现 |
| D | 参数变异/恢复 | 编辑、重算、恢复后的几何与语义完成分类 |
| E | Facade 实现 | 生产路径无隐藏 fallbackUI 不直连内核 |
| F | 事务恢复 | abort/undo/redo/stale/失败和资源所有权闭合 |
| G | FCStd 往返 | FreeCAD-Web-FreeCAD 无未知语义漂移 |
| H | 真实浏览器 | Chrome Worker、OPFS、UI 状态和资源释放通过 |
| I | 能力提升 | A-H 全过,聚焦门禁与 exact blocker 由机器报告同步 |
每个阶段只允许一个主要产物和一个聚焦检查。缺少任一阶段时,能力最多保持 `compatible` 或既有的显式 proxy 等级。
## 4. 单轮执行协议
1. 读取队列的 `nextTask`,确认依赖和输入 artifact 哈希。
2. 只修改该任务直接需要的 oracle、实现、检查器和报告。
3. 先运行原生 probe 或真实浏览器 test再运行对应只读 check。
4. 验证 Shape/Worker/OPFS 等资源回到基线,失败不污染文档。
5. 运行相关 Facade、FCStd、TSN/执行计划门禁。
6. 仅在退出条件全部满足后记录完成证据并重新生成队列。
7. 运行 `git diff --check`、全部 JSON 解析和与风险相称的回归;发布点运行完整 `./npmw run verify`
原生拒绝是有效分类,不应被强行实现为接受;但必须固定失败阶段、诊断、事务回滚和干净往返。原生接受则必须使用真实 builder不能用通用 Shape 代理冒充。

View File

@@ -50,29 +50,14 @@
- Property status15 类已观察状态均已进入类型化 Facade 行为;`LockDynamic``PartialTrigger` 已有原生事务证据,但完整 observer 与局部重算序列未关闭。 - Property status15 类已观察状态均已进入类型化 Facade 行为;`LockDynamic``PartialTrigger` 已有原生事务证据,但完整 observer 与局部重算序列未关闭。
- TSN30/30 原生 mutation、219/219 几何阶段恢复通过11 个 PartDesign case/28 个恢复命名阶段均已通过第二次独立原生复跑分类为允许演进。 - TSN30/30 原生 mutation、219/219 几何阶段恢复通过11 个 PartDesign case/28 个恢复命名阶段均已通过第二次独立原生复跑分类为允许演进。
- 生产命名链:五阶段 5/5 几何恢复2 个恢复拓扑 case/5 个下游阶段已分类为允许演进;仍有 3/5 通用 Shape 代理并缺注册 rotate mutation family。 - 生产命名链:五阶段 5/5 几何恢复2 个恢复拓扑 case/5 个下游阶段已分类为允许演进;仍有 3/5 通用 Shape 代理并缺注册 rotate mutation family。
- 有序操作19×19 共 361 对8 对已有分类回放353 对待原生接受/拒绝判定 - 有序操作19×19 的逐对分类持续执行;实时已完成数、未知数和唯一活动项只从生成状态读取
- FCStd支持的原生 codec 与未知资源保留已有证据,但所有对象/属性/Extension/Shape/ElementMap2/StringHasher 的 FreeCAD-Web-FreeCAD 双向变异未穷尽。 - FCStd支持的原生 codec 与未知资源保留已有证据,但所有对象/属性/Extension/Shape/ElementMap2/StringHasher 的 FreeCAD-Web-FreeCAD 双向变异未穷尽。
- GUI应用壳和代表性工作流已有浏览器证据但动态命令、Task、对话框、焦点、上下文和逐工作台流程没有完整原生对照。 - GUI应用壳和代表性工作流已有浏览器证据但动态命令、Task、对话框、焦点、上下文和逐工作台流程没有完整原生对照。
- 发布:系统级 exact 为 false`EX-REL-01` 为 pending跨浏览器、长期运行和全模块 exact promotion 尚未开始。 - 发布:系统级 exact 为 false`EX-REL-01` 为 pending跨浏览器、长期运行和全模块 exact promotion 尚未开始。
### 2.4 可机器核对的完成度仪表盘 ### 2.4 可机器核对的完成度仪表盘
| 范围 | 当前值 | 已完成代表什么 | 下一个可关闭单元 | 实时仪表盘为 `docs/freecad-active-work-status.generated.zh-CN.md`,由 `generate:freecad-active-work-queue` 同活动队列一起生成,并由 `check:freecad-active-work-queue` 逐字校验。此处不再复制任务数、完成数、操作对覆盖数、Property 覆盖数或下一任务;任何手工状态摘要均不具备治理效力。
| --- | --- | --- | --- |
| 兼容实现计划 | 83/83 completed | 已有可运行 Web 实现、门禁和能力边界 | 不再用该数字表示 exact 进度 |
| exact 计划 | 5/52 completed36 in_progress11 pending | 仅 `EX-TSN-00..03``EX-PD-02` 达到自身验收范围 | `EX-TSN-04` 的恢复漂移与有序组合 |
| 模块能力 | 0 exact / 26 compatible / 5 proxy / 3 development | 34 模块均有明确对外等级,无 unexplained | 任一模块提升前必须关闭其 exact task |
| 原生 GUI 清册 | 1111 runtime / 19 source-only / 2 context-only | 命令 ID 和三种激活上下文已分类 | 选择下一命令族并逐个关闭五态 |
| Pad GUI 工作流 | 7/7 completed | success/disabled/failure/cancel/recovery 和 blocker 已对齐 | 不因单命令族完成而关闭 `EX-ORA-01` |
| TSN 30-case corpus | 30/30 mutation219/219 几何恢复 | 几何传播与恢复已闭环 | 逐 case 解释 11 个命名漂移 |
| TSN 恢复命名 | 11/11 case28/28 stage 已分类 | 全部具备第二次原生复跑与稳定 resave | 已关闭该队列,保持分类门禁 |
| 生产五阶段链 | 5/5 几何恢复2/2 漂移 case 已分类2/5 原生 builder | 漂移已解释且不错绑 | 逐 stage 去代理并补 rotate mutation family |
| 19x19 有序组合 | 8/361 已分类回放 | 4 条五阶段转换与 4 条 `fuse->*` 已覆盖 | `TSN-PAIR-fuse-pad` |
| Property codec | 27/85 类原生 codec58 类 opaque | 4987/5510 记录可结构化处理 | 按 M04 顺序一次关闭 1 类 |
| FCStd | 23 个往返场景,未知差异 0 | 已支持范围内的当前样本稳定 | 对每种对象/Property/资源做双向 mutation |
| 活动微任务 | 487 项134 completed / 1 in_progress / 352 pending | 队列满足 `maxInProgress=1` | 只允许执行 `TSN-PAIR-fuse-pad` |
仪表盘的任何数字变化都必须由配置/报告生成器证明;文档不能单独改变状态。
## 3. 目标架构和不可破坏边界 ## 3. 目标架构和不可破坏边界
@@ -161,7 +146,7 @@ FCStd / STEP / IGES / mesh / CAM / engineering resources
每项必须得到且只能得到一种结论FreeCAD 稳定语义、允许的命名演进、实现缺陷。实现缺陷必须增加回归测试;未知不能作为结论。 每项必须得到且只能得到一种结论FreeCAD 稳定语义、允许的命名演进、实现缺陷。实现缺陷必须增加回归测试;未知不能作为结论。
每个 case 再拆成六个不计入 487 项队列总数、但必须依次完成的最小检查点: 每个 case 再拆成六个必须依次完成的最小检查点;后续能力的 A-I 检查点已经全部进入生成队列
| 检查点 | 唯一产物 | fail-closed 验收 | | 检查点 | 唯一产物 | fail-closed 验收 |
| --- | --- | --- | | --- | --- | --- |
@@ -174,7 +159,7 @@ FCStd / STEP / IGES / mesh / CAM / engineering resources
任一检查点失败时,当前任务保持 `in_progress`,不得领取下一 case。如果分类为 `implementation_defect`,还必须在同一队列项内完成最小修复、回归测试、原生/Web 对照和资源释放,不另行跳过。 任一检查点失败时,当前任务保持 `in_progress`,不得领取下一 case。如果分类为 `implementation_defect`,还必须在同一队列项内完成最小修复、回归测试、原生/Web 对照和资源释放,不另行跳过。
### 5.4 TSN 有序操作对361 项8 完成/1 进行中/352 待办 ### 5.4 TSN 有序操作对
操作全集为 Fuse、Cut、Common、Rotate、Pad、Pocket、Loft、Pipe、Revolution、Groove、Fillet、Chamfer、Hole、Draft、Thickness、LinearPattern、PolarPattern、Mirrored、MultiTransform。 操作全集为 Fuse、Cut、Common、Rotate、Pad、Pocket、Loft、Pipe、Revolution、Groove、Fillet、Chamfer、Hole、Draft、Thickness、LinearPattern、PolarPattern、Mirrored、MultiTransform。
@@ -189,7 +174,7 @@ FCStd / STEP / IGES / mesh / CAM / engineering resources
7. 在生产 Worker 回放并检查释放。 7. 在生产 Worker 回放并检查释放。
8. 拒绝时固定原生错误类型和文档未污染证据。 8. 拒绝时固定原生错误类型和文档未污染证据。
已覆盖的八对为 `cut->rotate``rotate->fillet``fillet->mirrored``mirrored->linear-pattern``fuse->fuse``fuse->cut``fuse->common``fuse->rotate`。当前只处理 `fuse->pad`,其余严格按机器队列逐对领取。 初始五阶段转换和 `fuse->*` 连续前缀均已进入机器分类账本;实时覆盖集合与当前操作对严格从生成状态和 `config/freecad-ordered-operation-pair-classification.json` 读取,其余逐对领取。
## 6. 后续关键路径微里程碑 ## 6. 后续关键路径微里程碑
@@ -227,7 +212,7 @@ FCStd / STEP / IGES / mesh / CAM / engineering resources
### M03 `EX-TSN-04B` builder 与 19×19 转换矩阵 ### M03 `EX-TSN-04B` builder 与 19×19 转换矩阵
状态:`in_progress`已覆盖 8/361 对,当前执行 `TSN-PAIR-fuse-pad` 状态:`in_progress`实时覆盖数和当前操作对见生成状态
- M03.1:用真实 builder 替换五阶段链中 Cut 之后的通用 Shape 代理,一次替换一个 stage。 - M03.1:用真实 builder 替换五阶段链中 Cut 之后的通用 Shape 代理,一次替换一个 stage。
- M03.2:增加 Rotate 原生 mutation family。 - M03.2:增加 Rotate 原生 mutation family。
@@ -422,6 +407,6 @@ M00
`TSN-DRIFT-003` 已完成:`partdesign-taper``Length 6 -> 6.75 -> 6` 在 Body13 和 Pad13 两个阶段恢复相同几何,关系与语义命名摘要演进为新的稳定历史。锁定报告与第二次独立 FreeCAD 1.1.1 复跑的逐阶段摘要一致,演进值在 save/reopen/resave 中保持,因此同样分类为 `allowed_evolution`,不是 Web 实现缺陷。 `TSN-DRIFT-003` 已完成:`partdesign-taper``Length 6 -> 6.75 -> 6` 在 Body13 和 Pad13 两个阶段恢复相同几何,关系与语义命名摘要演进为新的稳定历史。锁定报告与第二次独立 FreeCAD 1.1.1 复跑的逐阶段摘要一致,演进值在 save/reopen/resave 中保持,因此同样分类为 `allowed_evolution`,不是 Web 实现缺陷。
`TSN-DRIFT-004..010` 与生产 `cut`/`rotate` 漂移均已完成独立复跑,并唯一分类为 `allowed_evolution`;恢复漂移队列因此以 13/13 关闭。`fuse->fuse``fuse->cut``fuse->common``fuse->rotate` 的新增分类与既有五阶段四条转换合计覆盖 8/361 对 `TSN-DRIFT-004..010` 与生产 `cut`/`rotate` 漂移均已完成独立复跑,并唯一分类为 `allowed_evolution`;恢复漂移队列因此关闭
当前唯一下一任务为 `TSN-PAIR-fuse-pad`。退出条件是记录原生接受或拒绝;接受时必须具备真实 builder、mutation/restore、history/naming FreeCAD save/reopen/resave 证据,拒绝时必须固定原生错误与无文档污染证据 `fuse->pad` 已由原生 OCCT builder 和 FreeCAD `PartDesign::Pad` 文档路径稳定拒绝,事务 abort 后对象集与源 Shape 恢复,干净 FCStd save/reopen/resave 不受污染。`fuse->pocket` 已由真实 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 接受,长度变异与恢复、history/naming FCStd 三阶段均通过。唯一下一任务不在此手工记录,只读取生成状态

View File

@@ -18,25 +18,54 @@ for (const artifact of report.nativeProbe?.artifacts ?? []) {
if (bytes !== artifact.bytes || sha256(content) !== artifact.sha256) fail(`${artifact.name} provenance is stale.`) if (bytes !== artifact.bytes || sha256(content) !== artifact.sha256) fail(`${artifact.name} provenance is stale.`)
} }
const expectedPairs = [ const expectedPairs = [
{ pair: 'fuse->fuse', secondBuilder: 'BRepAlgoAPI_Fuse', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [12, 11, 12] }, { pair: 'fuse->fuse', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepAlgoAPI_Fuse', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [12, 11, 12] },
{ pair: 'fuse->cut', secondBuilder: 'BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'toolSize', mutationTrajectory: [4, 3, 4] }, { pair: 'fuse->cut', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'toolSize', mutationTrajectory: [4, 3, 4] },
{ pair: 'fuse->common', secondBuilder: 'BRepAlgoAPI_Common', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [10, 9, 10] }, { pair: 'fuse->common', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepAlgoAPI_Common', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [10, 9, 10] },
{ pair: 'fuse->rotate', secondBuilder: 'BRepBuilderAPI_Transform', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [15, 22.5, 15] }, { pair: 'fuse->rotate', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepBuilderAPI_Transform', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [15, 22.5, 15] },
{ pair: 'fuse->pad', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakePrism', secondInputCount: 1, mutationParameter: 'length', mutationTrajectory: [5, 7.5, 5], nativeTypeId: 'PartDesign::Pad' },
{ pair: 'fuse->pocket', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'length', mutationTrajectory: [5, 4, 5] },
{ pair: 'fuse->loft', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', rejectionAuthority: 'freecad-document', kernelOutcome: 'accepted', secondBuilder: 'BRepOffsetAPI_ThruSections', secondInputCount: 2, mutationParameter: 'ruled', mutationTrajectory: [false, true, false], nativeTypeId: 'Part::Loft', freeCadDiagnostic: 'Profile shape is not a single vertex, edge, wire nor face.' },
{ pair: 'fuse->pipe', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', rejectionAuthority: 'freecad-document', kernelOutcome: 'invalid-result', secondBuilder: 'BRepOffsetAPI_MakePipe', secondInputCount: 2, mutationParameter: 'spineLength', mutationTrajectory: [15, 12, 15], nativeTypeId: 'Part::Sweep', freeCadDiagnostic: 'A fatal error occurred when making the sweep' },
] ]
if (report.nativeProbe?.artifacts?.length !== 3 || report.classifications?.length !== expectedPairs.length) fail('native artifacts or classification prefix is incomplete.') if (report.nativeProbe?.artifacts?.length !== 3 || report.classifications?.length !== expectedPairs.length) fail('native artifacts or classification prefix is incomplete.')
for (const [index, entry] of report.classifications.entries()) { for (const [index, entry] of report.classifications.entries()) {
const expected = expectedPairs[index] const expected = expectedPairs[index]
const secondOperation = expected.pair.split('->')[1] const secondOperation = expected.pair.split('->')[1]
if (entry.taskId !== `TSN-PAIR-${expected.pair.replace('->', '-')}` || entry.pair !== expected.pair || entry.classification !== 'accepted' || entry.nativeDecision !== 'accepted' || entry.first?.operation !== 'fuse' || entry.first.builder !== 'BRepAlgoAPI_Fuse' || entry.first.inputCount !== 2 || entry.second?.operation !== secondOperation || entry.second.builder !== expected.secondBuilder || entry.second.inputCount !== expected.secondInputCount || entry.first.historyProvider !== 'occt-native' || entry.second.historyProvider !== 'occt-native') fail(`${expected.pair} decision contract is invalid.`) if (entry.taskId !== `TSN-PAIR-${expected.pair.replace('->', '-')}` || entry.pair !== expected.pair || entry.classification !== expected.decision || entry.reasonCode !== expected.reasonCode || entry.nativeDecision !== expected.decision || entry.first?.operation !== 'fuse' || entry.first.builder !== 'BRepAlgoAPI_Fuse' || entry.first.inputCount !== 2 || entry.first.historyProvider !== 'occt-native' || entry.first.summary?.isValid !== true || entry.first.historyRecords < 1) fail(`${expected.pair} decision contract is invalid.`)
if (entry.first.summary?.isValid !== true || entry.second.summary?.isValid !== true || entry.first.historyRecords < 1 || entry.second.historyRecords < 1 || entry.mutation?.changed !== true || entry.mutation?.restoredExactly !== true) fail(`${expected.pair} builder or mutation evidence is incomplete.`) if (expected.decision === 'rejected') {
if ('second' in entry || 'mutation' in entry || 'naming' in entry) fail(`${expected.pair} fabricates evidence for a result that FreeCAD rejected.`)
const rejection = entry.rejection
if (rejection?.scope !== 'second-operation-only' || rejection.authority !== expected.rejectionAuthority || rejection.parameter !== expected.mutationParameter || JSON.stringify(rejection.trajectory) !== JSON.stringify(expected.mutationTrajectory)) fail(`${expected.pair} rejection trajectory is invalid.`)
if (expected.rejectionAuthority === 'occt-builder') {
if (rejection.stable !== true || rejection.attempts?.length !== 3) fail(`${expected.pair} OCCT rejection is unstable.`)
for (const [attemptIndex, attempt] of rejection.attempts.entries()) {
if (attempt.operation !== secondOperation || attempt.builder !== expected.secondBuilder || attempt.inputCount !== expected.secondInputCount || attempt.parameter !== expected.mutationParameter || attempt.parameterValue !== expected.mutationTrajectory[attemptIndex] || attempt.runtime !== 'occt-native' || attempt.outcome !== 'rejected' || attempt.resultProduced !== false || attempt.shapeProduced !== false || attempt.historyProduced !== false || attempt.rejectionStage !== 'builder' || attempt.exceptionTransport !== 'emscripten-cpp-exception' || attempt.thrownType !== 'number') fail(`${expected.pair} OCCT attempt ${attemptIndex} is invalid.`)
for (const absent of ['summary', 'historyProvider', 'historyRecords', 'historySha256', 'namingStatus', 'namingEvidenceSha256', 'namingSemanticSha256', 'resultStep', 'resultBrep', 'shape', 'history']) if (absent in attempt) fail(`${expected.pair} OCCT attempt ${attemptIndex} fabricates ${absent}.`)
}
} else {
if (rejection.kernelRestoredExactly !== true || rejection.kernelAttempts?.length !== 3) fail(`${expected.pair} OCCT diagnostic trajectory is incomplete.`)
for (const [attemptIndex, attempt] of rejection.kernelAttempts.entries()) if (attempt.operation !== secondOperation || attempt.builder !== expected.secondBuilder || attempt.inputCount !== expected.secondInputCount || attempt.parameter !== expected.mutationParameter || attempt.parameterValue !== expected.mutationTrajectory[attemptIndex] || attempt.runtime !== 'occt-native' || attempt.outcome !== expected.kernelOutcome || attempt.resultProduced !== true || attempt.shapeProduced !== true || attempt.validShapeProduced !== (expected.kernelOutcome === 'accepted') || attempt.historyProduced !== true || attempt.historyProvider !== 'occt-native' || attempt.summary?.isValid !== (expected.kernelOutcome === 'accepted') || attempt.historyRecords < 1 || !attempt.historySha256 || attempt.authority !== (expected.kernelOutcome === 'accepted' ? 'kernel-superset-only' : 'kernel-invalid-result')) fail(`${expected.pair} OCCT compatibility attempt ${attemptIndex} is invalid.`)
}
const persistence = rejection.persistence
const diagnostic = persistence?.diagnostic
if (persistence?.status !== 'pass' || persistence.decision !== 'rejected' || persistence.pair !== entry.pair || persistence.operation !== secondOperation || persistence.nativeTypeId !== expected.nativeTypeId || persistence.freecadVersion !== '1.1.1' || !Object.values(persistence.checks ?? {}).every(Boolean)) fail(`${expected.pair} FCStd rejection evidence is incomplete.`)
if (diagnostic?.nativeTypeId !== expected.nativeTypeId || diagnostic.parameter?.name !== expected.mutationParameter || diagnostic.parameter.value !== expected.mutationTrajectory[0] || diagnostic.inputShape?.solids !== 1 || diagnostic.recomputeResult !== true || diagnostic.shapeNull !== true || diagnostic.shapeValid !== false || !diagnostic.state?.includes('Invalid') || diagnostic.statusString === 'Valid' || !diagnostic.objects?.some(({ name, typeId }) => name === 'PairResult' && typeId === expected.nativeTypeId)) fail(`${expected.pair} did not capture a real rejected FreeCAD recompute.`)
if (expected.rejectionAuthority === 'freecad-document' && diagnostic.statusString !== expected.freeCadDiagnostic) fail(`${expected.pair} FreeCAD profile diagnostic changed.`)
if (persistence.source?.shape?.solids !== 1 || !persistence.source.shape.brepSha256 || JSON.stringify(persistence.rollback?.before) !== JSON.stringify(persistence.rollback?.after)) fail(`${expected.pair} abort did not restore the source object set and Shape.`)
const phases = persistence.phases
if (JSON.stringify(phases?.initial) !== JSON.stringify(phases?.reopened) || JSON.stringify(phases?.initial) !== JSON.stringify(phases?.resaved) || phases.initial.objects?.length !== persistence.rollback.before.objects.length || phases.initial.objects?.some(({ typeId }) => typeId === 'PartDesign::Body' || typeId === 'PartDesign::Pad' || typeId === 'Part::Loft')) fail(`${expected.pair} polluted the clean FCStd save/reopen/resave chain.`)
continue
}
if (entry.second?.operation !== secondOperation || entry.second.builder !== expected.secondBuilder || entry.second.inputCount !== expected.secondInputCount || entry.second.historyProvider !== 'occt-native') fail(`${expected.pair} accepted second operation is invalid.`)
if (entry.second.summary?.isValid !== true || entry.second.historyRecords < 1 || entry.mutation?.changed !== true || entry.mutation?.restoredExactly !== true) fail(`${expected.pair} builder or mutation evidence is incomplete.`)
if (entry.mutation.scope !== 'second-operation-only' || entry.mutation.parameter !== expected.mutationParameter || JSON.stringify(entry.mutation.trajectory) !== JSON.stringify(expected.mutationTrajectory) || entry.mutation.beforeValue !== expected.mutationTrajectory[0] || entry.mutation.editedValue !== expected.mutationTrajectory[1] || entry.mutation.restoredValue !== expected.mutationTrajectory[2]) fail(`${expected.pair} mutation trajectory is invalid.`) if (entry.mutation.scope !== 'second-operation-only' || entry.mutation.parameter !== expected.mutationParameter || JSON.stringify(entry.mutation.trajectory) !== JSON.stringify(expected.mutationTrajectory) || entry.mutation.beforeValue !== expected.mutationTrajectory[0] || entry.mutation.editedValue !== expected.mutationTrajectory[1] || entry.mutation.restoredValue !== expected.mutationTrajectory[2]) fail(`${expected.pair} mutation trajectory is invalid.`)
if (entry.mutation.editedEvidence?.operation !== secondOperation || entry.mutation.restoredEvidence?.operation !== secondOperation || entry.mutation.editedEvidence?.builder !== expected.secondBuilder || entry.mutation.restoredEvidence?.builder !== expected.secondBuilder || entry.mutation.editedEvidence?.inputCount !== expected.secondInputCount || entry.mutation.restoredEvidence?.inputCount !== expected.secondInputCount || entry.mutation.editedEvidence?.historyProvider !== 'occt-native' || entry.mutation.restoredEvidence?.historyProvider !== 'occt-native') fail(`${expected.pair} mutation history provider is invalid.`) if (entry.mutation.editedEvidence?.operation !== secondOperation || entry.mutation.restoredEvidence?.operation !== secondOperation || entry.mutation.editedEvidence?.builder !== expected.secondBuilder || entry.mutation.restoredEvidence?.builder !== expected.secondBuilder || entry.mutation.editedEvidence?.inputCount !== expected.secondInputCount || entry.mutation.restoredEvidence?.inputCount !== expected.secondInputCount || entry.mutation.editedEvidence?.historyProvider !== 'occt-native' || entry.mutation.restoredEvidence?.historyProvider !== 'occt-native') fail(`${expected.pair} mutation history provider is invalid.`)
if (entry.second.namingSemanticSha256 !== entry.mutation.restoredEvidence?.namingSemanticSha256) fail(`${expected.pair} semantic naming evidence did not restore to nominal.`) if (entry.second.namingSemanticSha256 !== entry.mutation.restoredEvidence?.namingSemanticSha256) fail(`${expected.pair} semantic naming evidence did not restore to nominal.`)
if (entry.naming?.upstreamEvidenceRestored !== true || entry.naming?.downstreamEvidence !== true || entry.naming?.jsonRoundtripStable !== true) fail(`${expected.pair} naming evidence is incomplete.`) if (entry.naming?.upstreamEvidenceRestored !== true || entry.naming?.downstreamEvidence !== true || entry.naming?.jsonRoundtripStable !== true) fail(`${expected.pair} naming evidence is incomplete.`)
if (entry.persistence?.status !== 'pass' || entry.persistence.pair !== entry.pair || entry.persistence.freecadVersion !== '1.1.1' || !Object.values(entry.persistence.checks ?? {}).every(Boolean)) fail(`${expected.pair} FCStd persistence evidence is incomplete.`) if (entry.persistence?.status !== 'pass' || entry.persistence.decision !== 'accepted' || entry.persistence.pair !== entry.pair || entry.persistence.freecadVersion !== '1.1.1' || !Object.values(entry.persistence.checks ?? {}).every(Boolean)) fail(`${expected.pair} FCStd persistence evidence is incomplete.`)
const phases = entry.persistence.phases const phases = entry.persistence.phases
if (JSON.stringify(phases?.initial) !== JSON.stringify(phases?.reopened) || JSON.stringify(phases?.initial) !== JSON.stringify(phases?.resaved) || phases.initial.namingEvidenceSha256 !== entry.second.namingEvidenceSha256) fail(`${expected.pair} FCStd phases changed Shape or naming evidence.`) if (JSON.stringify(phases?.initial) !== JSON.stringify(phases?.reopened) || JSON.stringify(phases?.initial) !== JSON.stringify(phases?.resaved) || phases.initial.namingEvidenceSha256 !== entry.second.namingEvidenceSha256) fail(`${expected.pair} FCStd phases changed Shape or naming evidence.`)
} }
const expectedSummary = { registeredOperations: 19, orderedPairs: 361, classifiedPairs: 4, accepted: 4, rejected: 0, unknown: 357 } const expectedSummary = { registeredOperations: 19, orderedPairs: 361, classifiedPairs: 8, accepted: 5, rejected: 3, unknown: 353 }
if (JSON.stringify(report.summary) !== JSON.stringify(expectedSummary)) fail('summary is inconsistent.') if (JSON.stringify(report.summary) !== JSON.stringify(expectedSummary)) fail('summary is inconsistent.')
console.log(JSON.stringify({ status: 'freecad-ordered-operation-pair-classification-pass', completedTasks: report.classifications.map(({ taskId }) => taskId), pairs: report.classifications.map(({ pair, classification }) => ({ pair, classification })), nativeBuilderRuns: report.classifications.length * 4, fcstdPhases: report.classifications.length * 3, remainingPairs: report.summary.unknown }, null, 2)) console.log(JSON.stringify({ status: 'freecad-ordered-operation-pair-classification-pass', completedTasks: report.classifications.map(({ taskId }) => taskId), pairs: report.classifications.map(({ pair, classification }) => ({ pair, classification })), nativeBuilderRuns: report.classifications.length * 4, fcstdPhases: report.classifications.length * 3, remainingPairs: report.summary.unknown }, null, 2))

View File

@@ -14,12 +14,11 @@ def version_text():
return ".".join(str(value) for value in App.Version()[:3]) return ".".join(str(value) for value in App.Version()[:3])
def shape_snapshot(feature): def shape_snapshot_from_shape(shape, include_brep=False):
shape = feature.Shape
if shape.isNull(): if shape.isNull():
raise RuntimeError("ordered pair produced a null Shape") raise RuntimeError("ordered pair produced a null Shape")
bounds = shape.BoundBox bounds = shape.BoundBox
return { snapshot = {
"valid": bool(shape.isValid()), "valid": bool(shape.isValid()),
"solids": len(shape.Solids), "solids": len(shape.Solids),
"faces": len(shape.Faces), "faces": len(shape.Faces),
@@ -29,6 +28,15 @@ def shape_snapshot(feature):
"area": round(float(shape.Area), 7), "area": round(float(shape.Area), 7),
"bounds": [round(float(value), 7) for value in (bounds.XMin, bounds.YMin, bounds.ZMin, bounds.XMax, bounds.YMax, bounds.ZMax)], "bounds": [round(float(value), 7) for value in (bounds.XMin, bounds.YMin, bounds.ZMin, bounds.XMax, bounds.YMax, bounds.ZMax)],
} }
if include_brep:
brep = shape.exportBrepToString()
brep_bytes = brep.encode("utf-8") if isinstance(brep, str) else bytes(brep)
snapshot["brepSha256"] = hashlib.sha256(brep_bytes).hexdigest()
return snapshot
def shape_snapshot(feature):
return shape_snapshot_from_shape(feature.Shape)
def phase_snapshot(document): def phase_snapshot(document):
@@ -44,7 +52,35 @@ def phase_snapshot(document):
} }
def collect(): def document_snapshot(document):
return {
"objects": [{
"name": obj.Name,
"typeId": obj.TypeId,
**({"shape": shape_snapshot(obj)} if hasattr(obj, "Shape") and not obj.Shape.isNull() else {}),
} for obj in document.Objects],
}
def object_set_snapshot(document):
return [{"name": obj.Name, "typeId": obj.TypeId} for obj in document.Objects]
def link_snapshot(value):
linked = value
subelements = []
if isinstance(value, tuple):
linked = value[0]
if len(value) > 1:
subelements = [str(item) for item in value[1]]
return {
"name": linked.Name if linked is not None else None,
"typeId": linked.TypeId if linked is not None else None,
"subelements": subelements,
}
def collect_accepted():
step_path = os.environ["FREECAD_PAIR_STEP_PATH"] step_path = os.environ["FREECAD_PAIR_STEP_PATH"]
evidence_path = os.environ["FREECAD_PAIR_EVIDENCE_PATH"] evidence_path = os.environ["FREECAD_PAIR_EVIDENCE_PATH"]
pair = os.environ["FREECAD_ORDERED_PAIR"] pair = os.environ["FREECAD_ORDERED_PAIR"]
@@ -85,6 +121,7 @@ def collect():
"freecadVersion": version_text(), "freecadVersion": version_text(),
"gitCommit": FREECAD_COMMIT, "gitCommit": FREECAD_COMMIT,
"pair": pair, "pair": pair,
"decision": "accepted",
"phases": {"initial": initial, "reopened": reopened_snapshot, "resaved": resaved_snapshot}, "phases": {"initial": initial, "reopened": reopened_snapshot, "resaved": resaved_snapshot},
"checks": { "checks": {
"shapeStable": initial["shape"] == reopened_snapshot["shape"] and initial["shape"] == resaved_snapshot["shape"], "shapeStable": initial["shape"] == reopened_snapshot["shape"] and initial["shape"] == resaved_snapshot["shape"],
@@ -97,8 +134,162 @@ def collect():
App.closeDocument(name) App.closeDocument(name)
def collect_rejected():
step_path = os.environ["FREECAD_PAIR_STEP_PATH"]
pair = os.environ["FREECAD_ORDERED_PAIR"]
operation = pair.split("->", 1)[1]
output_directory = os.environ["FREECAD_PAIR_OUTPUT_DIRECTORY"]
document = App.newDocument("FreeCadOrderedPairRejection")
try: try:
report = collect() Import.insert(step_path, document.Name)
document.recompute()
source_objects = [obj for obj in document.Objects if hasattr(obj, "Shape") and not obj.Shape.isNull()]
if len(source_objects) != 1:
raise RuntimeError("Fuse STEP import must produce exactly one Shape object")
source = source_objects[0]
if len(source.Shape.Solids) != 1:
raise RuntimeError("Fuse STEP import did not produce one Solid")
if source.Shape.isNull():
raise RuntimeError("STEP import produced no Shape")
source_name = source.Name
source_type_id = source.TypeId
document.UndoMode = 1
baseline = document_snapshot(document)
baseline_objects = object_set_snapshot(document)
baseline_source_shape = shape_snapshot_from_shape(source.Shape, include_brep=True)
diagnostic = {
"exceptionType": None,
"exception": None,
"recomputeResult": None,
"statusString": None,
"state": [],
"shapeNull": True,
"shapeValid": False,
}
document.openTransaction("ordered-pair-rejection")
try:
if operation == "pad":
body = document.addObject("PartDesign::Body", "Body")
feature = body.newObject("PartDesign::Pad", "PairResult")
feature.Profile = source
feature.Length = 5.0
diagnostic["parameter"] = {"name": "length", "value": round(float(feature.Length), 7)}
diagnostic["input"] = link_snapshot(feature.Profile)
elif operation == "loft":
section = document.addObject("Part::Feature", "Section")
section.Shape = Part.makePolygon([
App.Vector(0.0, 0.0, 15.0),
App.Vector(2.0, 0.0, 15.0),
App.Vector(2.0, 3.0, 15.0),
App.Vector(0.0, 3.0, 15.0),
App.Vector(0.0, 0.0, 15.0),
])
feature = document.addObject("Part::Loft", "PairResult")
feature.Sections = [source, section]
feature.Solid = True
feature.Ruled = False
diagnostic["parameter"] = {"name": "ruled", "value": bool(feature.Ruled)}
diagnostic["input"] = {
"sections": [{"name": item.Name, "typeId": item.TypeId} for item in feature.Sections],
}
elif operation == "pipe":
spine = document.addObject("Part::Feature", "Spine")
spine.Shape = Part.makePolygon([App.Vector(0.0, 0.0, 0.0), App.Vector(0.0, 0.0, 15.0)])
feature = document.addObject("Part::Sweep", "PairResult")
feature.Sections = [source]
feature.Spine = spine
feature.Solid = True
feature.Frenet = False
diagnostic["parameter"] = {"name": "spineLength", "value": 15.0}
diagnostic["input"] = {
"sections": [{"name": item.Name, "typeId": item.TypeId} for item in feature.Sections],
"spine": link_snapshot(feature.Spine),
}
else:
raise RuntimeError("Unsupported rejected ordered-pair operation: " + operation)
diagnostic["nativeTypeId"] = feature.TypeId
diagnostic["inputShape"] = shape_snapshot_from_shape(source.Shape)
diagnostic["objects"] = object_set_snapshot(document)
try:
diagnostic["recomputeResult"] = bool(document.recompute())
except Exception as error:
diagnostic["exceptionType"] = type(error).__name__
diagnostic["exception"] = str(error)
diagnostic["statusString"] = str(feature.getStatusString())
diagnostic["state"] = [str(value) for value in feature.State]
diagnostic["shapeNull"] = bool(feature.Shape.isNull())
diagnostic["shapeValid"] = False if feature.Shape.isNull() else bool(feature.Shape.isValid())
finally:
document.abortTransaction()
document.recompute()
initial = document_snapshot(document)
restored_objects = object_set_snapshot(document)
restored_source = document.getObject(source_name)
if restored_source is None or not hasattr(restored_source, "Shape") or restored_source.Shape.isNull():
raise RuntimeError("Fuse source Shape is absent after transaction abort")
restored_source_shape = shape_snapshot_from_shape(restored_source.Shape, include_brep=True)
initial_path = os.path.join(output_directory, "ordered-pair-rejection.FCStd")
resaved_path = os.path.join(output_directory, "ordered-pair-rejection-resaved.FCStd")
document.saveAs(initial_path)
App.closeDocument(document.Name)
reopened = App.openDocument(initial_path)
reopened.recompute()
reopened_snapshot = document_snapshot(reopened)
reopened.saveAs(resaved_path)
App.closeDocument(reopened.Name)
resaved = App.openDocument(resaved_path)
resaved.recompute()
resaved_snapshot = document_snapshot(resaved)
App.closeDocument(resaved.Name)
feature_rejected = diagnostic["shapeNull"] and (
diagnostic["exceptionType"] is not None
or diagnostic["recomputeResult"] is False
or diagnostic["statusString"] != "Valid"
or "Error" in diagnostic["state"]
or "Invalid" in diagnostic["state"]
)
expected_type_id = {"pad": "PartDesign::Pad", "loft": "Part::Loft", "pipe": "Part::Sweep"}[operation]
input_names = [diagnostic.get("input", {}).get("name")] if operation == "pad" else [item["name"] for item in diagnostic.get("input", {}).get("sections", [])]
checks = {
"nativeFeatureConstructed": diagnostic.get("nativeTypeId") == expected_type_id,
"fuseSolidUsedAsInput": source_name in input_names and diagnostic.get("inputShape", {}).get("solids") == 1,
"recomputeStatusCaptured": diagnostic["recomputeResult"] is not None or diagnostic["exceptionType"] is not None,
"nativeFeatureRejected": feature_rejected,
"objectSetRestored": baseline_objects == restored_objects,
"sourceShapeRestored": baseline_source_shape == restored_source_shape,
"documentUnpolluted": baseline == initial,
"documentStable": initial == reopened_snapshot and initial == resaved_snapshot,
}
return {
"schemaVersion": 1,
"baselineId": "freecad-1.1.1-ordered-operation-pair-resave",
"freecadVersion": version_text(),
"gitCommit": FREECAD_COMMIT,
"pair": pair,
"decision": "rejected",
"operation": operation,
"nativeTypeId": expected_type_id,
"source": {
"name": source_name,
"typeId": source_type_id,
"shape": baseline_source_shape,
},
"diagnostic": diagnostic,
"rollback": {
"before": {"objects": baseline_objects, "sourceShape": baseline_source_shape},
"after": {"objects": restored_objects, "sourceShape": restored_source_shape},
},
"phases": {"initial": initial, "reopened": reopened_snapshot, "resaved": resaved_snapshot},
"checks": checks,
"status": "pass" if all(checks.values()) else "failed",
}
finally:
for name in list(App.listDocuments().keys()):
App.closeDocument(name)
try:
report = collect_rejected() if os.environ.get("FREECAD_PAIR_DECISION") == "rejected" else collect_accepted()
except Exception as error: except Exception as error:
report = { report = {
"schemaVersion": 1, "schemaVersion": 1,

View File

@@ -3,8 +3,9 @@ import { resolve } from 'node:path'
const root = resolve(new URL('..', import.meta.url).pathname) const root = resolve(new URL('..', import.meta.url).pathname)
const outputPath = resolve(root, 'config/freecad-active-work-queue.json') const outputPath = resolve(root, 'config/freecad-active-work-queue.json')
const statusOutputPath = resolve(root, 'docs/freecad-active-work-status.generated.zh-CN.md')
const load = (path) => readFile(resolve(root, path), 'utf8').then(JSON.parse) const load = (path) => readFile(resolve(root, path), 'utf8').then(JSON.parse)
const [gui, workflowPlan, composite, correlation, production, recoveredNaming, productionDriftClassification, orderedPairClassification] = await Promise.all([ const [gui, workflowPlan, composite, correlation, production, recoveredNaming, productionDriftClassification, orderedPairClassification, propertySemantics, exactPlan, platformCoverage, followUpProgress] = await Promise.all([
load('.cache/freecad/reference-desktop-gui-commands.json'), load('.cache/freecad/reference-desktop-gui-commands.json'),
load('config/freecad-gui-workflow-plan.json'), load('config/freecad-gui-workflow-plan.json'),
load('config/freecad-composite-history-elementmap-oracle.json'), load('config/freecad-composite-history-elementmap-oracle.json'),
@@ -13,6 +14,10 @@ const [gui, workflowPlan, composite, correlation, production, recoveredNaming, p
load('config/freecad-recovered-naming-classification.json'), load('config/freecad-recovered-naming-classification.json'),
load('config/freecad-production-drift-classification.json'), load('config/freecad-production-drift-classification.json'),
load('config/freecad-ordered-operation-pair-classification.json'), load('config/freecad-ordered-operation-pair-classification.json'),
load('config/freecad-native-property-semantics.json'),
load('config/freecad-web-exact-parity-plan.json'),
load('config/platform-module-coverage.json'),
load('config/freecad-follow-up-task-progress.json'),
]) ])
const guiShardTasks = gui.guiCommands.mergedShards.map((shard, index) => ({ const guiShardTasks = gui.guiCommands.mergedShards.map((shard, index) => ({
@@ -108,6 +113,127 @@ const transitionTasks = production.operations.flatMap((from) => production.opera
} }
})) }))
const completionSteps = [
{ id: 'A', title: 'Lock native inventory and preconditions', exit: 'the native TypeId, inputs, defaults, dependencies and applicability are machine recorded' },
{ id: 'B', title: 'Capture native success evidence', exit: 'nominal and boundary successes record values, Shape, topology, status and diagnostics' },
{ id: 'C', title: 'Capture native failure and cancel evidence', exit: 'invalid input, disabled state, cancellation and document non-pollution are reproducible' },
{ id: 'D', title: 'Capture parameter mutation and restore evidence', exit: 'edit, recompute and restore return geometry and semantics to the classified native state' },
{ id: 'E', title: 'Implement the Facade capability', exit: 'the production Facade uses the declared native or explicit proxy path without hidden fallback' },
{ id: 'F', title: 'Close transaction and recovery behavior', exit: 'abort, undo, redo, stale, failure and resource ownership return to the expected state' },
{ id: 'G', title: 'Close FCStd save, reopen and resave', exit: 'FreeCAD to Web to FreeCAD persistence has zero unknown semantic drift' },
{ id: 'H', title: 'Replay in a real browser', exit: 'Chrome Worker, OPFS, UI state and resource release evidence pass' },
{ id: 'I', title: 'Promote capability and synchronize blockers', exit: 'all eight evidence tasks pass and machine reports alone advance the capability level' },
]
const slug = (value) => value.replaceAll('::', '-').replace(/[^A-Za-z0-9]+/g, '-').replace(/^-|-$/g, '').toLowerCase()
const makeCapabilityTasks = (prefix, capabilities, firstDependency) => {
const tasks = []
for (const capability of capabilities) {
for (const step of completionSteps) {
const id = `${prefix}-${slug(capability.id)}-${step.id}`
const previous = tasks.at(-1)?.id
tasks.push({
id,
title: `${step.title}: ${capability.title}`,
status: 'pending',
dependencies: [previous ?? firstDependency],
capability: capability.id,
phase: step.id,
...(capability.recordCount === undefined ? {} : { recordCount: capability.recordCount }),
evidenceRequired: step.id === 'I' ? ['all-prior-phases-pass', 'focused-check', 'exact-blocker-sync'] : ['one-native-or-production-artifact', 'one-focused-check'],
exit: step.exit,
})
}
}
return tasks
}
const propertyCapabilities = propertySemantics.types
.filter(({ support }) => support === 'opaque-fcstd-proxy')
.map(({ typeId, recordCount }) => ({ id: typeId, title: typeId, recordCount }))
if (propertyCapabilities.length !== propertySemantics.supportSummary?.['opaque-fcstd-proxy']?.typeCount) throw new Error('Opaque Property capability inventory is inconsistent.')
const followUpMilestoneDefinitions = [
{
id: 'PROPERTY-CODECS', exactTasks: ['EX-DOC-01'], title: 'Close every opaque Property type', prefix: 'PROP', capabilities: propertyCapabilities,
},
{
id: 'DOCUMENT-SEMANTICS', exactTasks: ['EX-DOC-02', 'EX-DOC-03', 'EX-DOC-04'], title: 'Close document, transaction and lifecycle semantics', prefix: 'DOC', capabilities: [
['observer-object-order', 'Object add/remove/rename observer order'],
['observer-property-order', 'Property add/remove/rename/change observer order'],
['nested-transactions', 'Nested commit/abort/undo/redo transactions'],
['dag-partial-recompute', 'DAG dirty propagation and partial recompute'],
['recompute-failure-recovery', 'Last-valid Shape and diagnostic recovery'],
['partial-document-load', 'Partial document load and PartialTrigger'],
['object-lifecycle', 'Copy/clone/delete/relink/group lifecycle'],
['extension-lifecycle', 'Extension schema, migration and unknown preservation'],
['feature-python-boundary', 'FeaturePython proxy, signature and execution boundary'],
['multi-document-links', 'External links, close/reopen, merge and recovery'],
].map(([id, title]) => ({ id, title })),
},
{
id: 'CORE-MODELING', exactTasks: ['EX-KER-01', 'EX-SK-01', 'EX-SK-02', 'EX-PART-01', 'EX-PD-01'], title: 'Close core modeling operations', prefix: 'CORE', capabilities: production.operations.map((operation) => ({ id: operation, title: `core operation ${operation}` })),
},
{
id: 'FCSTD-AND-FORMATS', exactTasks: ['EX-FC-01', 'EX-FMT-01', 'EX-FMT-02', 'EX-FMT-03', 'EX-FMT-04'], title: 'Close FCStd and interchange formats', prefix: 'IO', capabilities: [
['fcstd-readonly-roundtrip', 'FCStd unchanged object and Property round-trip'],
['fcstd-edited-roundtrip', 'Web edit, FreeCAD reopen/resave and Web reopen'],
['fcstd-shape-resources', 'Shape, ElementMap2, StringHasher, Expression and GuiDocument resources'],
['fcstd-unknown-resources', 'Unknown XML, BRep, script and Extension preservation'],
['fcstd-adversarial-input', 'Corrupt ZIP, traversal, compression limit and cancellation'],
['step', 'STEP units, colors, layers, names, assemblies and metadata'],
['iges', 'IGES units, colors, layers, names, assemblies and metadata'],
['brep', 'BREP tolerance, topology and editable round-trip'],
].map(([id, title]) => ({ id, title })),
},
{
id: 'GUI-CLOSURE', exactTasks: ['EX-UI-01', 'EX-UI-02', 'EX-UI-03', 'EX-UI-04', 'EX-UI-05'], title: 'Close full GUI behavior', prefix: 'GUI', capabilities: [
'application-shell', 'menus', 'toolbars', 'workbench-switching', 'mdi', 'combo-view', 'model-tree', 'task-panels', 'data-view-editors', 'dialogs', 'shortcuts', 'context-menus', 'status-report-jobs', '3d-selection', 'responsive-accessibility',
].map((id) => ({ id, title: id.replaceAll('-', ' ') })),
},
{
id: 'DOCUMENT-ENGINEERING-WORKBENCHES', exactTasks: ['EX-DOCWB-01', 'EX-ASM-01', 'EX-BIM-01', 'EX-SURF-01', 'EX-MESH-01', 'EX-MAT-01'], title: 'Close document and engineering workbenches', prefix: 'WB', capabilities: [
'draft', 'spreadsheet', 'techdraw', 'plot', 'cross-workbench-document', 'assembly', 'bim-ifc', 'surface', 'mesh-meshpart', 'material',
].map((id) => ({ id, title: id.replaceAll('-', ' ') })),
},
{
id: 'PLATFORM-SCRIPT-PROXY', exactTasks: ['EX-FEM-01', 'EX-CAM-01', 'EX-ROBOT-01', 'EX-DATA-01', 'EX-INSPECT-01', 'EX-SCRIPT-01', 'EX-ADDON-01', 'EX-PROXY-01', 'EX-PLATFORM-01'], title: 'Close platform, scripting and proxy capabilities', prefix: 'PLAT', capabilities: [
'fem', 'cam', 'robot', 'points-reverseengineering', 'inspection-measure', 'python-macro', 'addon', 'cloud', 'help', 'idf', 'jt', 'openscad', 'platform-boundaries',
].map((id) => ({ id, title: id.replaceAll('-', ' ') })),
},
{
id: 'QA-RELEASE', exactTasks: ['EX-QA-01', 'EX-QA-02', 'EX-QA-03', 'EX-REL-01'], title: 'Close QA and exact release', prefix: 'REL', capabilities: [
['chrome-baseline', 'Chrome locked baseline'],
['browser-matrix', 'Firefox, Safari and explicit unsupported boundaries'],
['os-gpu-matrix', 'Windows, macOS, Linux and GPU/software rendering'],
['accessibility-locale', 'Keyboard, screen reader, locale, long text and narrow viewport'],
['fuzz-soak-recovery', 'Fuzz, 1000 recomputes, long session, crash, migration and rollback'],
['resource-performance', 'Performance and WASM/JS/GPU/OPFS release accounting'],
['signed-release', 'SBOM, licenses, signing, offline install and rollback drill'],
['exact-promotion', '52-task, 34-module and zero-unknown promotion report'],
].map(([id, title]) => ({ id, title })),
},
]
let followUpDependency = transitionTasks.at(-1).id
const followUpMilestones = followUpMilestoneDefinitions.map((definition) => {
const tasks = makeCapabilityTasks(definition.prefix, definition.capabilities, followUpDependency)
followUpDependency = tasks.at(-1).id
return { id: definition.id, title: definition.title, exactTasks: definition.exactTasks, status: 'pending', tasks }
})
const followUpTasks = followUpMilestones.flatMap(({ tasks }) => tasks)
if (followUpProgress.schemaVersion !== 1 || followUpProgress.baseline?.freecadVersion !== '1.1.1' || followUpProgress.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || !Array.isArray(followUpProgress.completedTasks)) throw new Error('Follow-up task progress ledger is invalid.')
const followUpTaskIds = new Set(followUpTasks.map(({ id }) => id))
const completedFollowUpIds = new Set()
for (const entry of followUpProgress.completedTasks) {
if (!followUpTaskIds.has(entry.id) || completedFollowUpIds.has(entry.id) || !Array.isArray(entry.evidence) || entry.evidence.length === 0) throw new Error(`Follow-up progress entry ${entry.id} is invalid.`)
completedFollowUpIds.add(entry.id)
}
const completedFollowUpPrefix = followUpTasks.findIndex(({ id }) => !completedFollowUpIds.has(id))
const completedFollowUpCount = completedFollowUpPrefix < 0 ? followUpTasks.length : completedFollowUpPrefix
if (followUpTasks.slice(completedFollowUpCount).some(({ id }) => completedFollowUpIds.has(id))) throw new Error('Follow-up completed tasks must form one contiguous serial prefix.')
const orderedPairsClosed = transitionTasks.every(({ status }) => status === 'completed')
for (const [index, task] of followUpTasks.entries()) task.status = index < completedFollowUpCount ? 'completed' : orderedPairsClosed && index === completedFollowUpCount ? 'in_progress' : 'pending'
for (const milestone of followUpMilestones) milestone.status = milestone.tasks.every(({ status }) => status === 'completed') ? 'completed' : milestone.tasks.some(({ status }) => status === 'in_progress') ? 'in_progress' : 'pending'
const setupTasks = [ const setupTasks = [
{ id: 'ORA-GUI-SETUP-000', title: 'Split GUI state probing from object/property probing', status: 'completed' }, { id: 'ORA-GUI-SETUP-000', title: 'Split GUI state probing from object/property probing', status: 'completed' },
{ id: 'ORA-GUI-SETUP-001', title: 'Diagnose the BIM first-run modal block', status: 'completed', dependencies: ['ORA-GUI-SETUP-000'] }, { id: 'ORA-GUI-SETUP-001', title: 'Diagnose the BIM first-run modal block', status: 'completed', dependencies: ['ORA-GUI-SETUP-000'] },
@@ -122,12 +248,18 @@ const milestones = [
{ id: 'ORA-GUI-BASELINE', exactTask: 'EX-ORA-01', status: 'completed', tasks: [...setupTasks, ...guiShardTasks, ...closureTasks] }, { id: 'ORA-GUI-BASELINE', exactTask: 'EX-ORA-01', status: 'completed', tasks: [...setupTasks, ...guiShardTasks, ...closureTasks] },
{ id: 'ORA-GUI-WORKFLOWS', exactTask: 'EX-ORA-01', status: workflowClosed ? 'completed' : 'in_progress', tasks: workflowTasks }, { id: 'ORA-GUI-WORKFLOWS', exactTask: 'EX-ORA-01', status: workflowClosed ? 'completed' : 'in_progress', tasks: workflowTasks },
{ id: 'TSN-RECOVERY-DRIFT', exactTask: 'EX-TSN-04', status: productionDriftClosed ? 'completed' : workflowClosed ? 'in_progress' : 'pending', tasks: [...driftTasks, ...productionDriftTasks] }, { id: 'TSN-RECOVERY-DRIFT', exactTask: 'EX-TSN-04', status: productionDriftClosed ? 'completed' : workflowClosed ? 'in_progress' : 'pending', tasks: [...driftTasks, ...productionDriftTasks] },
{ id: 'TSN-ORDERED-PAIRS', exactTask: 'EX-TSN-04', status: productionDriftClosed ? 'in_progress' : 'pending', tasks: transitionTasks }, { id: 'TSN-ORDERED-PAIRS', exactTask: 'EX-TSN-04', status: orderedPairsClosed ? 'completed' : productionDriftClosed ? 'in_progress' : 'pending', tasks: transitionTasks },
...followUpMilestones,
] ]
const allTasks = milestones.flatMap((milestone) => milestone.tasks) const allTasks = milestones.flatMap((milestone) => milestone.tasks)
const allTaskIds = new Set(allTasks.map(({ id }) => id))
if (allTaskIds.size !== allTasks.length) throw new Error('FreeCAD active work queue contains duplicate task IDs.')
for (const milestone of followUpMilestones) for (const exactTask of milestone.exactTasks) if (!exactPlan.programs.some(({ tasks }) => tasks.some(({ id }) => id === exactTask))) throw new Error(`${milestone.id} references unknown exact task ${exactTask}.`)
for (const task of allTasks) for (const dependency of task.dependencies ?? []) if (!allTaskIds.has(dependency)) throw new Error(`${task.id} depends on unknown task ${dependency}.`)
const count = (status) => allTasks.filter((task) => task.status === status).length const count = (status) => allTasks.filter((task) => task.status === status).length
const nextTask = allTasks.find((task) => task.status === 'in_progress')?.id const nextTask = allTasks.find((task) => task.status === 'in_progress')?.id
if (!nextTask) throw new Error('FreeCAD active work queue requires one in-progress task.') if (!nextTask) throw new Error('FreeCAD active work queue requires one in-progress task.')
if (count('in_progress') !== 1) throw new Error('FreeCAD active work queue requires exactly one in-progress task.')
const queue = { const queue = {
schemaVersion: 1, schemaVersion: 1,
baseline: { freecadVersion: '1.1.1', commit: '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' }, baseline: { freecadVersion: '1.1.1', commit: '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' },
@@ -138,17 +270,46 @@ const queue = {
unit: 'one artifact, one focused check, one explicit exit condition', unit: 'one artifact, one focused check, one explicit exit condition',
transitionUnit: 'one ordered operation pair', transitionUnit: 'one ordered operation pair',
driftUnit: 'one native case', driftUnit: 'one native case',
capabilityDefinition: completionSteps.map(({ id, title }) => `${id}: ${title}`),
followUpProgress: 'config/freecad-follow-up-task-progress.json',
}, },
nextTask, nextTask,
summary: { tasks: allTasks.length, completed: count('completed'), inProgress: count('in_progress'), pending: count('pending') }, summary: { tasks: allTasks.length, completed: count('completed'), inProgress: count('in_progress'), pending: count('pending') },
milestones, milestones,
} }
const content = `${JSON.stringify(queue, null, 2)}\n` const content = `${JSON.stringify(queue, null, 2)}\n`
const exactTasks = exactPlan.programs.flatMap(({ tasks }) => tasks)
const moduleCounts = Object.fromEntries(['exact', 'compatible', 'proxy', 'development'].map((level) => [level, platformCoverage.modules.filter((module) => module.level === level).length]))
const milestoneRows = queue.milestones.map((milestone) => {
const taskCount = (status) => milestone.tasks.filter((task) => task.status === status).length
const active = milestone.tasks.find((task) => task.status === 'in_progress')?.id ?? '-'
return `| \`${milestone.id}\` | ${milestone.tasks.length} | ${taskCount('completed')} | ${taskCount('in_progress')} | ${taskCount('pending')} | \`${active}\` |`
})
const statusContent = `<!-- Generated by scripts/generate-freecad-active-work-queue.mjs. Do not edit. -->
# FreeCAD 后续工作机器状态
生成日期:${queue.updatedAt}。基线FreeCAD ${queue.baseline.freecadVersion} / \`${queue.baseline.commit}\`
- 唯一活动任务:\`${queue.nextTask}\`
- 微任务:${queue.summary.tasks} total / ${queue.summary.completed} completed / ${queue.summary.inProgress} in_progress / ${queue.summary.pending} pending
- 有序操作对:${coveredTransitions.size}/${transitionTasks.length} classified / ${transitionTasks.length - coveredTransitions.size} unknown
- Property${propertySemantics.supportSummary['native-editable-codec'].typeCount + propertySemantics.supportSummary['native-specialized-codec'].typeCount}/${propertySemantics.types.length} native types / ${propertySemantics.supportSummary['opaque-fcstd-proxy'].typeCount} opaque types
- exact 任务:${exactTasks.filter(({ status }) => status === 'completed').length}/${exactTasks.length} completed
- 模块能力:${moduleCounts.exact} exact / ${moduleCounts.compatible} compatible / ${moduleCounts.proxy} proxy / ${moduleCounts.development} development
| 里程碑 | 任务数 | completed | in_progress | pending | 当前任务 |
| --- | ---: | ---: | ---: | ---: | --- |
${milestoneRows.join('\n')}
详细任务、依赖、阶段、证据要求和退出条件见 \`config/freecad-active-work-queue.json\`。完成证据只能追加到 \`config/freecad-follow-up-task-progress.json\`,生成器会拒绝跳号、未知任务和无证据完成项。
`
if (process.argv.includes('--check')) { if (process.argv.includes('--check')) {
const current = await readFile(outputPath, 'utf8').catch(() => '') const current = await readFile(outputPath, 'utf8').catch(() => '')
if (current !== content) throw new Error('FreeCAD active work queue is stale; run generate:freecad-active-work-queue.') if (current !== content) throw new Error('FreeCAD active work queue is stale; run generate:freecad-active-work-queue.')
const currentStatus = await readFile(statusOutputPath, 'utf8').catch(() => '')
if (currentStatus !== statusContent) throw new Error('FreeCAD active work status is stale; run generate:freecad-active-work-queue.')
console.log(JSON.stringify({ status: 'freecad-active-work-queue-pass', nextTask: queue.nextTask, ...queue.summary }, null, 2)) console.log(JSON.stringify({ status: 'freecad-active-work-queue-pass', nextTask: queue.nextTask, ...queue.summary }, null, 2))
} else { } else {
await writeFile(outputPath, content) await Promise.all([writeFile(outputPath, content), writeFile(statusOutputPath, statusContent)])
console.log(JSON.stringify({ status: 'freecad-active-work-queue-generated', output: outputPath, nextTask: queue.nextTask, ...queue.summary }, null, 2)) console.log(JSON.stringify({ status: 'freecad-active-work-queue-generated', output: outputPath, statusOutput: 'docs/freecad-active-work-status.generated.zh-CN.md', nextTask: queue.nextTask, ...queue.summary }, null, 2))
} }

View File

@@ -50,12 +50,38 @@ const operationSpecs = {
cut: { builder: 'BRepAlgoAPI_Cut', nominal: { toolSize: 4, toolOffset: 2 }, edited: { toolSize: 3, toolOffset: 2 }, parameter: 'toolSize', beforeValue: 4, editedValue: 3 }, cut: { builder: 'BRepAlgoAPI_Cut', nominal: { toolSize: 4, toolOffset: 2 }, edited: { toolSize: 3, toolOffset: 2 }, parameter: 'toolSize', beforeValue: 4, editedValue: 3 },
common: { builder: 'BRepAlgoAPI_Common', nominal: { toolSize: 5, toolOffset: 10 }, edited: { toolSize: 5, toolOffset: 9 }, parameter: 'toolOffsetX', beforeValue: 10, editedValue: 9 }, common: { builder: 'BRepAlgoAPI_Common', nominal: { toolSize: 5, toolOffset: 10 }, edited: { toolSize: 5, toolOffset: 9 }, parameter: 'toolOffsetX', beforeValue: 10, editedValue: 9 },
rotate: { builder: 'BRepBuilderAPI_Transform', nominal: { angle: 15 }, edited: { angle: 22.5 }, parameter: 'angle', beforeValue: 15, editedValue: 22.5 }, rotate: { builder: 'BRepBuilderAPI_Transform', nominal: { angle: 15 }, edited: { angle: 22.5 }, parameter: 'angle', beforeValue: 15, editedValue: 22.5 },
pad: { builder: 'BRepPrimAPI_MakePrism', nominal: { length: 5 }, edited: { length: 7.5 }, parameter: 'length', beforeValue: 5, editedValue: 7.5, decision: 'rejected' },
pocket: { builder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', nominal: { length: 5 }, edited: { length: 4 }, parameter: 'length', beforeValue: 5, editedValue: 4 },
loft: { builder: 'BRepOffsetAPI_ThruSections', nominal: { ruled: false }, edited: { ruled: true }, parameter: 'ruled', beforeValue: false, editedValue: true, decision: 'rejected', rejectionAuthority: 'freecad-document', kernelOutcome: 'accepted' },
pipe: { builder: 'BRepOffsetAPI_MakePipe', nominal: { spineLength: 15 }, edited: { spineLength: 12 }, parameter: 'spineLength', beforeValue: 15, editedValue: 12, decision: 'rejected', rejectionAuthority: 'freecad-document', kernelOutcome: 'invalid-result' },
} }
const createOperation = (module, { operation, objectStep, toolSize, toolOffset, angle, stage }) => { const createOperation = (module, { operation, objectStep, toolSize, toolOffset, angle, length, ruled, spineLength, stage }) => {
const baseStep = objectStep ?? shapeStep(module, 'makeBox', 10, 10, 10) const baseStep = objectStep ?? shapeStep(module, 'makeBox', 10, 10, 10)
const objectInput = input('object', 'object', stage.objectId, stage.objectTag, baseStep, stage.namingEvidence) const objectInput = input('object', 'object', stage.objectId, stage.objectTag, baseStep, stage.namingEvidence)
if (operation === 'rotate') return { response: module.rotateHistoryFromStep(baseStep, 0, 0, 0, 0, 0, 1, angle), inputs: [objectInput] } if (operation === 'rotate') return { response: module.rotateHistoryFromStep(baseStep, 0, 0, 0, 0, 0, 1, angle), inputs: [objectInput] }
if (operation === 'pad') return { response: module.prismHistoryFromStep(baseStep, 0, 0, length), inputs: [objectInput] }
if (operation === 'pocket') {
const profileStep = shapeStep(module, 'makeRectangleFace', 2, 3)
return {
response: module.pocketHistoryFromStep(baseStep, profileStep, 0, 0, length),
inputs: [objectInput, input('tool', 'tool', `${stage.resultObjectId}:profile`, stage.objectTag + 1, profileStep)],
}
}
if (operation === 'loft') {
const sectionStep = shapeStep(module, 'makeRectangleFacePlaced', 2, 3, 0, 0, 15)
return {
response: module.loftHistoryFromStep(baseStep, sectionStep, ruled),
inputs: [objectInput, input('tool', 'tool', `${stage.resultObjectId}:section`, stage.objectTag + 1, sectionStep)],
}
}
if (operation === 'pipe') {
const spineStep = shapeStep(module, 'makeLineWire', 0, 0, 0, 0, 0, spineLength)
return {
response: module.pipeHistoryFromStep(baseStep, spineStep),
inputs: [objectInput, input('tool', 'tool', `${stage.resultObjectId}:spine`, stage.objectTag + 1, spineStep)],
}
}
const toolStep = shapeStep(module, 'makeBoxPlaced', toolSize, toolSize, toolSize, toolOffset, 0, 0) const toolStep = shapeStep(module, 'makeBoxPlaced', toolSize, toolSize, toolSize, toolOffset, 0, 0)
const inputs = [ const inputs = [
objectInput, objectInput,
@@ -64,6 +90,61 @@ const createOperation = (module, { operation, objectStep, toolSize, toolOffset,
return { response: module.booleanHistoryFromStep(baseStep, toolStep, operation), inputs } return { response: module.booleanHistoryFromStep(baseStep, toolStep, operation), inputs }
} }
const captureRejectedStage = (module, definition) => {
let capture
try {
capture = createOperation(module, definition)
} catch (error) {
return {
operation: definition.operation,
builder: operationSpecs[definition.operation].builder,
inputCount: 1,
parameter: operationSpecs[definition.operation].parameter,
parameterValue: definition.length,
runtime: 'occt-native',
outcome: 'rejected',
resultProduced: false,
shapeProduced: false,
historyProduced: false,
rejectionStage: 'builder',
exceptionTransport: typeof error === 'number' ? 'emscripten-cpp-exception' : 'javascript-exception',
thrownType: typeof error,
}
}
capture.response.result?.delete?.()
fail(`${definition.stage.stageId} unexpectedly produced a native result.`)
}
const rejectionSignature = ({ parameterValue: _parameterValue, ...attempt }) => JSON.stringify(attempt)
const captureKernelCompatibilityStage = (module, definition, parameterValue, expectedOutcome) => {
const { response, inputs } = createOperation(module, definition)
try {
const outcome = response.summary?.isValid === true ? 'accepted' : 'invalid-result'
if (response.provider !== 'occt-native' || outcome !== expectedOutcome || !response.resultStep?.startsWith('ISO-10303-21;') || response.records?.length < 1) fail(`${definition.stage.stageId} OCCT compatibility probe returned an unexpected result.`)
return {
operation: definition.operation,
builder: operationSpecs[definition.operation].builder,
inputCount: inputs.length,
parameter: operationSpecs[definition.operation].parameter,
parameterValue,
runtime: 'occt-native',
outcome,
resultProduced: true,
shapeProduced: true,
validShapeProduced: response.summary.isValid === true,
historyProduced: true,
historyProvider: response.provider,
summary: canonicalSummary(response.summary),
historyRecords: response.records.length,
historySha256: sha256(JSON.stringify(response.records)),
authority: outcome === 'accepted' ? 'kernel-superset-only' : 'kernel-invalid-result',
}
} finally {
response.result?.delete?.()
}
}
const captureStage = (module, definition) => { const captureStage = (module, definition) => {
const { response, inputs } = createOperation(module, definition) const { response, inputs } = createOperation(module, definition)
try { try {
@@ -114,12 +195,12 @@ const captureStage = (module, definition) => {
const createModule = (await import(pathToFileURL(modulePath).href)).default const createModule = (await import(pathToFileURL(modulePath).href)).default
const module = await createModule({ locateFile: (path) => resolve(dist, path) }) const module = await createModule({ locateFile: (path) => resolve(dist, path) })
const persistAcceptedPair = async (pair, second) => { const persistPair = async (pair, step, namingEvidence, decision) => {
const temporaryDirectory = await mkdtemp(join(tmpdir(), 'freecad-ordered-pair-')) const temporaryDirectory = await mkdtemp(join(tmpdir(), 'freecad-ordered-pair-'))
try { try {
const stepPath = join(temporaryDirectory, 'pair-result.step') const stepPath = join(temporaryDirectory, 'pair-result.step')
const evidencePath = join(temporaryDirectory, 'pair-naming.json') const evidencePath = join(temporaryDirectory, 'pair-naming.json')
await Promise.all([writeFile(stepPath, second.response.resultStep), writeFile(evidencePath, JSON.stringify(second.namingEvidence))]) await Promise.all([writeFile(stepPath, step), writeFile(evidencePath, JSON.stringify(namingEvidence ?? null))])
const execution = spawnSync(freecad, ['--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), resaveHarnessPath], { const execution = spawnSync(freecad, ['--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), resaveHarnessPath], {
cwd: root, cwd: root,
encoding: 'utf8', encoding: 'utf8',
@@ -128,6 +209,7 @@ const persistAcceptedPair = async (pair, second) => {
env: { env: {
...process.env, ...process.env,
FREECAD_ORDERED_PAIR: pair, FREECAD_ORDERED_PAIR: pair,
FREECAD_PAIR_DECISION: decision,
FREECAD_PAIR_STEP_PATH: stepPath, FREECAD_PAIR_STEP_PATH: stepPath,
FREECAD_PAIR_EVIDENCE_PATH: evidencePath, FREECAD_PAIR_EVIDENCE_PATH: evidencePath,
FREECAD_PAIR_OUTPUT_DIRECTORY: temporaryDirectory, FREECAD_PAIR_OUTPUT_DIRECTORY: temporaryDirectory,
@@ -143,7 +225,7 @@ const persistAcceptedPair = async (pair, second) => {
const payload = markerIndex >= 0 ? output.slice(markerIndex + marker.length).match(/\{.*\}/s)?.[0] : undefined const payload = markerIndex >= 0 ? output.slice(markerIndex + marker.length).match(/\{.*\}/s)?.[0] : undefined
if (execution.error || execution.status !== 0 || !payload) fail(`FreeCAD FCStd replay exited with ${execution.status}: ${execution.error?.message || output.trim()}`) if (execution.error || execution.status !== 0 || !payload) fail(`FreeCAD FCStd replay exited with ${execution.status}: ${execution.error?.message || output.trim()}`)
const persistence = JSON.parse(payload) const persistence = JSON.parse(payload)
if (persistence.status !== 'pass' || persistence.freecadVersion !== '1.1.1' || persistence.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || !Object.values(persistence.checks).every(Boolean)) fail(`FreeCAD FCStd replay failed: ${JSON.stringify(persistence)}`) if (persistence.status !== 'pass' || persistence.decision !== decision || persistence.freecadVersion !== '1.1.1' || persistence.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || !Object.values(persistence.checks).every(Boolean)) fail(`FreeCAD FCStd replay failed: ${JSON.stringify(persistence)}`)
return persistence return persistence
} finally { } finally {
await rm(temporaryDirectory, { recursive: true, force: true }) await rm(temporaryDirectory, { recursive: true, force: true })
@@ -164,6 +246,41 @@ const classifyFusePair = async (toOperation, pairIndex) => {
...spec.nominal, ...spec.nominal,
stage: { stageId: `pair:${pairId}:second`, ordinal: 1, objectId: `pair:${pairId}:first`, objectTag: 1101 + pairIndex * 100, resultObjectId: `pair:${pairId}:second`, resultObjectTag: 1201 + pairIndex * 100, namingEvidence: first.namingEvidence }, stage: { stageId: `pair:${pairId}:second`, ordinal: 1, objectId: `pair:${pairId}:first`, objectTag: 1101 + pairIndex * 100, resultObjectId: `pair:${pairId}:second`, resultObjectTag: 1201 + pairIndex * 100, namingEvidence: first.namingEvidence },
} }
if (spec.decision === 'rejected') {
try {
const trajectory = [spec.beforeValue, spec.editedValue, spec.beforeValue]
if (spec.rejectionAuthority === 'freecad-document') {
const kernelAttempts = trajectory.map((value) => captureKernelCompatibilityStage(module, { ...secondDefinition, [spec.parameter]: value }, value, spec.kernelOutcome))
const kernelRestoredExactly = JSON.stringify(kernelAttempts[0]) === JSON.stringify(kernelAttempts[2])
if (!kernelRestoredExactly) fail(`${pair} OCCT diagnostic probe did not restore exactly.`)
const persistence = await persistPair(pair, first.response.resultStep, first.namingEvidence, 'rejected')
return {
taskId: `TSN-PAIR-fuse-${toOperation}`,
pair,
classification: 'rejected',
reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave',
nativeDecision: 'rejected',
first: first.report,
rejection: { scope: 'second-operation-only', authority: 'freecad-document', parameter: spec.parameter, trajectory, kernelAttempts, kernelRestoredExactly, persistence },
}
}
const attempts = trajectory.map((length) => captureRejectedStage(module, { ...secondDefinition, length }))
const stableRejection = attempts.every((attempt) => rejectionSignature(attempt) === rejectionSignature(attempts[0]))
if (!stableRejection) fail(`${pair} rejection changed across its parameter trajectory.`)
const persistence = await persistPair(pair, first.response.resultStep, first.namingEvidence, 'rejected')
return {
taskId: `TSN-PAIR-fuse-${toOperation}`,
pair,
classification: 'rejected',
reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave',
nativeDecision: 'rejected',
first: first.report,
rejection: { scope: 'second-operation-only', authority: 'occt-builder', parameter: spec.parameter, trajectory, stable: stableRejection, attempts, persistence },
}
} finally {
first.response.result?.delete?.()
}
}
const second = captureStage(module, secondDefinition) const second = captureStage(module, secondDefinition)
const mutated = captureStage(module, { ...secondDefinition, ...spec.edited }) const mutated = captureStage(module, { ...secondDefinition, ...spec.edited })
const restored = captureStage(module, secondDefinition) const restored = captureStage(module, secondDefinition)
@@ -171,7 +288,7 @@ const classifyFusePair = async (toOperation, pairIndex) => {
const mutationChanged = JSON.stringify(second.report.summary) !== JSON.stringify(mutated.report.summary) const mutationChanged = JSON.stringify(second.report.summary) !== JSON.stringify(mutated.report.summary)
const restoredExactly = JSON.stringify(second.report.summary) === JSON.stringify(restored.report.summary) && second.report.historySha256 === restored.report.historySha256 && second.report.namingSemanticSha256 === restored.report.namingSemanticSha256 const restoredExactly = JSON.stringify(second.report.summary) === JSON.stringify(restored.report.summary) && second.report.historySha256 === restored.report.historySha256 && second.report.namingSemanticSha256 === restored.report.namingSemanticSha256
if (!mutationChanged || !restoredExactly) fail(`${pair} mutation did not change and restore native geometry/history.`) if (!mutationChanged || !restoredExactly) fail(`${pair} mutation did not change and restore native geometry/history.`)
const persistence = await persistAcceptedPair(pair, second) const persistence = await persistPair(pair, second.response.resultStep, second.namingEvidence, 'accepted')
return { return {
taskId: `TSN-PAIR-fuse-${toOperation}`, taskId: `TSN-PAIR-fuse-${toOperation}`,
pair, pair,
@@ -190,7 +307,7 @@ const classifyFusePair = async (toOperation, pairIndex) => {
} }
const classifications = [] const classifications = []
for (const [index, operation] of ['fuse', 'cut', 'common', 'rotate'].entries()) classifications.push(await classifyFusePair(operation, index)) for (const [index, operation] of ['fuse', 'cut', 'common', 'rotate', 'pad', 'pocket', 'loft', 'pipe'].entries()) classifications.push(await classifyFusePair(operation, index))
const [artifacts, executorHarness, matrixHarness, resaveHarness] = await Promise.all([ const [artifacts, executorHarness, matrixHarness, resaveHarness] = await Promise.all([
Promise.all(artifactNames.map(async (name) => { Promise.all(artifactNames.map(async (name) => {

132
后续工作.txt Normal file
View File

@@ -0,0 +1,132 @@
• 本项目应继续以锁定的 FreeCAD 1.1.1 为基线。当前已有可运行的 Web CAD 兼
容基线但距离“全部功能对标”仍较远exact 任务为 5/52 completed34 个
模块中 0 exact / 26 compatible / 5 proxy / 3 development。
详细机器账本见 config/freecad-web-exact-parity-plan.json:1当前执行总
览见 docs/freecad-web-execution-master-plan.zh-CN.md:194。
需要完善的工作
工作域 当前主要缺口 完成标准
━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━
原生 Oracle 1111 个命令已完成三态 每个 TypeId、Property、
清册,但只有 Pad 命令 Command、Task 和对话框
族完成成功、禁用、失 都有原生成功与负向证据
败、取消、恢复全流程
───────────────── ──────────────────────── ─────────────────────────
OCCT 几何内核 部分 builder 参数、容 每个操作的 Shape、质量
差、校验和失败诊断未穷 属性、拓扑、容差、错误
尽;原生 history 和资源释放与 FreeCAD 一
provider 尚未成为完整 致
默认链路
───────────────── ──────────────────────── ─────────────────────────
拓扑命名 仍有 7 个 PartDesign 零错绑、零未知漂移、
case/20 个恢复阶段未分 361/361 组合分类、允许
类;五阶段链存在 3 个 组合全部使用真实
通用 Shape 代理361 builder
个有序操作对只完成 4
───────────────── ──────────────────────── ─────────────────────────
Property 系统 85 种 Property 中仍有 默认值、边界值、状态、
58 种/523 条记录只能 编辑器、Undo/Redo、
opaque 保存 FCStd、Chrome 回放全部
闭合
───────────────── ──────────────────────── ─────────────────────────
Document/App observer 顺序、嵌套事 事务、DAG、失败恢复、链
务、局部重算、 接和对象生命周期与原生
Extension、 事件序列一致
FeaturePython、多文档
和合并恢复不完整
───────────────── ──────────────────────── ─────────────────────────
Sketcher 几何与约束已有较强基 每种几何/约束/工具均通
线,但全部 overload、 过求解、编辑、取消、撤
失败消息、编辑工具、自 销、FCStd 和浏览器回放
动约束、虚拟空间和焦点
矩阵未闭合
───────────────── ──────────────────────── ─────────────────────────
Part 基本体和代表性 builder 所有注册 Part 命令、选
已实现,但全部参数、 项和 TypeId 均有原生差
Refine、Section/ 分证据
Slice、Healing、检查和
失败分支未穷尽
───────────────── ──────────────────────── ─────────────────────────
PartDesign Attachment 等独立切片 Pad 到 MultiTransform
已 exact其余特征的全 的每种模式、失败、Body
部模式组合仍未完成 Tip、Support、命名和
FCStd 均一致
───────────────── ──────────────────────── ─────────────────────────
FCStd 当前支持 23 个往返场景 FreeCAD→Web 修改
和未知资源保留,但未完 →FreeCAD 重开/再保存后
成所有对象、Property、 零未知语义差异
Extension、Shape 的可
编辑往返
───────────────── ──────────────────────── ─────────────────────────
数据格式 STEP/IGES 高级元数据、 15 种格式分别具备单位、
装配、颜色层级不完整; 元数据、失败、取消、大
部分 STL/OBJ/PLY/DXF/ 文件和往返报告
SVG/PDF 只读JT/IDF/
OpenSCAD 为代理
───────────────── ──────────────────────── ─────────────────────────
GUI 应用壳可用,但完整 每个工作台端到端流程与
MDI、动态命令状态、所 原生 GUI oracle 一致
有 Task/属性编辑器/对
话框、快捷键和焦点行为
未闭合
───────────────── ──────────────────────── ─────────────────────────
文档工作台 Draft、TechDraw、 对象、命令、表达式、
Spreadsheet、Plot 目前 TopoRef、导出和跨工作台
是兼容子集 引用全部往返
───────────────── ──────────────────────── ─────────────────────────
工程工作台 Assembly、BIM、 装配求解、IFC、曲面修
Surface、Mesh、 复、网格分析和材料继承
Material 均缺高级算法 全部闭合
和完整对象语义
───────────────── ──────────────────────── ─────────────────────────
分析与制造 FEM、CAM、Robot、 完整求解器/刀路/运动学/
Inspection、Points/RE 点云算法、结果对象、失
只有代表性实现 败与导出闭合
───────────────── ──────────────────────── ─────────────────────────
Python 与 Addon 当前沙箱不能运行完整 受控 Python API、宏、权
FreeCAD Python API桌 限、依赖隔离、签名、回
面插件无法原样运行 滚和资源配额完成
───────────────── ──────────────────────── ─────────────────────────
QA 与发布 Chrome 基线较完整,但 52/52 exact、34/34
跨 OS/GPU、长期运行、 exact、零未知指标、签名
全 locale 和完整无障碍 发布和回滚演练通过
认证未完成
各工作台的细分任务范围已列在 docs/freecad-complete-parity-backlog.zh-
CN.md:88 和 docs/freecad-full-parity-execution-wbs.zh-CN.md:55。
建议执行顺序
1. 先关闭 EX-ORA-01 和 EX-TSN-04不要继续横向增加工作台。
2. 完成 7 个剩余命名漂移 case、生产 Cut/Rotate 漂移和 361 个有序操作
对。
3. 按类型逐项完成 58 种 opaque Property。
4. 关闭事务、observer、DAG、Extension、FeaturePython 和多文档语义。
5. 按单特征纵向完成 Kernel、Sketcher、Part、PartDesign。
6. 完成 FCStd 和 STEP/IGES/BREP 双向往返。
7. 完成 GUI 全量工作流。
8. 依次关闭文档、工程、分析制造和数据工作台。
9. 最后处理 Python、Addon、代理模块和跨平台发布。
立即执行入口
当前唯一机器任务是 config/freecad-active-work-queue.json:1563。完成后
依次处理剩余 Pocket 系列、生产 Cut、生产 Rotate再进入 357 个未分类操
作对。
此外需要修复规划治理本身:
- EX-TSN-04 的 blocker 文本仍写着旧的 1/11实际已经是 4/11。
- 早期 Backlog/WBS 仍包含过期完成度描述。
- 当前 487 项队列只细化了 GUI Oracle 和 TSN尚未为 Property、
Document、核心建模及 M10-M14 生成同等粒度的机器任务。
- 必须让状态摘要由机器报告自动生成,禁止手工维护重复数字。
每个能力应统一采用“原生清册、成功、失败/取消、参数变异、Facade 实现、
事务恢复、FCStd 往返、真实浏览器、能力提升”九步完成定义;缺任何一步只
能保持 compatible。