# LinuxCNC Web/WASM 契约快照 **契约版本**:`sim-contracts.v1` **任务**:`P0-03` **Context**:`linuxcnc-2.10.0-pre0-wasm-m1-py-c1` **状态**:implementation-verified(只冻结现有边界;不表示 release accepted) 本文冻结浏览器 Dedicated Worker 与 WASM 核心之间可观察的字段、版本和错误策略。它是当前 context 的兼容性快照,不替换 `context-lock.json`,也不改变已有 C ABI、Worker protocol 或 recovery schema。`sim_task.v1` 只冻结 P1-01 将实现的状态对象形状;本任务不实现该 runtime。 ## 1. 版本和边界 | 契约 | 线上的版本标识 | 当前实现边界 | |---|---|---| | `sim_task` | `sim_task.v1` | P1-01 authoritative state 预留;本任务只做形状校验 | | 命令信封 | `command-envelope.v1` | Worker 输入使用 `schema_version: 1` | | 快照 | `snapshot.v1` | `web/worker/replay.mjs` 的 snapshot 记录 | | 事件日志 | `event-log.v1` | `web/worker/replay.mjs` 的事件日志 | | 工作区包 | `workspace-package.v1` | 包 schema 固定;`format_version` 1/2 是同一 envelope 的可验证迁移 | 版本号是整数且不隐式降级。未知版本、缺少必填字段、未知顶层字段、非有限数、哈希或游标不一致都 fail closed。新必填字段或语义变化必须建立新 schema 标识和迁移测试;同一 v1 只允许增加明确标为可选的诊断字段。 ## 2. `sim_task.v1` 这是单 Worker authoritative task 的可观察状态,不是 C ABI 结构体。字段顺序在 canonical JSON 中按键名排序,数值使用 JSON 安全整数或有限 IEEE-754 数。 | 字段 | 类型 | 要求 | |---|---|---| | `schema` | string | 固定 `sim_task.v1` | | `context_id`、`session_id` | string | 非空;恢复时必须与锁定 context/session 相符 | | `state` | enum | `NEW`、`READY`、`RUNNING`、`PAUSED`、`ESTOP`、`FAULT`、`STOPPED`、`DESTROYED` | | `mode` | enum | `MANUAL`、`MDI`、`AUTO` | | `program_status` | enum | `idle`、`loaded`、`running`、`paused`、`completed`、`aborted`、`fault` | | `seq` | positive integer | 最后应用的命令序号 | | `simulation_tick` | non-negative integer | 逻辑 tick;不使用墙钟 | | `current_line` | non-negative integer or null | 当前程序行;未知时为 null | | `state_hash` | string | 对除 hash 外的完整状态做确定性 hash | P1-01 可以在同一 schema 内增加由契约测试先登记的可选域;改变上述语义或枚举必须升级版本。Task 状态、事件、快照和 operation ledger 必须在相同 tick 边界提交。 ## 3. `command-envelope.v1` 命令及其 Worker 响应共享以下信封字段: | 字段 | 要求 | |---|---| | `schema_version` | 固定整数 `1`,对应 C `sim_protocol_accept` 的 schema 参数 | | `kind` | 非空字符串,长度不超过 128(C `has_text` 边界);命令种类由 capability/Worker 白名单决定 | | `context_id` | 非空字符串,必须等于 context lock | | `session_id` | 非空字符串;同一会话不可改变 | | `seq` | 从 1 开始严格连续递增;重复副作用通过 `operation_id` 对账 | | `operation_id` | 副作用命令必填的非空字符串,长度不超过 128;只读查询可省略 | | `simulation_tick` | 可选的非负安全整数;不得小于 Worker 当前 tick | | `sent_at_monotonic_ms` | 可选有限数,仅用于诊断,绝不参与状态或 hash | | `payload` | 必填 JSON object;不能是数组、null 或包含非有限数 | 当前 C ABI 的状态与错误保持不变:`SIM_PROTOCOL_ERR_SCHEMA=-100`、`SEQUENCE=-101`、`STATE=-102`、`OPERATION_ID=-103`、`KIND=-104`、`ARGUMENT=-105`、`TICK=-106`。Worker 以稳定的 `protocol_*` 错误键映射这些结果,不能把错误改成成功或静默重排命令。 ## 4. `snapshot.v1` `SnapshotStore` 导出的对象固定为 `{schema, snapshots}`;每条 snapshot 使用: ```json { "schema": "snapshot.v1", "snapshot_id": "snap::", "base_snapshot_id": null, "seq": 1, "tick": 0, "state": {}, "state_hash": "fnv1a64:<16 lowercase hex>" } ``` `seq` 为正整数且同一序号只能有一个内容,`tick` 不回退;增量 snapshot 的 `base_snapshot_id` 必须引用已导入的 snapshot。`state_hash` 和 `snapshot_id` 必须由同一 canonical state 重算。容量不足、重复序号、缺少 base、哈希不符和非有限 state 均拒绝,导入失败不得替换当前 store。 ## 5. `event-log.v1` 日志对象固定为 `{schema: "event-log.v1", cursor, tick, events}`。每条事件固定字段: ```json { "seq": 1, "tick": 0, "kind": "SESSION_READY", "payload": {}, "payload_hash": "fnv1a64:<16 lowercase hex>" } ``` `seq` 从 1 严格连续,`tick` 单调不减,`kind` 非空且最多 64 个字符,payload 必须是 object。C 端保留 `sim_event_record` 的固定宽度布局(`uint32_t seq/tick`、`uint64_t payload_hash`、32 字节 kind),并保留 `SIM_EVENT_CAPACITY=256` 与错误码 `-200..-204`。WASM 和 Worker 对同一输入必须得到相同 cursor/tick/hash;半成品导入不改变当前日志。 ## 6. `workspace-package.v1` 包 envelope 的必填字段为 `schema`、`format_version`、`context_id`、`committed_seq`、`checkpoint`、`timeline`、`journal`、`content_hash`、`package_hash`。`format_version: 1` 是当前基线;已实现的 `format_version: 2` 只添加 `migration` 和 `integrity` 元数据,事件、snapshot、checkpoint、journal payload 保持 byte-for-byte v1 兼容。 迁移规则: 1. v1→v2 必须标记 `lossless: true`、源 package hash 和 timeline/journal/checkpoint integrity hash。 2. v2→v1 只有在源 hash 对账成功时允许;原包不可变,回滚只生成新目标包。 3. 不支持的版本、context、提交序号、timeline/checkpoint、content/package hash 或 journal 顺序均拒绝。 4. 缺失/损坏资源在离线模式返回明确错误;不能用空包或当前内存状态替代已提交包。 ## 7. 兼容性决策记录 - **ABI**:保留 `sim_api.h`、`sim_protocol.h`、`sim_event.h` 的函数签名、整数宽度、错误码和容量语义;没有修改锁定 ABI。 - **协议**:Worker 继续传递 `schema_version: 1`,C validator 继续拒绝非 1、序号缺口、tick 回退和副作用命令缺失 `operation_id`。 - **快照/事件**:沿用 `replay.mjs` 的 `schema` 字段和 FNV-1a-64 canonical hash;不把 `snapshot.v1` 或 `event-log.v1` 改成新的顶层结构。 - **工作区**:沿用现有 workspace migration 的 v1/v2 envelope;v2 不是新的 payload schema,不修改已锁定的事件/快照/checkpoint/journal 版本。 - **未知字段**:v1 顶层未知字段和缺失字段 fail closed;仅 `sent_at_monotonic_ms` 这一诊断字段可选且不影响 hash。需要其它字段时先建 v2 schema/迁移测试。 - **排除边界**:真实硬件、宿主路径、实时内核和动态库仍是 `REAL-EXCLUDED`;主轴、编码器、探针、限位、换刀、冷却和 IO 的仿真契约不因此排除。 直接证据:`web/worker/contracts.mjs`、`web/worker/test_contracts.mjs`、`wasm-core/tests/sim_contract_test.c`、`scripts/test_sim_contract.py`、`coverage/sim-contract.report.json`。