Files
linuxcncWebWASM/docs/linuxcnc-web-wasm-contracts.md

7.1 KiB
Raw Permalink Blame History

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 使用:

{
  "schema": "snapshot.v1",
  "snapshot_id": "snap:<seq>:<state_hash>",
  "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}。每条事件固定字段:

{
  "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。