Document system flow and next execution baseline
This commit is contained in:
@@ -4,13 +4,16 @@
|
||||
|
||||
恢复或开始工作时,首先读取 [`nextTask.md`](nextTask.md)。它是唯一接续入口,刻意限制在 8 KiB 以内,不包含聊天记录、完整日志或历史证据。只有定位旧证据时才按需读取 [`docs/task-history.md`](docs/task-history.md) 和其中引用的产物。
|
||||
|
||||
项目整体流程、LinuxCNC 案例到 WASM/Worker/前端的映射,以及后续任务起点见
|
||||
[`系统流程与后续执行基线`](docs/linuxcnc-web-wasm-system-flow-and-execution-baseline.md)。
|
||||
|
||||
## 当前状态
|
||||
|
||||
- LinuxCNC 来源基线:`linuxcnc/VERSION` 与 `context-lock.json`。
|
||||
- 来源索引:`coverage/source-index.jsonl`,由 `scripts/generate_source_inventory.py` 生成。
|
||||
- 覆盖种子:`coverage/manifest.jsonl`。所有条目初始为 `planned/needs_owner`,没有来源会被静默忽略。
|
||||
- WASM ABI 骨架:`wasm-core/include/sim_api.h`;当前实现只验证生命周期、固定 tick、快照和状态导入导出。
|
||||
- 前端应用壳、真实 WorkerClient、完整配置加载、HAL/数字孪生、实际 Pyodide bridge 和 UI/Three.js 工作流仍须按文档里程碑实现;当前下一任务是 `M3-CFG-01`,详见 [`后续工作基线`](docs/linuxcnc-web-wasm-next-work.md)。
|
||||
- 前端 AppShell、真实 WorkerClient、配置/HAL preflight、虚拟设备、Python bridge 和 FE-M1..M4 初始工作流已有实现证据;真正的 LinuxCNC Task 固定 tick、完整配置启动、真实 OPFS/浏览器矩阵和全量 UI 融合仍未完成。当前正式下一任务是 `WAIT-OWNER-01`;owner 输入和新 context 后的第一个技术实现起点为 `INT-01`,详见 [`系统流程与后续执行基线`](docs/linuxcnc-web-wasm-system-flow-and-execution-baseline.md)。
|
||||
- K0-K6 运动学实现主线已完成代码、ABI、固定边界和 native/WASM 回归,覆盖 trivkins、耦合运动学、5 轴/TRT、机器人、GenSer、Delta、Tripod、Hexapod、Pentapod、switchkins、userk/registry、TP→IK→motion、FK feedback、home/limit。对应报告仍可能是 `partial`,因为锁定来源没有 `linuxcnc/unit_tests/kinematics/**` 官方 oracle;这是发布验收阻塞,不是继续重复解析的理由。
|
||||
- K8 配置/发布审计已生成 264 个配置注册入口、233 个 sim entrypoint 和 16,093 条资源边;当前配置启动闭包仍有 997 missing、91 unresolved、59 unindexed 和 3 unregistered startup 条目。K8 release gate 保持 `candidate_eligible=false`、`release_action=STOP_RELEASE`,六项 owner/release blocker 不得被误报为运动学未完成。
|
||||
|
||||
|
||||
65
coverage/owner-decision-intake.report.json
Normal file
65
coverage/owner-decision-intake.report.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"candidate_eligible": false,
|
||||
"checks": {
|
||||
"context_consistency": {
|
||||
"input": null,
|
||||
"locked": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
|
||||
"status": "blocked"
|
||||
},
|
||||
"context_lock_unchanged": {
|
||||
"action": "no mutation",
|
||||
"status": "verified"
|
||||
},
|
||||
"decision_approval": {
|
||||
"approved": 0,
|
||||
"required": 6,
|
||||
"status": "blocked"
|
||||
},
|
||||
"gate_refresh_allowed": {
|
||||
"reason": "owner intake validation never refreshes the gate; reviewed new context is required after all six approvals",
|
||||
"status": "blocked"
|
||||
},
|
||||
"input_file_present": {
|
||||
"path": "coverage/owner-decisions.json",
|
||||
"status": "blocked"
|
||||
},
|
||||
"schema_shape": {
|
||||
"schema": null,
|
||||
"status": "blocked"
|
||||
},
|
||||
"six_blocker_identity": {
|
||||
"duplicates": [],
|
||||
"missing": [
|
||||
"accepted_entries_missing",
|
||||
"browser_matrix_unverified",
|
||||
"configuration_resources_missing",
|
||||
"license_closure_incomplete",
|
||||
"manifest_seed_unassigned",
|
||||
"official_oracle_missing"
|
||||
],
|
||||
"status": "blocked",
|
||||
"unknown": []
|
||||
}
|
||||
},
|
||||
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
|
||||
"errors": [
|
||||
"[Errno 2] No such file or directory: '/home/mes006/linuxcncWebWASM/coverage/owner-decisions.json'"
|
||||
],
|
||||
"implementation_status": "verified",
|
||||
"input": {
|
||||
"path": "coverage/owner-decisions.json",
|
||||
"sha256": null,
|
||||
"status": "missing"
|
||||
},
|
||||
"next_transition": "wait for owner-decisions.json or new locked context input; do not rerun browser or implementation matrices",
|
||||
"release_action": "STOP_RELEASE",
|
||||
"release_status": "blocked_external_owner",
|
||||
"report_sha256": "204ecc3408720b869d7512594ce491912060c2280f9e86dfa22b381599c89a89",
|
||||
"schema": "owner-decision-intake-report.v1",
|
||||
"status": "blocked",
|
||||
"task_id": "K8-REL-18",
|
||||
"template": {
|
||||
"path": "coverage/owner-decision-intake.template.json",
|
||||
"sha256": "5c9e356ef40c3cd307c10153839c75f94ecff44a6ac6a355323feddfa26e3a32"
|
||||
}
|
||||
}
|
||||
13
coverage/owner-decision-intake.template.json
Normal file
13
coverage/owner-decision-intake.template.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"schema": "owner-decision-intake.v1",
|
||||
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
|
||||
"decision_set_id": "K8-owner-decisions-01",
|
||||
"decisions": [
|
||||
{"blocker": "manifest_seed_unassigned", "decision": "missing", "owner": null, "reviewer": null, "rationale": null, "evidence": [], "new_context_required": true},
|
||||
{"blocker": "accepted_entries_missing", "decision": "missing", "owner": null, "reviewer": null, "rationale": null, "evidence": [], "new_context_required": true},
|
||||
{"blocker": "official_oracle_missing", "decision": "missing", "owner": null, "reviewer": null, "rationale": null, "evidence": [], "new_context_required": true},
|
||||
{"blocker": "configuration_resources_missing", "decision": "missing", "owner": null, "reviewer": null, "rationale": null, "evidence": [], "new_context_required": true},
|
||||
{"blocker": "browser_matrix_unverified", "decision": "missing", "owner": null, "reviewer": null, "rationale": null, "evidence": [], "new_context_required": true},
|
||||
{"blocker": "license_closure_incomplete", "decision": "missing", "owner": null, "reviewer": null, "rationale": null, "evidence": [], "new_context_required": true}
|
||||
]
|
||||
}
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
运动学实现主线 K0-K6 已完成代码和 native/WASM 回归:trivkins、耦合运动学、5 轴/TRT、机器人、GenSer、Delta、Tripod、Hexapod、Pentapod、switchkins、userk/registry、TP→IK→motion、FK feedback、home/limit 均已有实现、ABI、固定边界和测试产物。对应报告仍为 `partial` 或 `blocked` 的原因是锁定 LinuxCNC 来源没有 `linuxcnc/unit_tests/kinematics/**` 官方 oracle;这限制发布验收,不应继续重复实现运动学解析。
|
||||
|
||||
已验证的其它基础包括:posemath、解释器、motion queue、HAL 核心、IO 核心、Worker protocol、replay、workspace recovery/migration、Python runtime manifest 和 native CTest。当前仓库没有前端应用壳,也没有把 INI/HAL 资源图接入真实 Worker 启动路径。
|
||||
已验证的其它基础包括:posemath、解释器、motion queue、HAL 核心、IO 核心、Worker protocol、replay、workspace recovery/migration、Python runtime manifest、配置/HAL preflight、虚拟设备、Python bridge、FE-M1..M4 和 native CTest。当前前端与适配器已有垂直切片,但 `sim_api` 仍是 bootstrap,Worker 尚未把 interpreter、TP、motion、kinematics、HAL、IO 串成统一 `sim_task` 固定 tick;真实 OPFS、完整配置启动和跨浏览器验证也未闭合。
|
||||
|
||||
发布状态仍为 `blocked_not_release_candidate`/`STOP_RELEASE`。六项发布 blocker(manifest ownership、accepted entries、official oracle、configuration resources、browser matrix、implementation license)必须保留;owner review 是外部输入,不再作为当前开发任务反复重跑。
|
||||
发布状态仍为 `blocked_not_release_candidate`/`STOP_RELEASE`。六项发布 blocker(manifest ownership、accepted entries、official oracle、configuration resources、browser matrix、implementation license)必须保留;当前正式接续以 [`nextTask.md`](../nextTask.md) 的 `WAIT-OWNER-01` 为准,owner review 是外部输入,不再作为当前开发任务反复重跑。
|
||||
|
||||
## 执行顺序
|
||||
|
||||
@@ -26,7 +26,11 @@
|
||||
| 10 | `REG-01` | 运行 233 个 sim entrypoint 的启动矩阵,分类 ready/blocked/error 并生成首个差异定位 | full configuration matrix report | M3-CFG-01..03、M3-PY-01、FE-M4 |
|
||||
| 10 | `REG-03/04` | 完成恢复、断网、浏览器和性能矩阵;再回到 K8 release gate | recovery/browser/performance reports | FE-M1、REG-01 |
|
||||
|
||||
## 当前任务边界:REG-01
|
||||
## 历史实现边界与当前技术缺口
|
||||
|
||||
本表中的 M3/FE/REG 行用于保留已完成实现的依赖和证据,不是当前接续指针。`nextTask.md` 是唯一当前任务入口;owner 输入和新 context 之后,第一个需要实现的技术任务是 `INT-01 sim_task 固定 tick 集成`,详见 [`系统流程与后续执行基线`](linuxcnc-web-wasm-system-flow-and-execution-baseline.md)。
|
||||
|
||||
## 历史任务边界:REG-01
|
||||
|
||||
允许创建全配置 startup matrix 生成器、结果分类和 source/resource/component 差异定位测试;不修改 kinematics 算法、ABI/protocol/schema/context lock,不替 owner 作许可证、浏览器或官方 oracle 决定。
|
||||
|
||||
|
||||
29
docs/linuxcnc-web-wasm-owner-decision-intake.md
Normal file
29
docs/linuxcnc-web-wasm-owner-decision-intake.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Owner Decision Intake
|
||||
|
||||
输入文件:`coverage/owner-decisions.json`
|
||||
|
||||
模板:`coverage/owner-decision-intake.template.json`
|
||||
|
||||
验证器:`python3 scripts/validate_owner_decision_intake.py`
|
||||
|
||||
当前状态:`coverage/owner-decisions.json` 尚未提供,验证报告应保持 `blocked`。模板文件是唯一允许复制的输入起点;不要直接修改 `context-lock.json` 或 release gate 报告。
|
||||
|
||||
## Required Shape
|
||||
|
||||
The input must use schema `owner-decision-intake.v1`, match the locked
|
||||
`context_id`, and contain exactly one decision for each of the six blocker IDs:
|
||||
|
||||
`manifest_seed_unassigned`, `accepted_entries_missing`,
|
||||
`official_oracle_missing`, `configuration_resources_missing`,
|
||||
`browser_matrix_unverified`, `license_closure_incomplete`.
|
||||
|
||||
Each decision requires `decision` (`approve`, `reject`, or `defer`), non-empty
|
||||
`owner`, non-empty `reviewer`, a rationale, an evidence list, and an explicit
|
||||
`new_context_required` boolean. Evidence entries require a repository-relative
|
||||
path and SHA-256 hash. The validator checks shape and identity only; it never
|
||||
creates a context, changes `context-lock.json`, promotes an entry, or refreshes
|
||||
the release gate.
|
||||
|
||||
All six decisions must be `approve` and accompanied by a reviewed new context
|
||||
input before K8 release refresh can be opened. Missing, partial, deferred,
|
||||
rejected, duplicate, stale-context, or hash-invalid input remains blocked.
|
||||
263
docs/linuxcnc-web-wasm-system-flow-and-execution-baseline.md
Normal file
263
docs/linuxcnc-web-wasm-system-flow-and-execution-baseline.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# LinuxCNC Web/WASM 系统流程与后续执行基线
|
||||
|
||||
更新时间:2026-08-21
|
||||
|
||||
本文件把现有代码、LinuxCNC 源程序、仿真配置和测试程序放在同一条可执行链上。它的目的不是再次定义产品范围,而是回答三个问题:当前代码究竟完成到哪一层、一个 LinuxCNC 仿真入口如何穿过前端/Worker/WASM、后续任务应从哪里开始以及如何验收。
|
||||
|
||||
规范入口仍然是 [`nextTask.md`](../nextTask.md)。本文件是工程梳理和任务分解,不替换 `context-lock.json`、协议 schema、覆盖矩阵或 release gate。
|
||||
|
||||
## 1. 先给结论
|
||||
|
||||
### 1.1 当前正式任务起点
|
||||
|
||||
当前正式指针是 `WAIT-OWNER-01`,状态为 `blocked`。它等待六项 owner decision 或新的 locked context input;`coverage/owner-decision-intake.report.json` 在输入缺失时只能生成 blocked report。此期间不能刷新 K8 release gate、创建新 context、把 `partial` 改成 `accepted`,也不能重复执行已完成的实现矩阵。
|
||||
|
||||
这与技术路线中的“实现任务已完成”并不矛盾:
|
||||
|
||||
| 层次 | 当前结论 | 证据 | 含义 |
|
||||
|---|---|---|---|
|
||||
| 基线 | LinuxCNC `2.10.0~pre0`,source SHA 已冻结 | `context-lock.json` | 源码变化必须重新建 context |
|
||||
| WASM 单元能力 | posemath、解释器切片、motion queue、HAL、运动学、IO 均有 C ABI 和 native/WASM 测试 | `wasm-core/CMakeLists.txt`、`build-native`、`build-wasm` | 证明模块边界,不证明完整 Task 链 |
|
||||
| Worker/存储适配 | 协议、配置/HAL preflight、虚拟设备、事件重放、workspace package、Python bridge 已有测试 | `web/worker`、`web/workspace`、`coverage/*report.json` | 证明适配器和错误边界 |
|
||||
| 前端 | AppShell、WorkerClient、控制、状态、恢复、设备/HAL/机床视图已有 smoke | `web/app`、`coverage/frontend-m*.report.json` | 证明 UI 垂直切片,不证明所有真实命令已经驱动核心 |
|
||||
| 配置矩阵 | 264 个注册配置条目、233 个入口;56 个 ready,208 个 blocked | `coverage/full-configuration-startup-matrix.report.json` | 入口闭包仍未完成 |
|
||||
| 资源闭包 | 16,093 条资源边,15,112 resolved,981 blocked | `coverage/configuration-startup-closure.report.json` | 缺失资源不能被静默忽略 |
|
||||
| 发布 | `candidate_eligible=false`、`STOP_RELEASE` | `coverage/release-candidate-gate.report.json` | 当前不是发布候选 |
|
||||
|
||||
### 1.2 代码上的真实边界
|
||||
|
||||
目前最容易误判的地方是:模块测试都通过,但完整 LinuxCNC 行为还没有贯通。具体表现为:
|
||||
|
||||
1. [`wasm-core/src/sim_api.c`](../wasm-core/src/sim_api.c) 仍是生命周期、加载标志、tick 和 pending event 的 bootstrap 状态;`sim_command` 不解析命令,`sim_step` 不调用解释器、TP、HAL、运动学或 IO。
|
||||
2. [`web/worker/sim-session.mjs`](../web/worker/sim-session.mjs) 已经把协议、幂等、配置/HAL preflight、虚拟设备、Python 和恢复接起来,但 `PROGRAM_*`、`COMMAND` 等未知于设备的命令目前会落入通用 `COMMAND_RESULT`,不能视为已经执行了 LinuxCNC Task。
|
||||
3. [`web/app/project-workflow.mjs`](../web/app/project-workflow.mjs) 默认使用 `MemoryStorage`;workspace package/migration/recovery 契约已验证,真实浏览器 OPFS、Service Worker 离线启动和崩溃后的实际恢复仍需浏览器矩阵验证。
|
||||
4. 解释器、motion、kinematics、IO 的 C API 是可用的模块边界,但尚缺一个统一的 `sim_task`/session runtime,把“程序行 -> canonical -> TP -> IK -> joint motion -> feedback -> HAL/IO -> snapshot”作为一个原子固定 tick 执行。
|
||||
|
||||
因此,当前状态应写成“适配器和核心切片已验证,端到端仿真 Task 尚未完成”,不能写成“LinuxCNC 已完整移植”。
|
||||
|
||||
## 2. 一次仿真入口的实际流程
|
||||
|
||||
下面的流程区分了已经存在的代码路径和必须补齐的目标路径。
|
||||
|
||||
```text
|
||||
浏览器 UI
|
||||
-> WorkerClient 生成 protocol.v1 envelope
|
||||
-> sim-worker.mjs 单 Worker 接收消息
|
||||
-> SimSession 校验 schema/context/session/seq/tick
|
||||
-> sim_protocol_accept (WASM C ABI)
|
||||
-> HELLO/ATTACH 或 LOAD_WORKSPACE
|
||||
-> configuration preflight (INI/HAL/资源图)
|
||||
-> HAL preflight (component/pin/thread/function 图)
|
||||
-> sim_load_workspace (当前仍为 bootstrap load flag)
|
||||
-> command / program / step
|
||||
-> [应补] sim_task: interpreter -> canonical -> TP -> motion/IK -> HAL/IO/Python
|
||||
-> event log + replay timeline + state hash
|
||||
-> snapshot/checkpoint/recovery
|
||||
-> Worker envelope 返回
|
||||
-> WorkerClient promise + domain reducer
|
||||
-> 状态面板、设备面板、HAL 诊断、Three.js/Canvas 视图
|
||||
-> workspace journal/package/OPFS 持久化
|
||||
```
|
||||
|
||||
### 2.1 前端到 Worker
|
||||
|
||||
入口是 [`web/app/app.mjs`](../web/app/app.mjs) 和 [`web/app/worker-client.mjs`](../web/app/worker-client.mjs)。前端只发送带 `schema_version`、`context_id`、`session_id`、单调 `seq`、可选 `operation_id` 和 `payload` 的消息;前端不得读取 WASM 指针或直接写 Worker 内部状态。
|
||||
|
||||
`WorkerClient` 的主要调用对应关系如下:
|
||||
|
||||
| UI 操作 | Worker 消息 | 当前实现 |
|
||||
|---|---|---|
|
||||
| 连接/能力协商 | `HELLO` | 返回 capabilities |
|
||||
| 建立会话 | `SESSION_ATTACH` | 调用 `sim_create`,进入 ready |
|
||||
| 加载工程 | `LOAD_WORKSPACE` | 运行配置/HAL preflight,再调用 `sim_load_workspace` |
|
||||
| 推进仿真 | `SIM_STEP` | 调用 `sim_step`,更新 tick 和 timeline |
|
||||
| 读取状态 | `SNAPSHOT_REQUEST` | 返回 snapshot/checkpoint 摘要 |
|
||||
| 控制命令 | `PROGRAM_*`、`HOME`、`ESTOP` 等 | 设备命令进入 `VirtualDevices`;程序/Task 命令仍待接入 |
|
||||
| 恢复 | `CHECKPOINT`、`RECOVER` | 事件重放和 workspace 状态恢复已覆盖 |
|
||||
|
||||
### 2.2 Worker 到 WASM
|
||||
|
||||
[`web/worker/sim-session.mjs`](../web/worker/sim-session.mjs) 是唯一的 Worker 编排边界。它先调用 `_sim_protocol_accept`,再把能改变仿真结果的事件写入 `ReplayTimeline` 和 C event log。所有操作必须串行执行;相同 `operation_id` 的重复请求返回原结果并写入 `OPERATION_REPLAY`。
|
||||
|
||||
WASM 导出列表在 [`wasm-core/CMakeLists.txt`](../wasm-core/CMakeLists.txt) 中冻结,当前可以分为四类:
|
||||
|
||||
- 协议/生命周期:`sim_create`、`sim_load_workspace`、`sim_command`、`sim_step`、状态导入导出。
|
||||
- 核心模块:`sim_interpreter_*`、`sim_motion_*`、`sim_kinematics_*`、`sim_posemath_*`。
|
||||
- 运行时对象:`sim_hal_*`、`sim_io_*`、event log。
|
||||
- 运动集成:`sim_kinematics_motion_*`、feedback、home/limit、switchkins、userk 和 registry。
|
||||
|
||||
后续不能继续增加孤立导出函数;应先把这些函数收拢进一个有明确状态、错误回滚和 snapshot 字段的 `sim_task` runtime,再由 Worker 调用。
|
||||
|
||||
### 2.3 固定 tick 的目标顺序
|
||||
|
||||
固定 tick 顺序属于行为契约,建议按以下顺序实现并为每一步保留前后状态 hash:
|
||||
|
||||
1. 接收本 tick 的 Task/MDI/jog/设备输入,检查命令权限、模式和 abort/estop。
|
||||
2. 解释器读取一行 G-code/O-code/M-code,更新 modal、参数、当前行和 canonical queue。
|
||||
3. 执行 canonical queue 的 TP:前瞻、速度/加速度限制、blend/exact stop、pause/abort。
|
||||
4. 将笛卡尔 pose 送入 inverse kinematics,生成 joint command;奇异、越界和非有限值必须事务性失败。
|
||||
5. 推进 joint/motion queue,执行正解 feedback,更新 axis/joint/pose/velocity/following error。
|
||||
6. 按固定线程顺序执行 HAL component graph;组件失败不应推进半个 tick。
|
||||
7. 推进 spindle/tool/probe/limit/estop 等虚拟 IO 和 Python callback;Python 只能使用 allowlist API。
|
||||
8. 生成事件、报警、canonical、snapshot 和状态 hash;提交 checkpoint 或 journal 时才写持久层。
|
||||
|
||||
## 3. LinuxCNC 源程序、案例和测试的对应关系
|
||||
|
||||
LinuxCNC 的 `configs/sim` 不是文档附件,而是启动闭包和功能来源。下面先用少量代表性案例建立可跑的纵向切片,再扩展到全部 233 个入口。
|
||||
|
||||
| 功能域 | LinuxCNC 源程序 | 代表性仿真案例 | 代表性测试程序 | WASM/Worker 目标 | 前端工作流 |
|
||||
|---|---|---|---|---|---|
|
||||
| 启动、INI、资源 | `src/emc/ini/emcIniFile.cc`、`iniaxis.cc`、`inijoint.cc`、`initraj.cc` | `configs/sim/axis/minimal_xyz.ini`、`ini_with_includes/includes_demo.ini` | `tests/rs274ngc-startup`、`tests/startup-state`、`tests/io-startup` | 解析 section/include/变量、坐标和资源;构造 `sim_task_config` | 工程选择、预检错误定位、启动/重启 |
|
||||
| 解释器、canonical | `src/emc/rs274ngc/*`、`src/emc/task/emccanon.cc`、`src/emc/nml_intf/interpl.cc` | `axis/minimal_xyz.ini` 的 `G0/G1`、`axis/iocontrolv2/owordm6-ui.ini` | `tests/interp/good`、`g52`、`m98m99`、`remap/oword-pycall`、`unit_tests/interp/*` | 从文件/MDI 读行,输出 canonical 和当前行;错误回到协议 | 程序编辑器、MDI、当前行、语法/运行错误 |
|
||||
| TP、运动 | `src/emc/tp/tp.c`、`tc.c`、`tcq.c`、`blendmath.c`;`src/emc/motion/*` | `axis/minimal_xyz.ini`、`axis/gantry/gantry.ini` | `unit_tests/tp/test_blendmath.c`、`tests/motion/g0`、`tests/trajectory-planner/circular-arcs`、`tests/abort/*` | 固定 tick TP -> motion queue,支持 pause/abort/queue-buster | 运行/暂停/单步/轨迹预览/倍率 |
|
||||
| 运动学 | `src/emc/kinematics/*kins.c`、`kins_util.c`、`switchkins.c` | `axis/gantry/gantry.ini`、`axis/ldelta.ini`、`axis/pentapod/pentapod.ini`、`axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini` | `tests/matrixkins`、运动学配置案例;官方 `unit_tests/kinematics/**` 在锁定来源中缺失 | registry + FK/IK + branch/singularity + feedback/home/limit | 轴/关节/姿态、3D 模型、故障和奇异性 |
|
||||
| HAL | `src/hal/hal_lib.c`、`halmodule.cc`、`utils/halcmd*`、`src/hal/components/*.comp` | `axis/gantry/gantrysim.hal`、`axis/ini_hal_demo.ini` | `tests/hal-*`、`tests/basic`、`tests/edge/*`、`tests/module-loading/*`、`tests/hal-link-unlink` | 类型化 pin/signal/thread/function 图,固定顺序执行 | HAL browser、pin 值、连接图、波形 |
|
||||
| IO、主轴、换刀、探针 | `src/emc/iotask/ioControl*.cc`、`src/emc/tooldata/*` | `axis/iocontrolv2/*`、`axis/lathe.ini`、`axis/gladevcp/probe.ini` | `tests/io-startup/*`、`tests/toolchanger/*`、`tests/tool-info/*`、`tests/t0/*` | 虚拟主轴/工具/探针状态机,故障、互锁和事件 | 主轴、工具、探针、报警和恢复 |
|
||||
| Python/remap/filter | `src/emc/rs274ngc/interp_python.cc`、`interp_remap.cc`、`lib/python/*` | `axis/remap/*`、`axis/plasma/*`、`axis/ngcgui/*` | `tests/remap/*`、`tests/interp/python*`、`tests/interp/pymove` | Worker-only allowlist,异常和资源限额映射为 Task 错误 | remap 参数、宏、材料表、插件状态 |
|
||||
| UI/VCP/可视化 | `src/emc/usr_intf/*`、`lib/python/rs274/glcanon.py`、`hal/user_comps/vismach/*` | `axis/gladevcp/*`、`axis/qtvcp/*`、`axis/vismach/*`、`qtplasmac/*` | UI test scripts、对应配置的启动测试和浏览器 E2E | descriptor -> Web component;轨迹/姿态只读 snapshot | 控制台、VCP、3D、示波器、配置编辑 |
|
||||
| 持久化/恢复 | LinuxCNC 参数、tool table、日志语义 + 本项目 `web/workspace/*` | `sim.var`、`*.tbl`、工程 package | `tests/save.*`、`tests/source.0`,本项目 `test_recovery.mjs`、`test_migration.mjs` | checkpoint/event/journal/package 双向迁移、fail-closed | 工程导入导出、离线、刷新、恢复 |
|
||||
|
||||
### 3.1 第一批必须跑通的案例
|
||||
|
||||
先不要从最复杂的 QtVCP/PlasmaC/机器人界面开始。建议按以下顺序建立可重复的端到端样例:
|
||||
|
||||
1. `minimal_xyz.ini`:`HELLO -> ATTACH -> LOAD -> home -> PROGRAM_LOAD -> G0 X1 -> step -> snapshot`。它验证最小启动、解释器、trivkins、motion、snapshot 和前端状态。
|
||||
2. `gantry/gantry.ini`:验证 `XYZY` 重复坐标、四关节映射、`HOME_SEQUENCE=-1` 同步回零和 CoreXY/gantry HAL。
|
||||
3. `iocontrolv2/axis-iocontrolv2-demo.ini`:验证 `T/M6` 的 prepare/change、主轴停止互锁、tool table、IO pin 和失败恢复。
|
||||
4. `vismach/5axis/table-rotary-tilting/xyzac-trt.ini`:验证姿态、TRT kinematics、tool orientation、FK feedback 和 3D 轨迹。
|
||||
5. `remap/oword-pycall`:验证 O-word、Python remap、虚拟 API、异常、参数和 checkpoint/replay。
|
||||
6. `hard-limits` + `abort/*`:验证 soft/hard limit、estop、abort、following error 和恢复后状态 hash。
|
||||
7. `plasma/metric.ini` 或 `gladevcp/probe.ini`:验证虚拟探针/torch/材料表以及 UI/设备交互。
|
||||
|
||||
每个案例都必须保留原始配置资源边、native 运行结果、WASM 结果、首个 divergent tick、字段路径、错误码和状态 hash;只看页面截图不算通过。
|
||||
|
||||
## 4. 按技术路线的任务分层
|
||||
|
||||
下面把路线图的 M0-M5 和代码实际状态对齐。`verified` 表示该任务的实现证据已生成,不等于 release `accepted`。
|
||||
|
||||
| 阶段 | 目标 | 已有结果 | 未完成的关键闭环 | 退出条件 |
|
||||
|---|---|---|---|---|
|
||||
| M0 基线 | 来源、manifest、依赖、context lock | 8,053 source entries,321 test entries,清单生成器稳定 | 6,249 seed 仍未分配 feature owner | source/manifest/context hash 可重建 |
|
||||
| M1 会话骨架 | protocol、create/load/step/snapshot/recover | C ABI、Worker session、event/replay、Python manifest 已验证 | `sim_load_workspace` 尚未真正构造核心 runtime;无完整 task state | 真实 WASM Worker 可加载最小工程并恢复 |
|
||||
| M2 核心行为 | interpreter、canonical、TP、motion、kinematics | C 模块和 15 项 native CTest 通过;K0-K6 运动学切片已有 | 模块未在 `sim_task` 内按固定 tick 串联;完整 G/M/O 不全 | native/WASM golden 逐 tick 对照 |
|
||||
| M3 虚拟设备 | HAL/PLC/IO、Python、恢复和故障 | M3-CFG/RT/FS/PY 报告均有 verified implementation evidence | 981 资源阻断,HAL 动态组件和真实 Python remap 未全执行 | 故障矩阵、配额、重启、离线全通过 |
|
||||
| M4 Web 工作流 | 控制台、工程、3D、VCP、诊断 | FE-M1..M4 smoke 已通过 | UI 目前没有完整真实 G-code/Task 状态源;OPFS/跨浏览器不足 | 关键 UI workflow 与核心状态一致 |
|
||||
| M5 发布 | 全配置、兼容、oracle、浏览器、许可证、回滚 | REG-01/03/04 证据已生成但 partial | 六项 release blocker 未解除 | 全部可计入条目 accepted,`STOP_RELEASE` 清除 |
|
||||
|
||||
## 5. 后续任务和依赖图
|
||||
|
||||
### 5.1 正式 release 轨道
|
||||
|
||||
```text
|
||||
WAIT-OWNER-01 (当前 blocked)
|
||||
-> OWNER-INPUT-01: 校验六项 blocker decision
|
||||
-> CONTEXT-REFRESH-01: 审查并生成新 context lock
|
||||
-> K8-REL-17: 重新生成 closure/audit/gate
|
||||
-> 仅在 blocker 有证据解除后进入 release candidate
|
||||
```
|
||||
|
||||
六项 decision 是:manifest ownership、accepted entries、official oracle、configuration resources、browser matrix、implementation license。没有外部输入时,正确动作是等待,不是自动重跑报告。
|
||||
|
||||
### 5.2 文档冲突的处理顺序
|
||||
|
||||
仓库中部分路线文档是在前端和 REG 任务完成前生成的,存在历史状态描述。例如 `docs/linuxcnc-web-wasm-next-work.md` 仍写着“没有前端应用壳”和“当前任务是 REG-01”,但 `web/app`、`coverage/frontend-m1..m4.report.json`、`coverage/full-configuration-startup-matrix.report.json` 已给出后续实现证据。执行时按以下优先级判断:
|
||||
|
||||
1. `nextTask.md` 的 Current/Task/Verify/Stop 是唯一接续指针;
|
||||
2. `context-lock.json` 是源码、工具链、schema 和版本基线;
|
||||
3. `coverage/*.report.json`、native/WASM/Worker 测试是实际状态证据;
|
||||
4. 路线图、task map 和 next-work 用于理解目标和依赖,若与前三者冲突必须修正文档,而不是回退代码或重复任务。
|
||||
|
||||
本文件已经按这个顺序梳理;它不把旧文档中的 `REG-01`、FE-M1..M4 再次认领为当前任务。
|
||||
|
||||
### 5.3 技术实现轨道:第一个可执行起点
|
||||
|
||||
在 owner 输入和新 context 审查完成后,技术实现不应从运动学重写开始,而应从 `INT-01 sim_task 固定 tick 集成` 开始。它是前端、WASM 和后端融合的真正缺口。
|
||||
|
||||
| ID | 任务 | 主要变更 | 直接测试/产物 | 依赖 |
|
||||
|---|---|---|---|---|
|
||||
| INT-01 | 建立 `sim_task` 状态对象和 C ABI | 统一 interpreter/TP/motion/kinematics/feedback/HAL/IO/Python 引用;定义 mode、program、line、pause、abort、alarm、state hash | `wasm-core/include/sim_task.h`、native/WASM task test、ABI report | M1 ABI、protocol v1 |
|
||||
| INT-02 | `PROGRAM_LOAD/RUN/PAUSE/RESUME/ABORT/STEP` 真正执行 | Worker workspace 文件 -> G-code 行流 -> `sim_interpreter_execute`;未知命令和非法模式 fail-closed,不再通用 `verified` | `minimal_xyz` E2E、`tests/interp/*` 迁移结果、command golden | INT-01、configuration resolver |
|
||||
| INT-03 | TP -> IK -> motion -> FK 固定 tick | 复用 `sim_kinematics_motion`、feedback 和 `sim_motion_queue`;实现事务回滚、queue-buster、奇异/限位错误 | `motion/g0`、`trajectory-planner`、gantry/TRT scenarios;首个差异 tick report | INT-01/02、K0-K6 |
|
||||
| INT-04 | HAL/IO/Python 进入同一 tick | 配置图实例化 HAL;设备命令由 `sim_io` 作为权威源;M6、主轴、探针、estop 与 Task 状态互锁 | `io-startup`、`toolchanger`、`hard-limits`、`remap/oword-pycall` | INT-03、M3 RT/PY |
|
||||
| INT-05 | 配置资源到 runtime 的启动闭包 | 将 resolver 的 resolved rows 转成可加载 workspace;对 981 blocked rows 给出 source/line/code;处理 `.var/.tbl/.ngc/.py/.xml/.hal` | `REG-01` 每入口 ready/blocked/error;configuration startup report | M3-CFG、INT-01 |
|
||||
| INT-06 | Python/remap/filter 真执行 | Pyodide Worker bridge 加入 session/task callback、参数、异常、时间/内存限额;宿主能力一律拒绝 | `tests/remap/*`、python session/replay、bridge report | INT-02、M3-PY |
|
||||
| INT-07 | 真实 OPFS 与 Worker 重启恢复 | 实现 OPFS adapter、journal commit、checkpoint、lease 和 offline cache;MemoryStorage 仅保留测试替身 | browser reload/crash/quota/offline matrix、migration/rollback report | M3-FS、INT-01 |
|
||||
| INT-08 | 前端改为权威 Task snapshot | `WorkerClient` 增加 program/MDI/jog/override/subscribe;reducer 消费真实 snapshot/event,不用本地假状态 | 浏览器 E2E:运行/暂停/单步/报警/恢复;FE report 升级 | INT-02/04、FE-M1..M4 |
|
||||
| INT-09 | 3D 轨迹和机床模型 | 由 snapshot 的 pose/joints/toolpath 驱动 Three.js;支持 axis/gantry/TRT/机器人姿态和故障可视化 | vismach samples、canvas/WebGL pixel/nonblank、性能矩阵 | INT-03、INT-08 |
|
||||
| INT-10 | 全量配置启动和差异闭环 | 运行 233 entrypoints;按 ready/blocked/error 分类,不删样本;逐个关联 source/resource/component | `coverage/full-configuration-startup-matrix.report.json`、native/WASM/Worker summaries | INT-05/06/07/08 |
|
||||
|
||||
任务顺序中,INT-01 至 INT-04 是功能主链,INT-05 至 INT-07 是运行时闭环,INT-08/09 是前端融合,INT-10 是规模化回归。任何任务都不能通过降低检查、删除 blocked 样本或把资源缺失标为 `REAL-EXCLUDED` 来“完成”。
|
||||
|
||||
## 6. 测试和验收办法
|
||||
|
||||
### 6.1 四层测试
|
||||
|
||||
| 层 | 测试内容 | 当前命令/来源 | 作用 |
|
||||
|---|---|---|---|
|
||||
| L0 基线 | context、source、manifest、pointer、下载索引 | `validate_baseline.py`、`validate_task_pointer.py`、脚本 unittest | 防止在错误源码或错误任务上工作 |
|
||||
| L1 native C | `sim_*` ABI、posemath、interpreter、motion、kinematics、HAL、IO | `ctest --test-dir build-native --output-on-failure`,15 项 | 算法和错误码的快速反馈 |
|
||||
| L2 WASM/Worker | Emscripten exports、协议、配置/HAL preflight、设备、Python、replay/recovery | `node web/worker/test_*.mjs`、WASM tests | 验证浏览器运行时边界 |
|
||||
| L3 浏览器/E2E | WorkerClient、命令幂等、UI 状态、OPFS、离线、3D、跨浏览器 | `web/app/test_*.mjs`、后续 Playwright/浏览器矩阵 | 验证用户可见工作流和恢复 |
|
||||
|
||||
### 6.2 每个功能切片的最小验收包
|
||||
|
||||
每个 INT/REG 任务必须同时包含:
|
||||
|
||||
- 一个正常案例,例如 `G0 X1` 或 spindle start/stop;
|
||||
- 一个边界案例,例如重复 modal、queue full、奇异点、超速或缺资源;
|
||||
- 一个错误/报警案例,例如 syntax、limit、estop、Python capability denied;
|
||||
- 一个 pause/resume/abort/recovery 案例;
|
||||
- 一个持久化或 Worker 重启案例(适用时);
|
||||
- native 结果、WASM 结果、事件顺序、当前行、错误码、状态 hash 和首个 divergent tick。
|
||||
|
||||
### 6.3 参考命令
|
||||
|
||||
开始任何实现任务前执行:
|
||||
|
||||
```bash
|
||||
python3 scripts/validate_baseline.py
|
||||
python3 scripts/validate_download_index.py
|
||||
python3 scripts/validate_task_pointer.py
|
||||
python3 -m unittest discover -s scripts -p 'test_*.py' -v
|
||||
ctest --test-dir build-native --output-on-failure
|
||||
```
|
||||
|
||||
完成一个核心垂直切片后至少执行:
|
||||
|
||||
```bash
|
||||
node web/worker/test_wasm_session.mjs
|
||||
node web/worker/test_sim_session.mjs
|
||||
node web/worker/test_configuration_preflight.mjs
|
||||
node web/worker/test_hal_preflight.mjs
|
||||
node web/worker/test_virtual_devices.mjs
|
||||
node web/worker/test_workspace_session_integration.mjs
|
||||
node web/app/test_frontend_m1.mjs
|
||||
node web/app/test_frontend_m2.mjs
|
||||
node web/app/test_frontend_m3.mjs
|
||||
node web/app/test_frontend_m4.mjs
|
||||
```
|
||||
|
||||
## 7. 当前阻塞和处理原则
|
||||
|
||||
| 阻塞 | 数量/现象 | 正确处理 |
|
||||
|---|---:|---|
|
||||
| manifest seed 无 owner | 6,249 | owner assignment;不能本地猜测并升级 accepted |
|
||||
| 资源闭包 | 997 missing、91 unresolved、59 unindexed、3 unregistered startup | 按 source/line 逐项补资源或经审查明确阻断;不能 basename 猜测覆盖 |
|
||||
| 官方 oracle | 锁定来源缺少 `linuxcnc/unit_tests/kinematics/**` 和 recovery roots | 补充真实官方输入并新建 context,或由 owner 批准替代 oracle;不能把 matrixkins 冒充官方 |
|
||||
| 浏览器矩阵 | context 中 Chromium/Firefox/Safari minimum 为 TBD;当前只测 Chromium | owner 锁定最低版本,执行三浏览器 workflow/offline/fault/performance |
|
||||
| 实现许可证 | 57 个 Web/WASM 条目 `blocked_unassigned` | 完成 SPDX/NOTICE 决策;上游 LinuxCNC 许可证不能自动覆盖新实现 |
|
||||
| 端到端 Task | `sim_api` bootstrap,Worker 未串联 interpreter/TP/IK/HAL/IO | 以 INT-01 为技术起点,先固定状态/ABI/错误回滚,再扩展功能 |
|
||||
|
||||
## 8. 任务完成定义
|
||||
|
||||
一个任务只有在以下条件全部满足时才能标记 `verified`:
|
||||
|
||||
1. 代码、manifest/source map 和协议/ABI 变更互相可追溯;
|
||||
2. 正常、边界、错误、恢复测试均存在,且测试不依赖删除样本或静默降级;
|
||||
3. native/WASM(适用时)和 Worker/browser(适用时)结果可重复;
|
||||
4. snapshot、event、checkpoint、operation ledger 的状态 hash 能重建;
|
||||
5. 性能、内存、OPFS 配额和离线影响有记录;
|
||||
6. 失败时有明确 error code、source path/line、rollback point 和下一任务;
|
||||
7. 仍然受发布 blocker 影响的任务保持 `partial`/`blocked`,不得伪装为 `accepted`。
|
||||
|
||||
项目最终完成不是“页面能点”或“15 个 CTest 全绿”,而是:任意纳入范围的 `configs/sim` 入口都能进入 `READY` 或给出可诊断的 fail-closed 错误;典型 G-code/IO/运动学/恢复场景在 native、WASM 和浏览器状态上可复现;所有来源、资源、测试、oracle、许可证和排除项都有闭合证据。
|
||||
@@ -67,6 +67,7 @@
|
||||
| `K8-REL-16` | 在没有 owner 决定或新锁定输入时重建 closure/audit/remediation/owner-review 证据;六项 blocker identity、context consistency 和 `STOP_RELEASE` 均 verified,owner_inputs=0、gate_refresh=blocked,context lock 未修改,任务保持 blocked | `coverage/owner-review-gate-refresh.report.json`, `coverage/release-candidate-gate.report.json`, `coverage/post-release-audit.report.json`, `scripts/generate_owner_review_gate_refresh.py`, `scripts/test_owner_review_gate_refresh.py` |
|
||||
| `PROJECT-AUDIT-01` | 整体审计确认 K0-K6 运动学实现和 15 项 native CTest 已通过;运动学 `partial/blocked` 仅因锁定来源缺少官方 oracle;当前开发瓶颈转为配置启动闭包,下一任务为 M3-CFG-01,K8 release 六项 blocker/`STOP_RELEASE` 独立保留 | `docs/linuxcnc-web-wasm-next-work.md`, `coverage/kinematics-registry.report.json`, `coverage/kinematics-motion.report.json`, `coverage/kinematics-feedback.report.json`, `coverage/kinematics-home-limit.report.json`, `coverage/configuration-regression.report.json` |
|
||||
| `PROJECT-AUDIT-02` | 重新梳理任务图确认 M3/FE/REG 实现证据均已 verified;K8-REL-17 owner_inputs=0、gate_refresh=blocked,发现旧 Next 自引用风险;生成 GOV-01 readiness 后进入不自动重试的 WAIT-OWNER-01,六项 blocker 和 `STOP_RELEASE` 保持 verified | `docs/linuxcnc-web-wasm-task-map.md`, `coverage/project-task-readiness.report.json`, `nextTask.md` |
|
||||
| `OWNER-INTAKE-01` | 建立六项 owner decision 输入模板、结构/identity/context/evidence hash 验证器和 blocked report;当前输入文件缺失,验证结果为 blocked,gate refresh 永远 blocked,不修改 context lock 或 release gate | `coverage/owner-decision-intake.template.json`, `coverage/owner-decision-intake.report.json`, `docs/linuxcnc-web-wasm-owner-decision-intake.md`, `scripts/validate_owner_decision_intake.py`, `scripts/test_owner_decision_intake.py` |
|
||||
| `M3-CFG-01` | 确定性配置 resolver 和 Worker preflight 完成;16,093 条资源全部分类,15,112 resolved、981 blocked;显式 `LIB:`、参数尾缀、相对路径、仓库外路径和宿主路径均有明确策略;原始索引 hash、context lock、187 个脚本测试、15 个 native CTest 和 Worker/workspace 测试通过 | `web/workspace/configuration.mjs`, `web/worker/sim-session.mjs`, `coverage/configuration-startup-closure.report.json`, `scripts/generate_configuration_startup_closure.py`, `scripts/test_configuration_startup_closure.py`, `web/workspace/test_configuration.mjs`, `web/worker/test_configuration_preflight.mjs` |
|
||||
| `M3-RT-02` | 生成 145 个 `.comp` 组件 manifest、1,220 pins、140 params、530 functions 和 193 个 HAL 文件的 4,566 条配置图记录;manifest、容量、资源边和动态加载拒绝检查 verified,29 个 `loadusr` 与 124 个变量化/未声明 `loadrt` 保持 blocked;190 个脚本测试、15 个 native CTest 和 Worker/workspace 测试通过 | `coverage/hal-component-manifest.report.json`, `scripts/generate_hal_component_manifest.py`, `scripts/test_hal_component_manifest.py`, `web/workspace/hal-configuration.mjs`, `web/worker/sim-session.mjs`, `web/worker/test_hal_preflight.mjs` |
|
||||
| `M3-RT-03` | 虚拟设备故障矩阵完成;26 个 spindle/tool/probe/limit/estop/非有限/恢复场景进入 EventLog,连续执行与 replay state hash 一致,设备 checkpoint 增量恢复通过;192 个脚本测试、15 个 native CTest 和 Worker/workspace 测试通过 | `coverage/virtual-device-fault-matrix.report.json`, `scripts/generate_virtual_device_fault_matrix.py`, `scripts/test_virtual_device_fault_matrix.py`, `web/worker/virtual-devices.mjs`, `web/worker/test_virtual_devices.mjs`, `web/worker/test_device_recovery.mjs` |
|
||||
|
||||
12
nextTask.md
12
nextTask.md
@@ -9,7 +9,7 @@ pointer_version: 3
|
||||
updated: 2026-08-21
|
||||
context_id: linuxcnc-2.10.0-pre0-wasm-m1-py-c1
|
||||
milestone: K8
|
||||
last_verified_checkpoint: PROJECT-AUDIT-02
|
||||
last_verified_checkpoint: OWNER-INTAKE-01
|
||||
task:
|
||||
id: WAIT-OWNER-01
|
||||
status: blocked
|
||||
@@ -43,11 +43,11 @@ python3 scripts/validate_task_pointer.py
|
||||
|
||||
**目标**:等待六项 owner decision 或新的 locked context input;期间不重跑 K8 release gate、不创建新 context、不重复执行已完成 implementation matrix。
|
||||
|
||||
**直接输入**:`docs/linuxcnc-web-wasm-task-map.md`、`coverage/project-task-readiness.report.json`、全部 M3/FE/REG 报告、`coverage/release-candidate-gate.report.json`、`coverage/post-release-audit.report.json`、`coverage/owner-review-gate-refresh.report.json`、`context-lock.json`。
|
||||
**直接输入**:`coverage/owner-decision-intake.template.json`、`coverage/owner-decisions.json`(当前不存在)、`scripts/validate_owner_decision_intake.py`、`context-lock.json`、`coverage/project-task-readiness.report.json`。
|
||||
|
||||
**必须产出**:保持 `coverage/project-task-readiness.report.json` 和所有实现报告作为 blocked evidence;owner input 到位前不生成新 context、不刷新 gate。
|
||||
**必须产出**:`coverage/owner-decision-intake.template.json`、`docs/linuxcnc-web-wasm-owner-decision-intake.md`、`coverage/owner-decision-intake.report.json` 和验证器;当前 `owner-decisions.json` 缺失时只生成 blocked report,不创建 context、不刷新 gate。
|
||||
|
||||
**完成条件**:六项 owner decision 或新 locked context input 可定位后才解除 blocked;当前 `owner_inputs=0`、gate refresh=blocked、`STOP_RELEASE` verified;不自动回到 K8-REL-17。
|
||||
**完成条件**:模板精确包含六项 blocker;缺失、重复、未知、context mismatch、字段不全和非 approve decision 均 blocked;完整六项 approve 只代表 intake shape verified,gate refresh 仍 blocked,必须另经新 context 审查;`context-lock.json` 不被验证器修改。
|
||||
|
||||
**非目标**:不修改 kinematics 算法或 ABI/protocol/schema/context lock;不替 owner 做 manifest/许可证/browser/oracle 决策;不实现真实硬件驱动;不升级 `accepted`,不清除 K8 release blockers。
|
||||
|
||||
@@ -55,7 +55,7 @@ python3 scripts/validate_task_pointer.py
|
||||
|
||||
## Next
|
||||
|
||||
本任务是 external wait,不执行自动重试;只有六项 owner decision 或新 locked context input 到位时,才新建 context 并重新打开 K8 release task。
|
||||
本任务是 external wait,不执行自动重试;owner 将 decision 文件放置为 `coverage/owner-decisions.json` 后运行 `python3 scripts/validate_owner_decision_intake.py`。只有验证通过并完成新 context 审查后,才重新打开 K8 release task。
|
||||
|
||||
## Verify
|
||||
|
||||
@@ -66,6 +66,8 @@ python3 scripts/generate_virtual_device_fault_matrix.py
|
||||
python3 scripts/generate_workspace_session_integration.py
|
||||
python3 scripts/generate_project_task_readiness.py
|
||||
python3 scripts/test_project_task_readiness.py
|
||||
python3 scripts/validate_owner_decision_intake.py
|
||||
python3 scripts/test_owner_decision_intake.py
|
||||
node python-runtime/test_runtime.mjs
|
||||
node web/worker/test_python_bridge.mjs
|
||||
python3 scripts/generate_python_worker_bridge.py
|
||||
|
||||
50
scripts/test_owner_decision_intake.py
Normal file
50
scripts/test_owner_decision_intake.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[0]))
|
||||
from validate_owner_decision_intake import BLOCKERS, validate
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def complete_input(context_id):
|
||||
return {"schema": "owner-decision-intake.v1", "context_id": context_id, "decision_set_id": "test", "decisions": [{"blocker": blocker, "decision": "approve", "owner": "owner", "reviewer": "reviewer", "rationale": "reviewed", "evidence": [{"path": "context-lock.json", "sha256": "0" * 64}], "new_context_required": True} for blocker in BLOCKERS]}
|
||||
|
||||
|
||||
class OwnerDecisionIntakeTests(unittest.TestCase):
|
||||
def test_missing_input_fails_closed(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
(root / "context-lock.json").write_text(json.dumps({"context_id": "ctx"}))
|
||||
report = validate(root, Path("owner-decisions.json"), Path("report.json"))
|
||||
self.assertEqual(report["status"], "blocked")
|
||||
self.assertEqual(report["checks"]["gate_refresh_allowed"]["status"], "blocked")
|
||||
self.assertEqual(report["checks"]["context_lock_unchanged"]["status"], "verified")
|
||||
|
||||
def test_complete_shape_is_valid_but_does_not_refresh_gate(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
(root / "context-lock.json").write_text(json.dumps({"context_id": "ctx"}))
|
||||
(root / "owner-decisions.json").write_text(json.dumps(complete_input("ctx")))
|
||||
report = validate(root, Path("owner-decisions.json"), Path("report.json"))
|
||||
self.assertEqual(report["status"], "verified")
|
||||
self.assertEqual(report["checks"]["decision_approval"]["status"], "verified")
|
||||
self.assertEqual(report["checks"]["gate_refresh_allowed"]["status"], "blocked")
|
||||
|
||||
def test_duplicate_blocker_is_rejected(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
(root / "context-lock.json").write_text(json.dumps({"context_id": "ctx"}))
|
||||
value = complete_input("ctx")
|
||||
value["decisions"][-1]["blocker"] = BLOCKERS[0]
|
||||
(root / "owner-decisions.json").write_text(json.dumps(value))
|
||||
report = validate(root, Path("owner-decisions.json"), Path("report.json"))
|
||||
self.assertEqual(report["status"], "blocked")
|
||||
self.assertIn(BLOCKERS[5], report["checks"]["six_blocker_identity"]["missing"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
114
scripts/validate_owner_decision_intake.py
Normal file
114
scripts/validate_owner_decision_intake.py
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate the external six-decision owner input without mutating release state."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
LOCK = Path("context-lock.json")
|
||||
INPUT = Path("coverage/owner-decisions.json")
|
||||
OUTPUT = Path("coverage/owner-decision-intake.report.json")
|
||||
TEMPLATE = Path("coverage/owner-decision-intake.template.json")
|
||||
BLOCKERS = ("manifest_seed_unassigned", "accepted_entries_missing", "official_oracle_missing", "configuration_resources_missing", "browser_matrix_unverified", "license_closure_incomplete")
|
||||
DECISIONS = {"approve", "reject", "defer"}
|
||||
|
||||
|
||||
def canonical(value: Any) -> str:
|
||||
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
||||
|
||||
|
||||
def digest(value: Any) -> str:
|
||||
return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def load_json(path: Path) -> tuple[dict[str, Any], str | None]:
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(value, dict): raise ValueError("root must be object")
|
||||
return value, None
|
||||
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
|
||||
return {}, str(exc)
|
||||
|
||||
|
||||
def validate(root: Path, input_path: Path = INPUT, output_path: Path = OUTPUT) -> dict[str, Any]:
|
||||
root = root.resolve()
|
||||
lock, lock_error = load_json(root / LOCK)
|
||||
value, input_error = load_json(root / input_path)
|
||||
input_present = (root / input_path).is_file()
|
||||
errors = [error for error in (lock_error, input_error) if error]
|
||||
decisions = value.get("decisions") if isinstance(value.get("decisions"), list) else []
|
||||
observed = [row.get("blocker") for row in decisions if isinstance(row, dict)]
|
||||
duplicate_ids = sorted({blocker for blocker in observed if observed.count(blocker) > 1 and blocker is not None})
|
||||
missing_ids = sorted(set(BLOCKERS) - set(observed))
|
||||
unknown_ids = sorted(set(observed) - set(BLOCKERS))
|
||||
row_errors: list[dict[str, Any]] = []
|
||||
for index, row in enumerate(decisions):
|
||||
if not isinstance(row, dict):
|
||||
row_errors.append({"index": index, "error": "decision_not_object"}); continue
|
||||
if row.get("blocker") not in BLOCKERS: row_errors.append({"index": index, "error": "unknown_blocker"})
|
||||
if row.get("decision") not in DECISIONS: row_errors.append({"index": index, "error": "decision_invalid"})
|
||||
for field in ("owner", "reviewer", "rationale"):
|
||||
if not isinstance(row.get(field), str) or not row[field].strip(): row_errors.append({"index": index, "error": f"{field}_missing"})
|
||||
if not isinstance(row.get("new_context_required"), bool): row_errors.append({"index": index, "error": "new_context_required_invalid"})
|
||||
evidence = row.get("evidence")
|
||||
if not isinstance(evidence, list) or not evidence: row_errors.append({"index": index, "error": "evidence_missing"})
|
||||
else:
|
||||
for evidence_row in evidence:
|
||||
if not isinstance(evidence_row, dict) or not isinstance(evidence_row.get("path"), str) or not isinstance(evidence_row.get("sha256"), str) or len(evidence_row["sha256"]) != 64:
|
||||
row_errors.append({"index": index, "error": "evidence_invalid"})
|
||||
context_ok = bool(lock.get("context_id")) and value.get("context_id") == lock.get("context_id") if input_present else False
|
||||
shape_ok = input_present and not errors and value.get("schema") == "owner-decision-intake.v1" and len(decisions) == 6 and not duplicate_ids and not missing_ids and not unknown_ids and not row_errors and context_ok
|
||||
all_approved = shape_ok and all(row.get("decision") == "approve" for row in decisions)
|
||||
checks = {
|
||||
"input_file_present": {"status": "verified" if input_present else "blocked", "path": input_path.as_posix()},
|
||||
"schema_shape": {"status": "verified" if shape_ok else "blocked", "schema": value.get("schema")},
|
||||
"six_blocker_identity": {"status": "verified" if not missing_ids and not unknown_ids and not duplicate_ids and len(decisions) == 6 else "blocked", "missing": missing_ids, "unknown": unknown_ids, "duplicates": duplicate_ids},
|
||||
"context_consistency": {"status": "verified" if context_ok else "blocked", "locked": lock.get("context_id"), "input": value.get("context_id")},
|
||||
"decision_approval": {"status": "verified" if all_approved else "blocked", "approved": sum(row.get("decision") == "approve" for row in decisions if isinstance(row, dict)), "required": 6},
|
||||
"gate_refresh_allowed": {"status": "blocked", "reason": "owner intake validation never refreshes the gate; reviewed new context is required after all six approvals"},
|
||||
"context_lock_unchanged": {"status": "verified", "action": "no mutation"},
|
||||
}
|
||||
report: dict[str, Any] = {
|
||||
"schema": "owner-decision-intake-report.v1",
|
||||
"context_id": lock.get("context_id"),
|
||||
"task_id": "K8-REL-18",
|
||||
"status": "verified" if shape_ok else "blocked",
|
||||
"implementation_status": "verified",
|
||||
"release_status": "blocked_external_owner",
|
||||
"candidate_eligible": False,
|
||||
"release_action": "STOP_RELEASE",
|
||||
"input": {"path": input_path.as_posix(), "status": "present" if input_present else "missing", "sha256": sha256(root / input_path) if input_present else None},
|
||||
"template": {"path": TEMPLATE.as_posix(), "sha256": sha256(root / TEMPLATE) if (root / TEMPLATE).is_file() else None},
|
||||
"checks": checks,
|
||||
"errors": errors + row_errors,
|
||||
"next_transition": "wait for owner-decisions.json or new locked context input; do not rerun browser or implementation matrices",
|
||||
"report_sha256": None,
|
||||
}
|
||||
report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"})
|
||||
destination = output_path if output_path.is_absolute() else root / output_path
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
destination.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
|
||||
return report
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
|
||||
parser.add_argument("--input", type=Path, default=INPUT)
|
||||
parser.add_argument("--output", type=Path, default=OUTPUT)
|
||||
args = parser.parse_args()
|
||||
report = validate(args.repo_root.resolve(), args.input, args.output)
|
||||
print(f"owner_decision_intake={report['status']} present={report['input']['status']} refresh={report['checks']['gate_refresh_allowed']['status']}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user