@twinsearth/w2m-dsh-plugin 2.0.3 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +17 -0
  4. package/README.md +187 -462
  5. package/bin/w2m-joint.mjs +69 -7
  6. package/codex/mcp-servers.toml +1 -1
  7. package/docs/acceptance/2.0.4/RELEASE-2040.json +22 -0
  8. package/docs/acceptance/2.0.5/RELEASE-205.json +21 -0
  9. package/docs/acceptance/2.0.5/RELEASE-2050.json +22 -0
  10. package/docs/adr/0001-collaboration-crdt.md +59 -0
  11. package/lib/client.js +2754 -2599
  12. package/package.json +2 -2
  13. package/plugin.json +1 -1
  14. package/src/agent/agent.mjs +97 -36
  15. package/src/agent/artifact-delivery.mjs +89 -0
  16. package/src/agent/caps.mjs +16 -2
  17. package/src/agent/sinks/git-sink.mjs +78 -0
  18. package/src/agent/sinks/ssh-sink.mjs +83 -0
  19. package/src/agent/sse-cursor.mjs +16 -0
  20. package/src/agent/sse-watchdog.mjs +71 -0
  21. package/src/plugin/dataplane-sink-route.mjs +113 -0
  22. package/src/plugin/fleet-chat-route.mjs +11 -0
  23. package/src/plugin/gpw.mjs +99 -15
  24. package/src/plugin/joint-dev/dispatch.mjs +5 -1
  25. package/src/plugin/joint-dev/merge-approval.mjs +115 -0
  26. package/src/plugin/joint-dev/progress.mjs +107 -0
  27. package/src/plugin/joint-dev/relay-dispatch.mjs +52 -22
  28. package/src/plugin/project-stream-route.mjs +58 -0
  29. package/src/plugin/tools-joint.mjs +6 -1
  30. package/src/plugin/tools.mjs +25 -5
  31. package/src/relay/fleet-messages.mjs +48 -24
  32. package/src/relay/project-room.mjs +47 -2
  33. package/src/relay/server.mjs +11 -5
  34. package/src/relay/state.mjs +63 -3
  35. package/src/util/chat-crypto.mjs +70 -1
  36. package/docs/COLLAB/COLLABORATION-MODEL.md +0 -240
  37. package/docs/COLLAB/EXECUTION-AUTH-MODEL.md +0 -361
  38. package/docs/COLLAB/M1-ACCEPTANCE-RUNBOOK.md +0 -70
  39. package/docs/COLLAB/M1-MAINTENANCE.md +0 -313
  40. package/docs/COLLAB/M2-TASK-GRANT.md +0 -231
  41. package/docs/COLLAB/M3-GIT-NATIVE-TASKS.md +0 -291
  42. package/docs/COLLAB/M4-PROJECT-ROOM.md +0 -260
  43. package/docs/COLLAB/M5-MULTI-CLIENT-PRIMITIVES.md +0 -263
  44. package/docs/COLLAB/M6-CROSS-NETWORK-RELIABILITY.md +0 -231
  45. package/docs/COLLAB/M7-METRICS-ACCEPTANCE.md +0 -263
  46. package/docs/COLLAB/TASK-MODEL.md +0 -281
  47. package/docs/acceptance/1.9.10/RELEASE-1910.json +0 -21
  48. package/docs/acceptance/1.9.11/RELEASE-1911.json +0 -20
  49. package/docs/acceptance/1.9.12/RELEASE-1912.json +0 -20
  50. package/docs/acceptance/1.9.13/RELEASE-1913.json +0 -20
  51. package/docs/acceptance/1.9.14/RELEASE-1914.json +0 -20
  52. package/docs/acceptance/1.9.15/RELEASE-1915.json +0 -22
  53. package/docs/acceptance/1.9.16/RELEASE-1916.json +0 -21
  54. package/docs/acceptance/1.9.17/RELEASE-1917.json +0 -20
  55. package/docs/acceptance/1.9.19/RELEASE-1919.json +0 -21
  56. package/docs/acceptance/1.9.20/RELEASE-1920.json +0 -22
  57. package/docs/acceptance/1.9.4/RELEASE-194.json +0 -23
  58. package/docs/acceptance/1.9.5/RELEASE-195.json +0 -23
  59. package/docs/acceptance/1.9.6/RELEASE-196.json +0 -22
  60. package/docs/acceptance/1.9.7/RELEASE-197.json +0 -21
  61. package/docs/acceptance/1.9.8/RELEASE-198.json +0 -21
  62. package/docs/acceptance/1.9.9/RELEASE-199.json +0 -21
  63. package/lib/client.js.bak2 +0 -640
  64. package/src/plugin/dataflow.mjs +0 -72
  65. package/src/plugin/fleet-target.mjs.bak-arch2 +0 -812
  66. package/src/plugin/fleet-target.mjs.bak-archive +0 -812
  67. package/src/plugin/tofuture/report.mjs +0 -44
  68. package/src/plugin/tools.mjs.bak-c7 +0 -7029
  69. package/src/relay/tofuture-whitelist.mjs +0 -46
  70. package/src/util/data-plane.mjs +0 -180
  71. package/src/util/sealed-object.mjs +0 -141
  72. package/src/util/tofuture-hmac.mjs +0 -30
@@ -1,361 +0,0 @@
1
- # 执行授权模型(Execution Authorization Model)设计规格 — v1.4.0-beta
2
-
3
- > 状态:**已实现(v1.3.12-beta.1)**。把 M1(运维通道)与 M2(任务级 grant)归纳成一个可复用的授权模型,并冻结线协议与失败语义。
4
- > 版本:`1.4.0-beta.*`;实现按 M1(1.3.2)/ M2(1.3.3)分步交付,模型一致性验收在 1.4.0-beta 完成。
5
- > 相关:`docs/COLLAB/M1-MAINTENANCE.md`、`docs/COLLAB/M2-TASK-GRANT.md`。
6
-
7
- ---
8
-
9
- ## 1. 问题陈述
10
-
11
- 现状只有一个执行授权来源:**机器本地静态 allow-list**。它带来三个无法回避的后果:
12
-
13
- 1. **远程运维不可能**:升级/重启/排障命令不在清单里,只能 SSH;清单空白的机器(TwinsEarth 实测)连 `node --version` 都拒绝,编队里"在线但不可用"。
14
- 2. **放宽清单是唯一出路,且不安全**:为了升级一次就把安装命令永久写进清单,等于给任何能调度任务的人一个长期入口。
15
- 3. **没有"谁授权了什么"的记录**:allow-list 是配置文件,不是事件;无法回答"这条命令是谁、在何时、为什么被允许"。
16
-
17
- 模型目标:引入**显式的、可审计的、带时限的执行授权**,与 allow-list 并存,且不改变"只执行 argv、不执行 shell 字符串"的红线。
18
-
19
- ---
20
-
21
- ## 2. 模型总览
22
-
23
- ```
24
- ┌────────────── 签发(控制面,操作员私钥)──────────────┐
25
- │ grant / maintenance request(签名能力令牌) │
26
- └──────────────────────────┬──────────────────────────┘
27
- │ relay 传输(不解释内容)
28
- ▼
29
- 目标机 ┌────────────────── 授权判定 authorize(action, context) ──────────────────┐
30
- │ 1. 硬红线检查(永远拒绝) │
31
- │ 2. 能力校验:签名/主体/范围/时限/次数/重放/撤销 │
32
- │ 3. 与 allow-list 组合:显式授权优先;无授权则看清单;都不中则拒绝 │
33
- └──────────────────────────────┬───────────────────────────────────────┘
34
- ▼
35
- 执行面(二选一,权限不同)
36
- A. agent 用户执行(argv)
37
- B. root runner 执行(固定 action:update/restart/diag)
38
- ```
39
-
40
- 核心概念:
41
-
42
- | 概念 | 含义 |
43
- |---|---|
44
- | **Authority** | 操作员密钥(Ed25519),签名的唯一来源 |
45
- | **Capability** | 签名令牌,表达"允许在哪些机器、执行什么、多久、几次" |
46
- | **Subject** | 目标机器(machine_ids)/ 项目(后续 M3 的合并授权) |
47
- | **Scope** | argv_hash + 执行约束(mode/cwd/write/timeout/env 白名单) |
48
- | **Bounds** | TTL、max_uses、task 绑定、nonce、撤销 |
49
- | **Decision** | 允许 / 拒绝(带统一错误码),拒绝默认、无副作用 |
50
- | **Provenance** | 每次允许都记录来源:`allow-list` / `grant` / `maintenance`,进 ledger |
51
-
52
- ---
53
-
54
- ## 3. 能力类型(本版本冻结两种)
55
-
56
- ### 3.1 能力 A:执行授权(grant)——agent 用户权限
57
-
58
- - 主体:`machine_ids[]`
59
- - 范围:`argv_hash`(sha256 of canonical argv)+ 可收紧的 `mode/cwd_rel/write/write_scope/timeout_ms`
60
- - 边界:TTL ≤ 10 分钟、`max_uses`(按机器计)、可绑 `task_id`
61
- - 执行者:agent(w2m 等非特权用户)
62
- - 典型:空 allow-list 机器上执行 `node -e ...`、`npm --version`、诊断脚本
63
-
64
- ### 3.2 能力 B:维护请求(maintenance)——root/SYSTEM 权限
65
-
66
- - 主体:`machine_id`(单机)
67
- - 范围:固定 action 枚举 `update | restart | diag` 及其**白名单参数**(版本、sha256、URL 列表、目标单元)
68
- - 边界:TTL ≤ 10 分钟、单次
69
- - 执行者:root runner(独立单元,固定实现,解释 JSON 但不执行 shell)
70
- - 典型:远程升级插件/relay、重启服务、收集诊断
71
-
72
- > 两种能力共用同一签名载荷/规范化/密钥,但**权限不同、执行者不同、参数白名单不同**。这是模型的核心分离:让"跑一条命令"和"做一次系统维护"不共用危险通道。
73
-
74
- ### 3.3 未来能力(不在本版本)
75
-
76
- - 项目级能力(合并队列的 merge-slot 授权)——由 M3 定义,沿用本模型(主体=project+branch,范围=merge 操作)。
77
- - 角色委托(owner 签发子 key 给 maintainer)——v1.4.x 后续。
78
-
79
- ---
80
-
81
- ## 4. 授权判定函数(冻结语义)
82
-
83
- ```
84
- authorize(input) -> { allow: boolean, provenance, code?, detail? }
85
-
86
- input = {
87
- capability?: SignedCapability, // grant 或 maintenance,可能缺失
88
- action: { kind: "exec"|"maintenance", argv?, constraints?, maintenance? },
89
- context: { machine_id, task_id?, attempt, now, policy: { allow_list, red_lines, limits } },
90
- state: { used_count, nonce_seen, revoked_set, pinned_operator_keys }
91
- }
92
- ```
93
-
94
- 判定顺序(每一步失败都返回对应错误码,不执行、不落副作用):
95
-
96
- ```
97
- 1. 硬红线(任何来源都不能越过)
98
- 1.1 仅 argv 数组;禁止 shell 字符串/命令拼接
99
- 1.2 cwd 必须落在允许根内(项目目录/工作区),无路径穿越
100
- 1.3 禁止提权(agent 通道不能要求 root;maintenance 只走固定 action)
101
- 1.4 写入必须落在 write_scope 内;只读任务不得写
102
- 1.5 输出/超时/CPU/内存上限由机器策略决定,能力只能收紧
103
- → 违规:RED_LINE_VIOLATION
104
-
105
- 2. 有能力(capability 存在)
106
- 2.1 形状/大小/协议版本
107
- 2.2 验签(pinned operator key;未知 key_id → UNKNOWN_AUTHORITY)
108
- 2.3 主体匹配(machine_ids 含本机;maintenance 的 machine_id 必须相等)
109
- 2.4 范围匹配(argv_hash/参数逐项一致;约束不得比机器策略更宽)
110
- 2.5 时限与次数(issued_at ≤ now ≤ expires_at,TTL 上限;used_count < max_uses;task 绑定)
111
- 2.6 重放(nonce + capability_id 本地持久化)
112
- 2.7 撤销(relay 撤销集缓存窗口内命中)
113
- → 通过:allow,provenance = "grant" | "maintenance"
114
-
115
- 3. 无能力
116
- 3.1 allow-list 命中 → allow,provenance = "allow-list"
117
- 3.2 否则 → deny,code = NOT_AUTHORIZED(对 grant 场景为 GRANT_MISSING)
118
-
119
- 4. 记录
120
- 4.1 allow:ledger 记 provenance + capability_id + argv_hash + 结果
121
- 4.2 deny:ledger 记 code + 摘要(不记全量 argv 敏感内容,按红action策略脱敏)
122
- ```
123
-
124
- 组合与优先级:
125
-
126
- - **拒绝优先**:任一步拒绝即拒绝;能力不能覆盖硬红线。
127
- - **能力优先于清单**(用于审计显式性),但结果相同;两者都允许时记录 `grant`(显式授权),并标记 `also_in_allow_list: true`。
128
- - **清单是"常驻授权"**:其 provenance = `local-config`,不在 ledger 伪造操作员签名;模型上把 allow-list 视为"由机器 owner 预签发的长期能力",便于统一解释。
129
-
130
- ---
131
-
132
- ## 5. 信任建立与密钥生命周期
133
-
134
- ### 5.1 操作员密钥
135
-
136
- - 独立 Ed25519,与 operator token(调度凭据)**分离**:token 泄露不等于授权能力泄露,反之亦然。
137
- - 私钥存放在控制端(DSH 配置/OS keychain);不进入 relay、不进入 agent。
138
- - 公钥登记:控制端用 operator token 证明持有权 → relay 记录 `{key_id, public_key, created_at}`(ledger 事件)。
139
-
140
- ### 5.2 目标机信任(agent / root runner)
141
-
142
- ```
143
- 首选(推荐,M1/M2 终态):pairing-time pinning
144
- pairing 响应携带 operator 公钥 → 机器在配对时固定(操作员在场,这是可信锚点)
145
- 轮换:新公钥必须由旧 pinned key 签名(rotation chain),agent 验链后更新
146
- 撤销:操作员登记 revoke;agent 同步后拒绝旧 key
147
-
148
- beta 过渡(1.3.2/1.3.3 实现期):TOFU
149
- 首次验签时向 relay 拉取公钥并固定;文档明确"首次可被攻破的 relay 注入"
150
- 后续版本改为 pairing-time pinning 为默认,TOFU 仅作显式 fallback
151
- ```
152
-
153
- **已知限制(必须写进发布说明)**:TOFU 窗口内,被攻破的 relay 可注入伪造公钥。缓解:短 TTL、审计告警、尽快升级到 pinned 模式。
154
-
155
- ### 5.3 撤销
156
-
157
- - relay 维护撤销集(capability_id、key_id);ledger 记录 `capability.revoked`。
158
- - agent 缓存 30s;在 TTL 内撤销也生效(最多 30s 延迟)。
159
- - 语义声明:撤销**不会**中断正在执行的动作,只阻止新的执行。
160
-
161
- ---
162
-
163
- ## 6. 威胁模型(模型级)
164
-
165
- | 威胁 | 能否得逞 | 依据/处置 |
166
- |---|---|---|
167
- | 恶意编队成员伪造指令 | ❌ | 无操作员私钥,签名不可伪造 |
168
- | 被攻破的 relay 伪造授权 | ⚠️ TOFU 窗口内可注入公钥(文档声明);pinned 后不可 | 尽快 pinning;短 TTL;审计 |
169
- | 被攻破的 relay 扣留/延迟指令 | ✅(可用性) | 授权不影响正确性;重试/审计可发现 |
170
- | 被攻破的 agent | ❌ 提权 | agent 只能以自身用户执行;root 动作只走固定 maintenance action |
171
- | 重放旧授权 | ❌ | nonce + capability_id 持久化 + task 绑定 + TTL |
172
- | 改 argv 绕范围 | ❌ | argv_hash 校验 |
173
- | 越界写文件 | ❌ | 硬红线 cwd/write_scope |
174
- | 时钟回拨延长 TTL | ⚠️ | 服务端时间权威(relay);本地只做窗口判断;偏差容忍 ±60s |
175
- | 操作员误授权 | ⚠️ | 展示完整 argv、TTL ≤10min、max_uses、撤销;高风险动作需二次确认 |
176
-
177
- ---
178
-
179
- ## 7. 兼容与迁移
180
-
181
- - **旧 relay**:忽略 grant/maintenance 字段 → 行为退回 allow-list;控制端检测后返回 `UNSUPPORTED_RELAY`。
182
- - **旧 agent**:不认识字段 → allow-list 判定(维持 v1.3.1 行为),relay 可探测版本并提示 `UNSUPPORTED_AGENT`。
183
- - **旧 allow-list**:模型上保留为常驻授权,行为不变;不需要迁移配置。
184
- - **新安装**:默认仍是"空 allow-list + 无 pinned key",但配对时可选择 pin 操作员公钥(推荐);空清单机器不再"不可用"——只要操作员签一次能力即可。
185
-
186
- ---
187
-
188
- ## 8. 验收(模型一致性,横跨 M1/M2)
189
-
190
- ### 正向(真实任务证据)
191
-
192
- 1. **空 allow-list + grant**:TwinsEarth 执行 `node -e "console.log(...)"`;ledger provenance=`grant`,记录 capability_id/argv_hash。
193
- 2. **空 allow-list + maintenance**:同机完成 `update`/`restart`/`diag`;provenance=`maintenance`,root runner 日志与 ledger 一致。
194
- 3. **普通 allow-list 命令**:provenance=`allow-list`,行为与 1.3.1 identical(对账对照)。
195
- 4. **撤销窗口**:签发→撤销→派发(≤30s 内)被拒;超过缓存窗口同样被拒。
196
- 5. **轮换链**:旧 key 签新 key → agent 更新 pinned;未签的换 key 被拒。
197
-
198
- ### 负向(模型矩阵,自动化)
199
-
200
- ```
201
- 硬红线:shell 字符串/路径穿越/提权/越界写 → RED_LINE_VIOLATION
202
- 能力:缺签/错签/未知 key/过期/未来/换机/换 argv/重放/超次数/撤销/任务绑定不符 →
203
- NOT_AUTHORIZED / UNKNOWN_AUTHORITY / GRANT_* / MAINTENANCE_*
204
- 审计:拒绝不产生执行副作用;ledger 有 deny 记录
205
- 组合:能力拒绝时不回落 allow-list(防歧义);allow-list 命中但能力被篡改 → 拒绝(显式意图无效)
206
- 时钟:±61s 边界、issued_at 在未来、expires_at 恰好过期
207
- ```
208
-
209
- ### 审计查询
210
-
211
- - 给定 task_id → 能力来源、key_id、argv_hash、结果。
212
- - 给定机器 → 当前 pinned key、最近能力使用、拒绝计数。
213
- - 给定时间窗 → 所有 maintenance 动作与 grant 使用(导出 JSON)。
214
-
215
- ---
216
-
217
- ## 9. 与三个模型的关系
218
-
219
- | 模型 | 版本 | 依赖 | 说明 |
220
- |---|---|---|---|
221
- | 执行授权模型(本文) | v1.4.0-beta | 独立 | 定义"能否执行";M1/M2 是其两种实现 |
222
- | 任务模型 | v1.4.1-beta | 本模型(合并/审查任务需授权) | 定义"执行什么、产出什么(worktree/patch/merge)" |
223
- | 协作模型 | v1.4.2-beta | 本模型 + 任务模型 | 定义"谁和谁、在哪个项目/房间协作" |
224
-
225
- ---
226
-
227
- ## 10. 交付物与版本映射
228
-
229
- ```
230
- 模型文档:docs/COLLAB/EXECUTION-AUTH-MODEL.md(本文)
231
- 实现拆分:
232
- 1.3.2-beta M1 运维通道(能力 B:固定 action + root runner)
233
- 1.3.3-beta M2 任务级 grant(能力 A:argv 授权 + agent 验签)
234
- 1.4.0-beta 模型一致性验收:统一错误码/审计查询/轮换链/撤销窗口/真实任务证据
235
- (不新增功能;把 M1/M2 的行为对齐到本模型)
236
- 共享模块:
237
- src/auth/operator-key.mjs 密钥生成/登记/轮换链/pin/撤销
238
- src/auth/capability.mjs 两种能力的统一载荷、规范化、签发、验证
239
- src/auth/authorize.mjs 判定函数(纯函数,输入输出可单测)
240
- src/auth/audit.mjs 审计查询(ledger 上的投影)
241
- ```
242
-
243
- ## 11. 开放问题(实现前确认)
244
-
245
- 1. **pinned vs TOFU 的默认**:1.3.2/1.3.3 实现期是否直接做 pairing-time pinning(更安全,改动稍大)?
246
- 2. **操作员密钥创建 UX**:`dsh` 命令生成并写入 OS keychain,还是插件面板生成?
247
- 3. **能力签发 UX**:一次一条命令(简单)还是"策略模板"(如"升级到某版本"一键签一组)?
248
- 4. **谁可以签**:仅 owner 操作员,还是允许 maintainer 使用预设策略?
249
- 5. **max TTL**:10 分钟是否合适(运维场景可能更长)?
250
- 6. **审计可见性**:ledger 里的 argv 是否永久保存明文(敏感参数)还是只存 hash + 前缀?
251
-
252
- ---
253
-
254
- ## 签发(v1.3.12-beta.1)
255
-
256
- 执行授权模型之前只有静态 allow-list:机器所有者写下一个长期决定,任何不在表里的命令都没有"就这一次"的途径。现在补上
257
- 了操作员显式授权某一次执行的完整路径。
258
-
259
- ### 1. 生成操作员密钥
260
-
261
- 操作员持有 Ed25519 私钥;机器的 allow-list 保持不动(默认拒绝)。
262
-
263
- ### 2. 先取 task_id,再签发 grant
264
-
265
- grant 绑定 **一个 task、一个 argv、若干 machine_id、一个短 TTL(≤10 分钟)和 max_uses(默认 1)**。所以签发之前 task_id
266
- 必须已经确定(调度方生成后传给 `POST /v1/task` 的 `task_id` 字段)。
267
-
268
- ```bash
269
- node bin/w2m-grant.mjs \
270
- --key ~/.w2m/operator.pem \
271
- --task-id 01M5GRANTCLI0000000000000001 \
272
- --machines 6c92b251-3728-48bc-82a9-56cc40df48f2 \
273
- --argv '["node","--version"]' \
274
- --ttl-ms 120000 > /tmp/grant.json
275
- ```
276
-
277
- stdout 是 grant JSON;stderr 打印 `task_id / argv_hash / machines / expires_at` 供核对,便于管道使用。
278
-
279
- ### 3. 派发时携带 grant
280
-
281
- ```json
282
- POST /v1/task
283
- {
284
- "task_id": "01M5GRANTCLI0000000000000001",
285
- "mode": "replicate",
286
- "command_argv": ["node", "--version"],
287
- "cwd_rel": ".",
288
- "index_total": 1,
289
- "timeout_ms": 60000,
290
- "write": false,
291
- "grant": { ... /tmp/grant.json 内容 ... }
292
- }
293
- ```
294
-
295
- ### 4. 两处检查,各管一件事
296
-
297
- - **relay(派发预检)**:grant 的 `task_id` 必须与本次派发的 task 一致(`GRANT_TASK_MISMATCH`),`argv_hash` 必须与
298
- `command_argv` 一致(`GRANT_ARGV_MISMATCH`),`expires_at` 未过期(`GRANT_EXPIRED`)。这些是调用方错误(HTTP 400),
299
- 不是授权裁决。
300
- - **agent(真正的验证者)**:用机器上 pin 的操作员公钥验证签名、检查任务绑定/机器绑定/argv/有效期/max_uses,并把用过的
301
- grant 记入本地 ledger(重放拒绝为 `GRANT_REPLAY`)。空 allow-list + 合法 grant 会执行;同 argv 无 grant 仍
302
- `COMMAND_NOT_ALLOWED`。
303
-
304
- ### 5. 审计字段
305
-
306
- task summary 与 ledger 现在携带 `grant_id`、`argv_hash`、`grant_key_id`;项目事件里投影 `task.linked`,回答"这条命令是谁
307
- 授权、什么时候、在哪些机器上"。
308
-
309
- ### 6. 边界(仍不做的)
310
-
311
- - 不做长期委托/子授权:一次 grant 只覆盖一次执行。
312
- - 不做自动签发:操作员必须显式运行签发命令或调用等价 API。
313
- - 不做跨项目授权:grant 绑定机器与 argv,与项目无关。
314
-
315
- ## 撤销与审计(v1.3.12-beta.2)
316
-
317
- 撤销解决"签发之后才发现不该授权"的问题,不需要动已经发出的 grant 内容(它可能已在操作员本机或任务体里)。
318
-
319
- - **撤销一条**:`POST /v1/grants/revoke`,operator token,body `{grant_id, reason?}`。幂等:第二次撤销返回
320
- `already_revoked: true`,不报错。撤销写入 relay ledger(`grant.revoked`,含 reason 与操作者)。
321
- - **派发预检**:携带已撤销 grant 的任务在 `POST /v1/task` 阶段被拒:`403 GRANT_REVOKED`。机器不会收到 offer。
322
- - **审计视图**:`GET /v1/grants`,operator token。返回 `tasks`(所有携带过 grant 的任务:task_id / grant_id / argv_hash /
323
- key_id / mode / created_at)与 `revoked`(撤销记录)。只有 operator token 能读;device token 得到 401,因为这是授权台账,
324
- 不是机器的工作面。
325
- - **agent 侧边界**:agent 无法联系 relay 时仍按本地 ledger 执行已到手的 grant(正确性依赖 relay 派发,见 §4);撤销的作用
326
- 是阻止**新派发**,与"停止签发"同义。
327
-
328
- 撤销之后的新授权需要重新签发(新的 grant_id,绑定同一 task 或新 task)。
329
-
330
- ## 授权来源(v1.3.12-beta.3)
331
-
332
- 前两版解决了"能不能授权"和"能不能收回";这一版让结果自己回答"是谁授权的"。每个任务结果信封多一个字段:
333
-
334
- ```json
335
- {
336
- "authorization": {
337
- "provenance": "grant | allow-list | maintenance",
338
- "capability_id": "gr-…(grant 时)| maintenance request_id(maintenance 时)",
339
- "grant_id": "gr-…(grant 时)"
340
- }
341
- }
342
- ```
343
-
344
- - **allow-list**:机器所有者的长期决定,机器执行时命中静态表。
345
- - **grant**:操作员针对这一次执行的显式授权;附带 grant_id,可与 relay ledger 的 `grant.revoked` / 任务记录对账。
346
- - **maintenance**:签名维护请求;capability_id 是 request_id,对应 M1 的请求台账。
347
-
348
- 边界要写清楚:**provenance 是执行机器对自身判定的事实陈述,relay 只存储不验证**(signature 由 agent 验、派发由 relay 预检、撤销由
349
- relay 记账)。回答"这到底是谁允许的"仍以 relay ledger 为准;provenance 让每个结果可被机器与人快速定位到那条授权,而不是替代台账。
350
-
351
- ## 使用台账不可写(v1.3.12-beta.4)
352
-
353
- 真机发现:维护状态目录 `/var/lib/w2m/maintenance` 由 root 拥有且 0755,agent 以非特权用户运行;grant 第一次通过后写使用台账
354
- 失败(EACCES),异常冒泡成任务 crashed —— 表现是"授权了但一执行就崩"。这不是授权判定错误,是记录失败。
355
-
356
- 处理(fail closed):
357
-
358
- - `grant-gate` 把台账写入包在 try/catch 里,失败返回 `GRANT_LEDGER_UNAVAILABLE`,不执行未记录的一次授权(否则单次授权会
359
- 变成可重放的漏洞),也不再把整个 handler 打崩。
360
- - `bootstrap.sh` 把 `$BASE` 设为 `root:w2m 0775`(agent 需要在这里写 grant-uses.json);queue 仍为 0770、done/claimed 0750。
361
- - 回归用例:把 usedFile 指到不可写路径,断言返回该 code 而非抛异常。
@@ -1,70 +0,0 @@
1
- # M1 real-machine acceptance runbook (v1.3.10-beta.2+)
2
-
3
- > Goal: prove, on real machines, that a signed maintenance request updates/restarts/diagnoses a machine **without SSH and
4
- > without widening the allow-list**. This needs one privileged bootstrap per machine; after that everything is remote.
5
-
6
- ## 0. Prerequisites
7
-
8
- 1. The machine already runs the plugin (any version >= 1.3.10-beta.2) and the agent is paired.
9
- 2. The relay runs a version that carries the maintenance fields (**v1.3.10-beta.2 or later**). A 1.3.1 relay silently drops them.
10
- 3. You have one-time root (Linux) / Administrator (Windows) access to run the bootstrap script.
11
-
12
- ## 1. Bootstrap (once per machine)
13
-
14
- Linux:
15
-
16
- ~~~bash
17
- # copy the package to /opt/w2m/app first (deploy-w2m.sh), then:
18
- sudo bash /opt/w2m/app/deploy/maintenance/bootstrap.sh \
19
- --app-dir /opt/w2m/app \
20
- --machine-id <machine_id> \
21
- --operator-key /tmp/operator.pub.pem
22
- ~~~
23
-
24
- Windows (Administrator PowerShell):
25
-
26
- ~~~powershell
27
- powershell -ExecutionPolicy Bypass -File C:\w2m\app\deploy\maintenance\bootstrap.ps1 \
28
- -AppDir C:\w2m\app -MachineId <machine_id> -OperatorKey C:\w2m\operator.pub.pem
29
- ~~~
30
-
31
- The bootstrap installs a SYSTEM path/task that executes only update/restart/diag, creates the queue directories the agent
32
- writes to, and pins the operator public key. It never installs a shell.
33
-
34
- ## 2. Dispatch a signed request
35
-
36
- Use scripts/m1-acceptance.mjs on the control side:
37
-
38
- ~~~powershell
39
- $env:W2M_OPERATOR_TOKEN = '<operator token>'
40
- $env:W2M_DEVICE_TOKEN = '<this control machine device token>'
41
- node scripts/m1-acceptance.mjs --machine-id <target machine_id> --action diag
42
- ~~~
43
-
44
- Expected: task ends done, evidence file written under docs/acceptance/<version>/, and the target machine's
45
- /var/lib/w2m/maintenance/done/<request_id>.json holds the runner result.
46
-
47
- ## 3. Acceptance cases (record evidence for each)
48
-
49
- | Case | Request | Expected |
50
- |---|---|---|
51
- | M1-DIAG | action=diag | done; result sections include version/services/journal/disk |
52
- | M1-RESTART | action=restart targets=agent | done; machine heartbeats again |
53
- | M1-UPDATE | action=update version=<next> sha=<asset sha> | done; package.json version changes; app.old kept |
54
- | M1-UPDATE-ROLLBACK | same but broken sha/artifact | failed; previous tree still served (rollback) |
55
- | N1-EXPIRED | request with expires_at in the past | refused MAINTENANCE_EXPIRED, nothing executed |
56
- | N2-REPLAY | same request_id twice | second refused MAINTENANCE_REPLAY |
57
- | N3-TAMPER | change a param after signing | refused MAINTENANCE_BAD_SIGNATURE |
58
- | N4-WRONG-MACHINE | machine_id of another host | refused MAINTENANCE_WRONG_MACHINE |
59
-
60
- ## 4. What "green" means
61
-
62
- - Every positive case has a result file whose status is ok and whose assertions match the table.
63
- - Every negative case has a named refusal and **left the machine unchanged** (version, service state).
64
- - The ledger contains maintenance_summary for each task and the evidence JSON records task_id + assertions.
65
-
66
- ## 5. Known limitations to state in the release note
67
-
68
- - The bootstrap itself needs privilege; the channel removes SSH **after** it.
69
- - TOFU key pinning: first key seen wins until rotated by a signed rotation. Pairing-time pinning is the planned hardening.
70
- - No automatic rollback if the machine cannot be reached after restart; the operator re-dispatches restart (idempotent).