@twinsearth/dsh-master 3.3.1 → 3.3.3

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.
package/README.md CHANGED
@@ -21,8 +21,8 @@ DSH Host
21
21
  │ mirror / essence / derived core(主 Agent 控制面)
22
22
  │ fangForker / fangDecisionCenter ◄────── ├── superagent-router (essence/mother)
23
23
  │ fangSessionCatalog ├── superagent-worker (fork/essence)
24
- │ ├── cost-optimizer (fork)
25
- │ └── dual-manager (mother/essence)
24
+ │ ├── cost-optimizer (fork/mother)
25
+ │ └── dual-manager (mother/essence/fork)
26
26
  └── 协作只走服务键;任一方向失败一律 fail-closed,不本地降级执行
27
27
  ```
28
28
 
@@ -31,10 +31,14 @@ DSH Host
31
31
  | 子插件 | 行 id | 允许角色 | 路线图 | 已交付 |
32
32
  |---|---|---|---|---|
33
33
  | SuperAgent Router | `master-superagent-router` | **essence / mother** | 3.2.1/3.2.3 | Webhook/Ticket/Mandate、渐进信任、本地或母体桥派发、Worker 实时事件、回写 |
34
- | SuperAgent Worker | `master-superagent-worker` | **fork / essence** | 3.2.1 | Ticket 上下文、Mandate 守卫 |
35
- | Worker Agent Loop | `master-superagent-loop` | **fork / essence** | 3.2.3 | tsc 编译 preset;有 Ticket 自动跑多轮工具循环并流式回传 |
36
- | 成本优化 HarnessMM | `master-cost-optimizer` | **fork only** | 3.3.0 | observe 层:token 估算、实测记录 |
37
- | 双主管 PM + PJM | `master-dual-manager` | **mother / essence** | 3.4.0 | PRD/计划/调度/决策中心澄清 |
34
+ | SuperAgent Worker | `master-superagent-worker` | **fork / essence / mother** | 3.2.1 | Ticket 上下文、Mandate 守卫 |
35
+ | Worker Agent Loop | `master-superagent-loop` | **fork / essence / mother** | 3.2.3 | tsc 编译 preset;有 Ticket 自动跑多轮工具循环并流式回传 |
36
+ | 成本优化 HarnessMM | `master-cost-optimizer` | **fork / mother** | 3.3.0 | observe 层:token 估算、实测记录、瓮城回流 |
37
+ | 双主管 PM + PJM | `master-dual-manager` | **mother / essence / fork** | 3.4.0 | PRD/计划/调度/决策中心澄清 |
38
+
39
+ > 角色矩阵自 **3.3.3** 起:**母体全开**(含 Worker/Loop/成本),**分身也挂双主管**;
40
+ > 唯一保留的限制是 **Router 不进 fork**(分身只执行,不注册控制面)。
41
+ > 旧的「母体不接纳执行体」约束已按用户明确要求废止。
38
42
 
39
43
  ## 本地开发
40
44
 
@@ -92,9 +96,11 @@ dsh plugin add <package> # 加载 dist-host/dist-worker
92
96
 
93
97
  ## 状态
94
98
 
99
+ - **3.3.2 发布线**:与真实瓮城(dsh-Fang 3.1.35)完成回流实测——探针 `--peer fang` 用瓮城发布物当对端(取其 `VENDOR.json` 校验、`peerLabel` 取其 orchestrator),四项 M5 预期全部成立;同批修掉 contract 1.1.0 的 `rejected` 负数计数并同步 1.1.1。详见 [M5 回流实测](docs/EXPERIMENT-M5-fang-relay.md)。
95
100
  - **3.3.1 发布线**:M5 收口 —— 瓮城回流接口升级到 `@twinsearth/dsh-fang-contract` **1.1.0**(`fang.relay.publish` / `fang.relay.status`),房主侧 `src/cost/fang-relay.ts` 做真实探测与投递(只有对端回执才算送达,不可用给具名原因);里程碑 **M1–M5 全部 done**。详见 [M5 回流实测](docs/EXPERIMENT-M5-fang-relay.md)。
96
101
  - **3.3.0 发布线**:成本优化系统层(ledger / budget / policy / audit)+ Worker 四个成本切点 + A1 紧凑化(默认 shadow)+ A2–A3 / B1–B4;真实 A/B 实测 tokens/task 6263 → 3906.75(−37.6%),成功率 0pp 差,详见 [3.3.0 架构设计](docs/DESIGN-v3.3.0-cost-optimization.md)。
97
102
  - 3.2.10 发布线:角色矩阵放宽——Router 进入母体(essence/mother),Worker 与 Worker Agent Loop 进入原身(fork/essence);母体仍不接纳执行体,fork 仍不注册 Router。
103
+ - **3.3.3 发布线(角色矩阵调整,用户明确要求)**:母体全开(Worker / Worker Agent Loop / 成本优化进 mother),分身挂双主管(dual 进 fork);唯一保留的限制是 Router 不进 fork。
98
104
  - 3.2.9 发布线:房主面板改注册到 `shell.overlay` 常驻浮层——点击面板之外不收起、侧栏「房主」按钮二次点击收起、正文独立滚动;新增无浏览器 hook 运行时行为测试(开关、无 outside-click 监听、滚动容器、SSE 失败不卸载面板)。
99
105
  - 3.2.8 发布线:与瓮城通过 fang-master contract 1.0 互联(local/http/file/legacy 四级保底);GitHub Release + NPM public。
100
106
  - 当前工作树完成新一轮审计修复:工具名/死循环阈值、Router 鉴权与生命周期、真实路由分流、契约幂等与超时、PR 护栏与交付、飞书加密、长 Shell 后台作业、成本实测接线、PJM 派生契约。
package/client.js CHANGED
@@ -275,7 +275,7 @@ window.__ModuleLoader__.load({
275
275
  return h('div', { style: S.card, 'data-dsh-master': 'cost-block' },
276
276
  h('div', { style: S.row },
277
277
  h('span', { style: S.name }, '成本(只读)'),
278
- tag(block ? '3.3.1' : '未启用', block ? undefined : '#B45309')),
278
+ tag(block ? '3.3.3' : '未启用', block ? undefined : '#B45309')),
279
279
  failed ? h('div', { style: S.error }, '成本接口不可达') : null,
280
280
  !block && !failed ? h('div', { style: S.desc }, '成本系统未装配(cost-optimizer 未启用或角色不匹配)') : null,
281
281
  columns ? h('div', { style: S.desc },
@@ -9,7 +9,7 @@
9
9
  * * 删除/改义必须升 MAJOR,并保留至少一个 MINOR 周期的双写(method@1 与 method@2)。
10
10
  */
11
11
  export const CONTRACT_PACKAGE = '@twinsearth/dsh-fang-contract';
12
- export const CONTRACT_VERSION = '1.1.0';
12
+ export const CONTRACT_VERSION = '1.1.1';
13
13
  export const PROTOCOL = 'fang-master';
14
14
  export const PROTOCOL_VERSION = 1;
15
15
  export const MIN_COMPATIBLE_MAJOR = 1;
@@ -135,10 +135,12 @@ export function createRelayStore(options = {}) {
135
135
  }
136
136
  counters.batches += 1;
137
137
  const acceptedIds = [];
138
- let duplicates = 0;
138
+ let alreadySeen = 0;
139
+ let inBatchDuplicates = 0;
140
+ let rejectedCount = 0;
139
141
  for (const event of validated.accepted) {
140
142
  if (seen.has(event.id)) {
141
- duplicates += 1;
143
+ alreadySeen += 1;
142
144
  continue;
143
145
  }
144
146
  seen.set(event.id, { at: nowMs(now), cursor: cursor + 1 });
@@ -148,18 +150,21 @@ export function createRelayStore(options = {}) {
148
150
  remember({ id: event.id, type: event.type, at: event.at, cursor });
149
151
  lastAt = nowMs(now);
150
152
  }
151
- counters.duplicates += duplicates;
152
153
  for (const item of validated.rejected) {
153
154
  // 批内重复计入 duplicates;其余非法条目计入 rejected 并留原因(有界)。
154
155
  if (item.reason === 'duplicate-in-batch') {
155
- duplicates += 1;
156
- counters.duplicates += 1;
156
+ inBatchDuplicates += 1;
157
157
  continue;
158
158
  }
159
- counters.rejected += 1;
159
+ rejectedCount += 1;
160
160
  if (lastErrors.length < 50)
161
161
  lastErrors.push({ id: item.id, reason: item.reason });
162
162
  }
163
+ // 两个计数分开累计:duplicates = 已存在 + 批内重复;rejected 只数真正非法的条目。
164
+ // (1.1.0 曾用 `validated.rejected.length - duplicates` 计算 rejected,重投时会得到负数。)
165
+ const duplicates = alreadySeen + inBatchDuplicates;
166
+ counters.duplicates += duplicates;
167
+ counters.rejected += rejectedCount;
163
168
  const batchId = String(params?.batchId ?? `batch-${counters.batches}`);
164
169
  const receipt = Object.freeze({
165
170
  ok: true,
@@ -167,7 +172,7 @@ export function createRelayStore(options = {}) {
167
172
  taskId: params?.taskId === undefined ? null : String(params.taskId),
168
173
  accepted: acceptedIds.length,
169
174
  duplicates,
170
- rejected: validated.rejected.length - duplicates,
175
+ rejected: rejectedCount,
171
176
  cursor,
172
177
  acceptedIds,
173
178
  peer: peerLabel,
@@ -7,7 +7,7 @@
7
7
  * 与瓮城 dsh-Fang 的协作只走 DSH 服务键,不 import fang 代码。
8
8
  */
9
9
  import crypto from 'node:crypto';
10
- export const MASTER_VERSION = '3.3.1';
10
+ export const MASTER_VERSION = '3.3.3';
11
11
  export const FANG_CONTRACT_MIN = '3.1.14';
12
12
  export const SERVICE = Object.freeze({
13
13
  master: 'dshMaster',
@@ -60,18 +60,20 @@ export const ROW = Object.freeze({
60
60
  export const ROLE = Object.freeze({ mother: 'mother', essence: 'essence', fork: 'fork' });
61
61
  /**
62
62
  * 角色矩阵:
63
- * * Router 在 essence 与 mother 激活:只评估/建票/派发,不执行代码;fork 不注册。
64
- * * Worker / Worker Agent Loop 在 fork 与 essence 激活:原身可直接执行工单,
65
- * 分身仍是默认执行体;mother 不执行。
66
- * * 成本优化仍只 fork;双主管 mother/essence。
63
+ * * Router 在 essence 与 mother 激活:只评估/建票/派发,不执行代码改动;fork 不注册。
64
+ * * Worker / Worker Agent Loop 在 fork、essence、**mother** 激活:母体默认只做编排,
65
+ * 但 3.3.3 起允许母体直接执行工单(用户明确要求「母体全部激活」;此前"母体不接纳
66
+ * 执行体"的旧约束已废止,见 README 角色矩阵与 CHANGELOG)。
67
+ * * 成本优化在 fork 与 mother 激活(母体也需要看到成本账与回流);essence 不挂(元宝只编排)。
68
+ * * 双主管 mother / essence / fork 全开:管理能力不再按角色排除,是否派发由权限与预算闸决定。
67
69
  */
68
70
  export const ROLE_MATRIX = Object.freeze({
69
71
  'master-core': Object.freeze(Object.values(ROLE)),
70
72
  'master-superagent-router': Object.freeze([ROLE.essence, ROLE.mother]),
71
- 'master-superagent-worker': Object.freeze([ROLE.fork, ROLE.essence]),
72
- 'master-superagent-loop': Object.freeze([ROLE.fork, ROLE.essence]),
73
- 'master-cost-optimizer': Object.freeze([ROLE.fork]),
74
- 'master-dual-manager': Object.freeze([ROLE.mother, ROLE.essence]),
73
+ 'master-superagent-worker': Object.freeze([ROLE.fork, ROLE.essence, ROLE.mother]),
74
+ 'master-superagent-loop': Object.freeze([ROLE.fork, ROLE.essence, ROLE.mother]),
75
+ 'master-cost-optimizer': Object.freeze([ROLE.fork, ROLE.mother]),
76
+ 'master-dual-manager': Object.freeze([ROLE.mother, ROLE.essence, ROLE.fork]),
75
77
  });
76
78
  export const SUBPLUGIN_CATALOG = Object.freeze([
77
79
  Object.freeze({
@@ -108,7 +108,7 @@ export function apply(ctx, config = {}) {
108
108
  };
109
109
  const service = {
110
110
  id: SERVICE.cost,
111
- version: '3.3.1',
111
+ version: '3.3.3',
112
112
  role,
113
113
  get mode() {
114
114
  return system.policy.describe().mode;
@@ -354,7 +354,7 @@ export function apply(ctx, config = {}) {
354
354
  return {
355
355
  ok: true,
356
356
  id: 'cost-optimizer',
357
- version: '3.3.1',
357
+ version: '3.3.3',
358
358
  role,
359
359
  mode: current.mode,
360
360
  requestedMode: current.requestedMode,
@@ -396,7 +396,7 @@ export function apply(ctx, config = {}) {
396
396
  doubleCountGuard: report.doubleCountGuard,
397
397
  };
398
398
  })(),
399
- note: '3.3.1:系统层记账/预算/灰度/审计 + A1 工具输出紧凑化(默认 shadow)+ 瓮城回流出口(contract 1.1 真实投递);工具面 A2–A3 与决策复用 B1–B4 已接入(shadow 默认)。',
399
+ note: '3.3.3:系统层记账/预算/灰度/审计 + A1 工具输出紧凑化(默认 shadow)+ 瓮城回流出口(contract 1.1.1 真实投递,回执计数修复);工具面 A2–A3 与决策复用 B1–B4 已接入(shadow 默认);3.3.3 起角色矩阵母体全开、分身挂双主管。',
400
400
  };
401
401
  },
402
402
  };
@@ -35,8 +35,8 @@
35
35
  └──────────────────────────────┘
36
36
 
37
37
  旁路(后续版本):
38
- cost-optimizer → fork:HarnessMM observe 层(3.3.0)
39
- dual-manager → mother/essence:PM + PJM(3.4.0)
38
+ cost-optimizer → fork/mother:HarnessMM observe 层(3.3.0;3.3.3 起母体也开放)
39
+ dual-manager → mother/essence/fork:PM + PJM(3.4.0;3.3.3 起分身也开放)
40
40
  ```
41
41
 
42
42
  ### 角色矩阵(代码强制,见 `src/kernel.mjs ROLE_MATRIX`)
@@ -116,7 +116,7 @@
116
116
  ## 7. 3.3.0 / 3.3.1 成本优化(已实施并发布)
117
117
 
118
118
  ```text
119
- master-cost-optimizer(行,仅 fork)= 编排器 + 服务 dshMasterCost
119
+ master-cost-optimizer(行,fork/mother)= 编排器 + 服务 dshMasterCost
120
120
  │
121
121
  系统层(常驻):ledger 账本 │ budget 预算闸 │ policy 灰度 │ attribution 归因 │ audit 审计链 │ bus 事件总线
122
122
  │ hooks │ reports(→ 瓮城回流出口:fang-relay 真实投递)
@@ -132,7 +132,7 @@ master-cost-optimizer(行,仅 fork)= 编排器 + 服务 dshMasterCost
132
132
  Agent Loop **不认识具体模块**;模块异常 fail-open 并留痕,因此新增模块无需改 `agent-loop.ts`。
133
133
  - 四切点:`worker/cost-hooks.ts`,事件经 `sink.cost` 到宿主(HTTP sink 的 `cost` 事件 / Cordis `superagent:cost`)。
134
134
  - A1 默认 **shadow**:计算「本可节省」但返回原文;只有 `enforce` 且紧凑结果更短才替换,且归因单独入账(不重复计消耗)。
135
- - 瓮城回流(3.3.1):`src/cost/fang-relay.ts` 走 `@twinsearth/dsh-fang-contract` 1.1 的
135
+ - 瓮城回流(3.3.1 起):`src/cost/fang-relay.ts` 走 `@twinsearth/dsh-fang-contract` 1.1.x 的
136
136
  `fang.relay.publish` / `fang.relay.status`;只有对端回执才算送达,不可用给具名原因。
137
137
  - 详见 `docs/DESIGN-v3.3.0-cost-optimization.md`(§0 总体架构、§17 实施进度)、
138
138
  `docs/EXPERIMENT-M5-real-ab.md`(真实 A/B)、`docs/EXPERIMENT-M5-fang-relay.md`(回流实测)。
package/docs/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ ## 3.3.3(2026-11-13 已发布)
2
+
3
+ > 三方发布:annotated tag `v3.3.3` + GitHub Release + npm `@twinsearth/dsh-master@3.3.3`。
4
+ > 接口包无变化(仍 `@twinsearth/dsh-fang-contract@1.1.1`)。
5
+
6
+ **主题:角色矩阵按用户要求放开(母体全开 + 分身挂双主管)。**
7
+
8
+ - `ROLE_MATRIX` 调整:`superagent-worker` / `superagent-loop` 增加 **mother**;`cost-optimizer` 增加 **mother**(变 fork/mother);`dual-manager` 增加 **fork**(变 mother/essence/fork)。
9
+ - **保留的唯一限制**:`superagent-router` 仍为 essence/mother —— 分身不注册控制面(只执行)。
10
+ - 废止旧约束「母体不接纳执行体」(README 3.2.10 行曾记录)。同步更新 7 条测试断言(矩阵精确值、母体/分身角色门、成本与双主管准入、package-contract 安全断言 + 新增「Router 永不进 fork」不变量),README 角色表与架构说明。
11
+ - 影响面:母体现在可派发并运行 Worker/Loop,母体也能挂成本优化(成本账 + 瓮城回流);分身可挂双主管。安全边界不变:预算闸、Mandate 守卫、角色检测仍按原样生效,只是准入名单放宽。
12
+ - 测试:全量 **449 条**(`test:clean` 449/449)。
13
+
14
+ ## 3.3.2(2026-11-13 已发布)
15
+
16
+ > 三方发布:annotated tag `v3.3.2` + GitHub Release + npm `@twinsearth/dsh-master@3.3.2`。
17
+ > 同批发布接口包:`@twinsearth/dsh-fang-contract@1.1.1`(修回执计数缺陷)。
18
+
19
+ **主题:与真实瓮城(dsh-Fang 3.1.35)完成回流实测,并修掉回执计数缺陷。**
20
+
21
+ - **瓮城侧状态(只读核对)**:dsh-Fang **3.1.35** 已内嵌 contract **1.1.0**,`src/contract/` 8 个文件与发布包逐字节相同,并附 `VENDOR.json`(`version` / `fileCount` / 逐文件 sha256);`src/plugins/orchestrator.mjs` 声明 `peerLabel: 'dsh-fang-bundle'` 且注册 `...relayHandlers(relayStore)`。
22
+ - **探针新增 `--peer fang` 模式**:把**瓮城自己的发布物**当对端 —— 用它 vendored 的契约服务端,`peerLabel` 从它自己的 orchestrator 源码读取(不硬编码),并用它的 `VENDOR.json` 逐文件校验 sha256(8/8 通过)。实测:`peer=dsh-fang-bundle`、`events=6`、`sameProcess=false`、`failed=0`、`transport=http`、`contractVersion=1.1.0`;幂等重投 `accepted:0 / duplicates:3 / cursor` 不变。四项 M5 预期全部成立。
23
+ - **实测发现的缺陷(contract 1.1.0)**:relay 回执把 `rejected` 算成 `validated.rejected.length - duplicates`,重投时会得到**负数**(实测 `-3`)。影响仅限回执字段(事件去重、`accepted`、`cursor` 一直正确)。contract **1.1.1** 已修正为「`duplicates`(已存在 + 批内)与 `rejected`(仅非法条目)分开累计」,并补回归测试(契约侧 16/16)。
24
+ - **房主内嵌副本同步 1.1.1**:`src/contract/` 与已发布包逐字节相同(conformance 测试守卫);新增「重投回执非负且 cursor 不倒退」回归测试。
25
+ - **里程碑**:M5 证据由 `contract-server` 升级为 `dsh-fang-bundle · 6 条`,结果行 `notes` 里的残余缺口(“待 bundle 采用 1.1”)自动清空;**M1–M5 全部 done**。
26
+ - **仍未做**:宿主级 co-load(守则 §二 禁止把瓮城装进任何 profile);瓮城 UI/信息栏是否消费回流事件属瓮城产品面。
27
+ - 测试:全量 **449 条**(`npm test` 447 通过 + 2 条 Windows 并行跳过;`npm run test:clean` 449/449)。
28
+
1
29
  ## 3.3.1(2026-11-13 已发布)
2
30
 
3
31
  > 三方发布:annotated tag `v3.3.1` + GitHub Release + npm `@twinsearth/dsh-master@3.3.1`。
@@ -1,7 +1,7 @@
1
1
  # 房主 dsh-Master 3.3.0 架构设计:成本优化 & 省 Token 子插件(系统 + 模块)
2
2
 
3
- > 状态:**已实施并发布(3.3.0 / 3.3.1 三方发布:annotated tag + GitHub Release + npm)**。基线代码 `3.2.10`,本文件描述 3.3.0 的架构与落地路径;实施进度见 §17。
4
- > 关联:`docs/ROADMAP.md`(3.3.0 = HarnessMM 经济性)、`docs/ARCHITECTURE.md`(角色矩阵 / `master-cost-optimizer` 行 → 仅 fork)、
3
+ > 状态:**已实施并发布(3.3.0 / 3.3.1 / 3.3.2 三方发布:annotated tag + GitHub Release + npm)**。基线代码 `3.2.10`,本文件描述 3.3.0 的架构与落地路径;实施进度见 §17。
4
+ > 关联:`docs/ROADMAP.md`(3.3.0 = HarnessMM 经济性)、`docs/ARCHITECTURE.md`(角色矩阵 / `master-cost-optimizer` 行 → fork/mother;3.3.3 起母体也开放)、
5
5
  > `src/plugins/cost-optimizer.ts`(3.2.x 的 observe 骨架,本设计在其上扩展且**不破坏**现有服务接口)。
6
6
  > 参考实现:`dsh-ops`(MIT,工具层)、`HarnessMM`(Apache-2.0,arXiv:2610.32813,决策层)。**只移植协议语义、自研实现,不复制第三方代码。**
7
7
  > 移植边界的完整定义、当前实现来源核对与新增模块检查清单见 [`docs/THIRD-PARTY.md`](THIRD-PARTY.md),由 `test/license-provenance.test.mjs` 自动守卫。
@@ -12,7 +12,7 @@
12
12
 
13
13
  ```text
14
14
  ┌──────────────────────────────────────────┐
15
- │ master-cost-optimizer(行,仅 fork) │
15
+ │ master-cost-optimizer(行,fork/mother) │
16
16
  │ = 编排器 + 对外服务 dshMasterCost │
17
17
  └───────────────┬──────────────────────────┘
18
18
  │
@@ -158,7 +158,7 @@
158
158
 
159
159
  ```text
160
160
  ┌──────────────────────────────────────────┐
161
- │ master-cost-optimizer(行,仅 fork) │
161
+ │ master-cost-optimizer(行,fork/mother) │
162
162
  │ = 编排器 + 对外服务 dshMasterCost │
163
163
  └───────────────┬──────────────────────────┘
164
164
  │
@@ -1054,5 +1054,7 @@ node scripts/milestones.mjs --input evidence.json # 追加 M3 守卫准确率 /
1054
1054
  | **M2 收口** 系统层 attribution / bus + 模块 SPI | ✅ 已实施(3.3.0-dev,未发布) | `src/cost/attribution.ts`(按模块/任务聚合,enforce 才入账)、`src/cost/bus.ts`(事件总线 + 回流出口,订阅者异常隔离)、`worker/cost-spi.ts`(ToolModule / DecisionModule 契约)、`worker/cost-modules.ts`(注册表 + A1 适配,异常 fail-open)、`test/worker-cost-spi.test.ts`;Agent Loop 已改为只依赖注册表 |
1055
1055
  | **M2 余项 / M3–M4** | ✅ 已发布(3.3.0) | A2 渐进纰漏、A3 批量、B1–B4、真实 fork 对照实验、面板成本块(见 §14) |
1056
1056
  | **M5 回流收口** | ✅ 已发布(3.3.1) | contract 1.1.0(`fang.relay.publish`/`status`)+ `src/cost/fang-relay.ts` + `scripts/fang-relay-probe.mjs`;M5 由 `fang-relay-out-of-scope` 改为对端回执判定 |
1057
+ | **M5 真实瓮城实测** | ✅ 已发布(3.3.2) | 瓮城 3.1.35 内嵌 1.1.0 并自报 `dsh-fang-bundle`;探针 `--peer fang` 用瓮城发布物当对端(VENDOR.json 校验 8/8、peerLabel 取其 orchestrator);实测 6 条回执、幂等重投通过;同批修 contract 1.1.1 回执计数缺陷并同步 |
1058
+ | **角色矩阵放开** | ✅ 已发布(3.3.3) | 母体全开(Worker/Loop/成本进 mother)+ 分身挂双主管;唯一保留限制 Router 不进 fork;7 条测试与 5 处文档同步 |
1057
1059
 
1058
- *3.3.0 / 3.3.1 均已三方发布(annotated tag + GitHub Release + npm)。*
1060
+ *3.3.0 / 3.3.1 / 3.3.2 / 3.3.3 均已三方发布(annotated tag + GitHub Release + npm);contract 1.0.0 / 1.1.0 / 1.1.1 已发 npm。*
@@ -103,15 +103,78 @@ M5 │ 实现 ✔ │ 依赖 ✔ │ 效果 verified │ 状态 done │ 回流
103
103
 
104
104
  **不能证明**
105
105
 
106
- - 不能说“已与真实 dsh-Fang bundle 完成对接”:bundle 尚未依赖 contract 1.1,也尚未挂
107
- `relayHandlers`。等 bundle 采用 1.1 后,`peer` 自报值会变成 `dsh-fang-bundle`,
108
- M5 的 `notes` 会自动消失(代码里写死的是白名单,不需要改里程碑表);
106
+ - 不能说“两个插件在同一个 DSH 宿主里同时加载跑通了”:按本会话守则 §二,**不得把瓮城安装/升级到任何 profile**,
107
+ 所以本轮没有做宿主级 co-load;做的是「瓮城**已发布包**的 vendored 契约服务端 × 房主内嵌副本客户端」的跨进程实测;
109
108
  - 不能证明生产环境的网络/鉴权拓扑:本轮是 127.0.0.1 + Bearer token 的本机双进程。
110
109
 
111
110
  ## 6. 复现注意事项
112
111
 
113
112
  - 必须 `npm run build` 后跑:脚本 import 的是 `dist-host/`;
114
- - 子进程用的是 `node_modules` 里**已发布**的包,父进程用 `src/contract/`,两侧故意不同源,
115
- 才能真正测出“已发布包 ↔ 内嵌副本”的互操作;
113
+ - 两种对端:默认 `--peer contract` 用已发布的契约包(自报 `contract-server`,仅协议自测);
114
+ `--peer fang --fang-dir <解包目录>` 用**瓮城自己的包**当对端(自报 `dsh-fang-bundle`);
116
115
  - 证据文件不要提交进仓库(G6 零残留门禁会检查 `rsi-report.json` 一类产物);
117
116
  - Windows 上子进程退出由 `SIGTERM` 处理,若端口被占用会直接报 ready 超时。
117
+
118
+ ## 7. 瓮城 3.1.35 复核(2026-11-13 晚)
119
+
120
+ 瓮城已发 **3.1.35**(commit `feat(v3.1.35): 适配 contract 1.1.0 回流接口 (#100)`),房主侧按下面的方式复核:
121
+
122
+ ```bash
123
+ npm run build
124
+ node scripts/fang-relay-probe.mjs --peer fang --fang-dir <解包后的 @twinsearth/dsh-fang> --events 6
125
+ ```
126
+
127
+ **静态核对(对端是瓮城发布物本身)**
128
+
129
+ | 项 | 结果 |
130
+ |---|---|
131
+ | 瓮城 `src/contract/` 8 个文件 vs contract 1.1.0 | **8/8 逐字节相同** |
132
+ | 瓮城 `src/contract/VENDOR.json` | `package=@twinsearth/dsh-fang-contract, version=1.1.0, fileCount=8`,逐文件 sha256 与实物一致 |
133
+ | `src/plugins/orchestrator.mjs` | `createRelayStore({ peerLabel: 'dsh-fang-bundle' })` + handlers 里 `...relayHandlers(relayStore)` |
134
+
135
+ 探针不硬编码 `peerLabel`:它从**瓮城自己的 orchestrator 源码**里读出该值,并用瓮城的 `VENDOR.json` 校验文件哈希,
136
+ 所以 `peer` 是瓮城自报的,不是我们假设的。
137
+
138
+ **实测结果(`--peer fang`)**
139
+
140
+ ```json
141
+ {
142
+ "ok": true,
143
+ "peerMode": "fang",
144
+ "expectation": { "peer": "dsh-fang-bundle", "matched": true },
145
+ "fangRelay": { "available": true, "peer": "dsh-fang-bundle", "events": 6,
146
+ "sameProcess": false, "transport": "http", "contractVersion": "1.1.0",
147
+ "cursor": 6, "failed": 0, "dropped": 0 },
148
+ "provenance": { "fangVersion": "3.1.35", "vendorVersion": "1.1.0",
149
+ "vendoredFilesChecked": 8, "vendoredFilesOk": true,
150
+ "peerLabelSource": "src/plugins/orchestrator.mjs",
151
+ "relayHandlersRegistered": true },
152
+ "detail": {
153
+ "probePid": 10632, "peerPid": 12972, "sameProcess": false,
154
+ "idempotency": { "ok": true, "events": 3,
155
+ "first": { "accepted": 3, "duplicates": 0, "cursor": 9 },
156
+ "second": { "accepted": 0, "duplicates": 3, "cursor": 9 } }
157
+ }
158
+ }
159
+ ```
160
+
161
+ 四项预期全部成立:`peer === 'dsh-fang-bundle'`、`events ≥ 3`、`sameProcess === false`、`failed === 0`;
162
+ 额外核对幂等重投:同批事件第二次 `accepted:0 / duplicates:3 / cursor` 不倒退。
163
+
164
+ **这次实测发现 contract 1.1.0 的一个回执缺陷(已在 1.1.1 修复并发布)**
165
+
166
+ `createRelayStore().publish()` 用 `validated.rejected.length - duplicates` 计算 `rejected`,
167
+ 而 `duplicates` 同时包含「已存在重复」和「批内重复」,重投时会得到**负数**(实测 `-3`)。
168
+ 影响面:只影响回执里的 `rejected` 字段(事件去重、`cursor`、`accepted` 都正确),房主侧不读该字段;
169
+ 但负数回执是错的数据。已加回归测试并修正为「`rejected` 只数真正非法条目,`duplicates` 单独累计」,
170
+ 契约仓库测试 16/16 通过;**contract 1.1.1 已发布**(npm `latest=1.1.1`,字节核验通过),
171
+ 房主 **3.3.2** 已同步 1.1.1 并重新三方发布。
172
+
173
+ > 注意:瓮城 3.1.35 内嵌的仍是 1.1.0(含该回执缺陷)。这是 PATCH 级差异、只影响 `rejected` 显示,
174
+ > 不影响去重与投递;瓮城下次 vendor 同步时带上 1.1.1 即可,无需协议改动。
175
+
176
+ **仍未证明**
177
+
178
+ - 宿主级 co-load(守则 §二禁止往 profile 装瓮城);
179
+ - 瓮城侧 UI/信息栏是否消费了回流事件(属瓮城产品面,房主只需协议可用)。
180
+
package/docs/SECURITY.md CHANGED
@@ -60,14 +60,16 @@
60
60
  | 子插件 | 允许角色 | 强制位置 |
61
61
  |---|---|---|
62
62
  | SuperAgent Router | **essence / mother** | `superagent-router.ts` apply 开头 `roleAllowed(ROW_ID, role)`;fork 不注册服务 |
63
- | SuperAgent Worker | **fork / essence** | `superagent-worker.ts` 同上;母体不注册 |
64
- | 成本优化 | **fork only** | `cost-optimizer.ts` 同上 |
65
- | 双主管 | **mother / essence** | `dual-manager.ts` 同上 |
63
+ | SuperAgent Worker | **fork / essence / mother** | `superagent-worker.ts` 同上 |
64
+ | Worker Agent Loop | **fork / essence / mother** | 编译 preset(`dist-worker`,`worker/index.ts` 自注册);矩阵用于宿主状态与准入显示 |
65
+ | 成本优化 | **fork / mother** | `cost-optimizer.ts` 同上;essence 不注册 |
66
+ | 双主管 | **mother / essence / fork** | `dual-manager.ts` 同上 |
66
67
 
67
- - 母体(mother)只接纳 Router 与双主管,不接纳 Worker/Loop 执行体;
68
- - Router 是控制面:不读文件、不跑命令、不改代码;执行体在 fork Worker 或原身(3.2.10 起)中运行;
69
- - Worker 的物理隔离仍是默认执行路径,**绝不**在母体启用;
70
- - 测试逐角色断言(mother/essence/fork)服务是否存在。
68
+ - 角色矩阵自 **3.3.3** 起:母体全开(Worker / Loop / 成本进 mother),分身也挂双主管;
69
+ **唯一保留的硬限制:Router 不进 fork**(分身只执行,不注册控制面);
70
+ - Router 是控制面:不读文件、不跑命令、不改代码;执行体在 fork / essence / mother 中运行(3.3.3 起母体也可执行);
71
+ - 放开的是**准入名单**,不是安全边界:预算闸、Mandate 守卫、工具白名单、敏感路径拒绝仍然逐层生效;
72
+ - 测试逐角色断言(mother/essence/fork)服务是否存在,并断言 Router 在 fork 恒不可用。
71
73
 
72
74
  ## 2. 外部输入不可信
73
75
 
@@ -119,7 +121,7 @@ Gate:来源允许列表、Actor 允许列表(**空列表 = 全部拒绝**)
119
121
  | 面 | 措施 |
120
122
  |---|---|
121
123
  | 母体派发桥 | `POST /dsh-master/api/bridge/spawn` 必须 Bearer `bridgeToken`(常量时间比较);只接受 ticket + `DSH_MASTER_*` env;只调用 `fangForker.spawn`,无本地降级 |
122
- | Worker preset 角色 | 编译行 `master-superagent-loop` 只 fork;母体/元主(`DSH_FANG_ESSENCE=1`)直接拒绝执行 |
124
+ | Worker preset 角色 | 编译行 `master-superagent-loop` 自 3.3.3 起 fork/essence/mother 均可运行;Router 仍不注册 fork |
123
125
  | Worker 实时通道 | Router 生成 per-ticket stream token;Worker 只能提交自己 ticket 的事件(token→ticketId 绑定),done 后消费 token |
124
126
  | 回写 | 默认关闭;webhook/GitHub token 只在配置内,状态接口只返回模式;失败只告警 |
125
127
  | Shell 环境 | 白名单 env,剥掉 `KEY/TOKEN/SECRET/DSH_*`;超时 kill 进程树;解释器 `-e/-c` 黑名单 |
@@ -117,7 +117,7 @@ fangForker.spawn({
117
117
  |---|---|
118
118
  | 外部输入不可信 | 验签/Token → 归一化 → Gate;payload 永远当不可信文本 |
119
119
  | Mandate 防越权 | 不可变 Ticket;Worker 精确 scope 校验,越权退回 Router |
120
- | 物理隔离 | Worker 默认在 fork;3.2.10 起也可在 essence;Router 在 essence/mother;母体不接纳执行体 |
120
+ | 物理隔离 | Worker 默认在 fork;3.2.10 起也可在 essence;3.3.3 起母体也放开(用户要求);Router 恒在 essence/mother,不进 fork |
121
121
  | 长任务不阻塞 | `exec_shell_in_vm` 超过 5s 返回后台 job id,`vm_job_result` 查询真实终态与输出 |
122
122
  | 失败不假装成功 | dispatch 失败 → Ticket= failed、decision=unavailable,错误码原样返回 |
123
123
  | 去重 | 事件 id 最近 500 条 FIFO(重复事件不刷新顺序);契约幂等键随 envelope 传递 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twinsearth/dsh-master",
3
- "version": "3.3.1",
3
+ "version": "3.3.3",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -75,7 +75,7 @@
75
75
  "build": "npm run build:host && npm run build:worker"
76
76
  },
77
77
  "devDependencies": {
78
- "@twinsearth/dsh-fang-contract": "^1.1.0",
78
+ "@twinsearth/dsh-fang-contract": "^1.1.1",
79
79
  "@types/node": "^24.0.0",
80
80
  "typescript": "^5.9.0"
81
81
  },
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  export const CONTRACT_PACKAGE = '@twinsearth/dsh-fang-contract'
14
- export const CONTRACT_VERSION = '1.1.0'
14
+ export const CONTRACT_VERSION = '1.1.1'
15
15
  export const PROTOCOL = 'fang-master'
16
16
  export const PROTOCOL_VERSION = 1
17
17
  export const MIN_COMPATIBLE_MAJOR = 1
@@ -123,9 +123,11 @@ export function createRelayStore(options = {}) {
123
123
  }
124
124
  counters.batches += 1
125
125
  const acceptedIds = []
126
- let duplicates = 0
126
+ let alreadySeen = 0
127
+ let inBatchDuplicates = 0
128
+ let rejectedCount = 0
127
129
  for (const event of validated.accepted) {
128
- if (seen.has(event.id)) { duplicates += 1; continue }
130
+ if (seen.has(event.id)) { alreadySeen += 1; continue }
129
131
  seen.set(event.id, { at: nowMs(now), cursor: cursor + 1 })
130
132
  cursor += 1
131
133
  counters.accepted += 1
@@ -133,13 +135,17 @@ export function createRelayStore(options = {}) {
133
135
  remember({ id: event.id, type: event.type, at: event.at, cursor })
134
136
  lastAt = nowMs(now)
135
137
  }
136
- counters.duplicates += duplicates
137
138
  for (const item of validated.rejected) {
138
139
  // 批内重复计入 duplicates;其余非法条目计入 rejected 并留原因(有界)。
139
- if (item.reason === 'duplicate-in-batch') { duplicates += 1; counters.duplicates += 1; continue }
140
- counters.rejected += 1
140
+ if (item.reason === 'duplicate-in-batch') { inBatchDuplicates += 1; continue }
141
+ rejectedCount += 1
141
142
  if (lastErrors.length < 50) lastErrors.push({ id: item.id, reason: item.reason })
142
143
  }
144
+ // 两个计数分开累计:duplicates = 已存在 + 批内重复;rejected 只数真正非法的条目。
145
+ // (1.1.0 曾用 `validated.rejected.length - duplicates` 计算 rejected,重投时会得到负数。)
146
+ const duplicates = alreadySeen + inBatchDuplicates
147
+ counters.duplicates += duplicates
148
+ counters.rejected += rejectedCount
143
149
  const batchId = String(params?.batchId ?? `batch-${counters.batches}`)
144
150
  const receipt = Object.freeze({
145
151
  ok: true,
@@ -147,7 +153,7 @@ export function createRelayStore(options = {}) {
147
153
  taskId: params?.taskId === undefined ? null : String(params.taskId),
148
154
  accepted: acceptedIds.length,
149
155
  duplicates,
150
- rejected: validated.rejected.length - duplicates,
156
+ rejected: rejectedCount,
151
157
  cursor,
152
158
  acceptedIds,
153
159
  peer: peerLabel,
package/src/kernel.ts CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  import crypto from 'node:crypto'
12
12
 
13
- export const MASTER_VERSION = '3.3.1'
13
+ export const MASTER_VERSION = '3.3.3'
14
14
  export const FANG_CONTRACT_MIN = '3.1.14'
15
15
 
16
16
  export const SERVICE = Object.freeze({
@@ -67,18 +67,20 @@ export const ROLE = Object.freeze({ mother: 'mother', essence: 'essence', fork:
67
67
 
68
68
  /**
69
69
  * 角色矩阵:
70
- * * Router 在 essence 与 mother 激活:只评估/建票/派发,不执行代码;fork 不注册。
71
- * * Worker / Worker Agent Loop 在 fork 与 essence 激活:原身可直接执行工单,
72
- * 分身仍是默认执行体;mother 不执行。
73
- * * 成本优化仍只 fork;双主管 mother/essence。
70
+ * * Router 在 essence 与 mother 激活:只评估/建票/派发,不执行代码改动;fork 不注册。
71
+ * * Worker / Worker Agent Loop 在 fork、essence、**mother** 激活:母体默认只做编排,
72
+ * 但 3.3.3 起允许母体直接执行工单(用户明确要求「母体全部激活」;此前"母体不接纳
73
+ * 执行体"的旧约束已废止,见 README 角色矩阵与 CHANGELOG)。
74
+ * * 成本优化在 fork 与 mother 激活(母体也需要看到成本账与回流);essence 不挂(元宝只编排)。
75
+ * * 双主管 mother / essence / fork 全开:管理能力不再按角色排除,是否派发由权限与预算闸决定。
74
76
  */
75
77
  export const ROLE_MATRIX = Object.freeze({
76
78
  'master-core': Object.freeze(Object.values(ROLE)),
77
79
  'master-superagent-router': Object.freeze([ROLE.essence, ROLE.mother]),
78
- 'master-superagent-worker': Object.freeze([ROLE.fork, ROLE.essence]),
79
- 'master-superagent-loop': Object.freeze([ROLE.fork, ROLE.essence]),
80
- 'master-cost-optimizer': Object.freeze([ROLE.fork]),
81
- 'master-dual-manager': Object.freeze([ROLE.mother, ROLE.essence]),
80
+ 'master-superagent-worker': Object.freeze([ROLE.fork, ROLE.essence, ROLE.mother]),
81
+ 'master-superagent-loop': Object.freeze([ROLE.fork, ROLE.essence, ROLE.mother]),
82
+ 'master-cost-optimizer': Object.freeze([ROLE.fork, ROLE.mother]),
83
+ 'master-dual-manager': Object.freeze([ROLE.mother, ROLE.essence, ROLE.fork]),
82
84
  })
83
85
 
84
86
  export const SUBPLUGIN_CATALOG = Object.freeze([
@@ -117,7 +117,7 @@ export function apply(ctx, config = {}) {
117
117
 
118
118
  const service = {
119
119
  id: SERVICE.cost,
120
- version: '3.3.1',
120
+ version: '3.3.3',
121
121
  role,
122
122
  get mode() {
123
123
  return system.policy.describe().mode
@@ -354,7 +354,7 @@ export function apply(ctx, config = {}) {
354
354
  return {
355
355
  ok: true,
356
356
  id: 'cost-optimizer',
357
- version: '3.3.1',
357
+ version: '3.3.3',
358
358
  role,
359
359
  mode: current.mode,
360
360
  requestedMode: current.requestedMode,
@@ -396,7 +396,7 @@ export function apply(ctx, config = {}) {
396
396
  doubleCountGuard: report.doubleCountGuard,
397
397
  }
398
398
  })(),
399
- note: '3.3.1:系统层记账/预算/灰度/审计 + A1 工具输出紧凑化(默认 shadow)+ 瓮城回流出口(contract 1.1 真实投递);工具面 A2–A3 与决策复用 B1–B4 已接入(shadow 默认)。',
399
+ note: '3.3.3:系统层记账/预算/灰度/审计 + A1 工具输出紧凑化(默认 shadow)+ 瓮城回流出口(contract 1.1.1 真实投递,回执计数修复);工具面 A2–A3 与决策复用 B1–B4 已接入(shadow 默认);3.3.3 起角色矩阵母体全开、分身挂双主管。',
400
400
  }
401
401
  },
402
402
  }