@twinsearth/dsh-master 3.2.10 → 3.3.1

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 (104) hide show
  1. package/README.md +4 -0
  2. package/client.js +50 -0
  3. package/dist-host/contract/client.js +7 -0
  4. package/dist-host/contract/constants.js +16 -2
  5. package/dist-host/contract/index.js +1 -0
  6. package/dist-host/contract/relay.js +239 -0
  7. package/dist-host/cost/attribution.js +183 -0
  8. package/dist-host/cost/audit.js +163 -0
  9. package/dist-host/cost/budget.js +211 -0
  10. package/dist-host/cost/bus.js +95 -0
  11. package/dist-host/cost/cert-chain.js +100 -0
  12. package/dist-host/cost/experiment.js +132 -0
  13. package/dist-host/cost/fang-relay.js +329 -0
  14. package/dist-host/cost/gate.js +189 -0
  15. package/dist-host/cost/host-surface.js +80 -0
  16. package/dist-host/cost/index.js +345 -0
  17. package/dist-host/cost/ledger-report.js +193 -0
  18. package/dist-host/cost/ledger.js +401 -0
  19. package/dist-host/cost/metrics.js +198 -0
  20. package/dist-host/cost/milestones.js +209 -0
  21. package/dist-host/cost/modules/index.js +5 -0
  22. package/dist-host/cost/modules/manifests.js +130 -0
  23. package/dist-host/cost/policy.js +276 -0
  24. package/dist-host/cost/report.js +156 -0
  25. package/dist-host/cost/reuse-report.js +128 -0
  26. package/dist-host/cost/rsi.js +367 -0
  27. package/dist-host/cost/toolface.js +147 -0
  28. package/dist-host/cost/types.js +31 -0
  29. package/dist-host/kernel.js +28 -2
  30. package/dist-host/plugins/cost-optimizer.js +329 -27
  31. package/dist-host/plugins/superagent-router.js +104 -1
  32. package/dist-host/superagent/types.js +8 -0
  33. package/dist-worker/agent-loop.js +449 -7
  34. package/dist-worker/baseline-fit.js +66 -0
  35. package/dist-worker/budget-guard.js +93 -0
  36. package/dist-worker/ccs.js +99 -0
  37. package/dist-worker/cost-hooks.js +115 -0
  38. package/dist-worker/cost-modules.js +330 -0
  39. package/dist-worker/cost-spi.js +3 -0
  40. package/dist-worker/edg.js +372 -0
  41. package/dist-worker/gdr.js +159 -0
  42. package/dist-worker/index.js +80 -2
  43. package/dist-worker/llm-http.js +20 -0
  44. package/dist-worker/sink-http.js +1 -0
  45. package/dist-worker/spill.js +115 -0
  46. package/dist-worker/tool-batch.js +66 -0
  47. package/dist-worker/tool-compact.js +275 -0
  48. package/dist-worker/tool-disclosure.js +90 -0
  49. package/dist-worker/tools.js +16 -0
  50. package/docs/ARCHITECTURE.md +24 -0
  51. package/docs/CHANGELOG.md +66 -0
  52. package/docs/DESIGN-v3.3.0-cost-optimization.md +1058 -0
  53. package/docs/EXPERIMENT-M5-fang-relay.md +117 -0
  54. package/docs/EXPERIMENT-M5-real-ab.md +87 -0
  55. package/docs/THIRD-PARTY.md +87 -0
  56. package/package.json +2 -2
  57. package/src/contract/client.js +7 -0
  58. package/src/contract/constants.js +17 -2
  59. package/src/contract/index.js +1 -0
  60. package/src/contract/relay.js +223 -0
  61. package/src/cost/attribution.ts +186 -0
  62. package/src/cost/audit.ts +170 -0
  63. package/src/cost/budget.ts +226 -0
  64. package/src/cost/bus.ts +97 -0
  65. package/src/cost/cert-chain.ts +102 -0
  66. package/src/cost/experiment.ts +136 -0
  67. package/src/cost/fang-relay.ts +318 -0
  68. package/src/cost/gate.ts +191 -0
  69. package/src/cost/host-surface.ts +97 -0
  70. package/src/cost/index.ts +348 -0
  71. package/src/cost/ledger-report.ts +186 -0
  72. package/src/cost/ledger.ts +385 -0
  73. package/src/cost/metrics.ts +193 -0
  74. package/src/cost/milestones.ts +206 -0
  75. package/src/cost/modules/index.ts +13 -0
  76. package/src/cost/modules/manifests.ts +129 -0
  77. package/src/cost/policy.ts +276 -0
  78. package/src/cost/report.ts +158 -0
  79. package/src/cost/reuse-report.ts +131 -0
  80. package/src/cost/rsi.ts +364 -0
  81. package/src/cost/toolface.ts +151 -0
  82. package/src/cost/types.ts +298 -0
  83. package/src/kernel.ts +27 -2
  84. package/src/plugins/cost-optimizer.ts +313 -26
  85. package/src/plugins/superagent-router.ts +96 -1
  86. package/src/superagent/types.ts +70 -0
  87. package/worker/agent-loop.ts +444 -7
  88. package/worker/baseline-fit.ts +80 -0
  89. package/worker/budget-guard.ts +156 -0
  90. package/worker/ccs.ts +202 -0
  91. package/worker/cost-hooks.ts +133 -0
  92. package/worker/cost-modules.ts +356 -0
  93. package/worker/cost-spi.ts +283 -0
  94. package/worker/edg.ts +424 -0
  95. package/worker/gdr.ts +233 -0
  96. package/worker/index.ts +84 -3
  97. package/worker/llm-http.ts +18 -0
  98. package/worker/sink-http.ts +5 -2
  99. package/worker/spill.ts +133 -0
  100. package/worker/tool-batch.ts +124 -0
  101. package/worker/tool-compact.ts +317 -0
  102. package/worker/tool-disclosure.ts +131 -0
  103. package/worker/tools.ts +553 -531
  104. package/worker/types.ts +191 -0
@@ -0,0 +1,193 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (c) 2026 dsh-Master contributors — see LICENSE-CORE.
3
+ // @ts-nocheck — host JS→TS 迁移阶段:运行时行为优先,类型注解逐步补齐(worker/ 仍严格检查)。
4
+
5
+ /**
6
+ * 3.3.0 · metrics(验收指标,设计 §10.1)。
7
+ *
8
+ * **每份报告必须给全**七项指标;缺任何一项都必须显式报 `complete: false` + `missing[]`,
9
+ * 不允许"报一半也算报告"。
10
+ *
11
+ * | 指标 | 口径 |
12
+ * |---|---|
13
+ * | tokens/task | 完整任务输入+输出,含重试/回退/失败执行 |
14
+ * | 成功率 | 与对照组同 scorer;差值 ≥2pp 下降即触发降级审查 |
15
+ * | GDR 命中率 / 未命中分布 | 每次复用尝试为分母 |
16
+ * | CCS 替换数 / 证书完整率 | 证书必须 100% 可回放 |
17
+ * | 节省归因 | A1/A2/A3/B1–B4 分列 + unattributed |
18
+ * | governance 成本 | 作者步骤、RSI 离线、选择/验证 |
19
+ * | 耗时 | wall-clock 中位数与 p95 |
20
+ */
21
+
22
+ export const METRIC_KEYS = Object.freeze([
23
+ 'tokensPerTask',
24
+ 'success',
25
+ 'gdr',
26
+ 'ccs',
27
+ 'attribution',
28
+ 'governance',
29
+ 'latency',
30
+ ])
31
+
32
+ /** 成功率相对对照组下降 ≥2pp ⇒ 触发降级审查(§10.1)。 */
33
+ export const SUCCESS_REGRESSION_PP = 0.02
34
+
35
+ const MODULES = Object.freeze(['a1-compact', 'a2-disclosure', 'a3-batch', 'b1-edg', 'b2-gdr', 'b3-ccs', 'b4-rsi', 'unattributed'])
36
+
37
+ function num(value) {
38
+ const n = Number(value)
39
+ return Number.isFinite(n) ? n : 0
40
+ }
41
+
42
+ function percentile(sorted, ratio) {
43
+ if (sorted.length === 0) return null
44
+ if (sorted.length === 1) return sorted[0]
45
+ const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(ratio * sorted.length) - 1))
46
+ return sorted[index]
47
+ }
48
+
49
+ function median(values) {
50
+ const sorted = [...values].sort((a, b) => a - b)
51
+ return percentile(sorted, 0.5)
52
+ }
53
+
54
+ function p95(values) {
55
+ const sorted = [...values].sort((a, b) => a - b)
56
+ return percentile(sorted, 0.95)
57
+ }
58
+
59
+ /**
60
+ * @param {{
61
+ * entries?: object[],
62
+ * certificates?: object[],
63
+ * tasks?: Array<{taskId: string, success?: boolean, elapsedMs?: number, tokens?: number}>,
64
+ * control?: { successRate?: number, tokensPerTask?: number },
65
+ * guard?: { attempts?: number, hits?: number, byReason?: object },
66
+ * audit?: { ok?: boolean, count?: number },
67
+ * traceCheck?: (taskId: string) => { reconciled?: boolean },
68
+ * }} [input]
69
+ */
70
+ export function buildMetricsReport(input = {}) {
71
+ const entries = Array.isArray(input.entries) ? input.entries : []
72
+ const certificates = Array.isArray(input.certificates) ? input.certificates : []
73
+ const tasks = Array.isArray(input.tasks) ? input.tasks : []
74
+ const missing = []
75
+
76
+ // ---- tokens/task:完整任务输入+输出(含重试/回退/失败执行)----
77
+ const tokensByTask = new Map()
78
+ for (const entry of entries) {
79
+ if (entry?.account !== 'execution') continue
80
+ const key = String(entry.taskId ?? 'unattributed')
81
+ tokensByTask.set(key, (tokensByTask.get(key) ?? 0) + Math.max(0, num(entry.total)))
82
+ }
83
+ const taskTokens = (tasks.length > 0
84
+ ? tasks.map((task) => Math.max(0, num(task.tokens) || (tokensByTask.get(String(task.taskId)) ?? 0)))
85
+ : [...tokensByTask.values()])
86
+ // 既有任务显式 tokens、也有账本 token 事件,才算「有 token 数据」;否则该项缺失。
87
+ const hasTokenData = tokensByTask.size > 0 || tasks.some((task) => num(task?.tokens) > 0)
88
+ const tokensPerTask = hasTokenData && taskTokens.length > 0
89
+ ? Math.round((taskTokens.reduce((sum, value) => sum + value, 0) / taskTokens.length) * 100) / 100
90
+ : null
91
+ if (tokensPerTask === null) missing.push('tokensPerTask')
92
+
93
+ // ---- 成功率:与对照组同 scorer;≥2pp 下降触发审查 ----
94
+ const scored = tasks.filter((task) => typeof task?.success === 'boolean')
95
+ const successRate = scored.length > 0 ? scored.filter((task) => task.success === true).length / scored.length : null
96
+ if (successRate === null) missing.push('success')
97
+ const controlRate = Number.isFinite(Number(input?.control?.successRate)) ? Number(input.control.successRate) : null
98
+ const successDelta = successRate !== null && controlRate !== null ? Math.round((successRate - controlRate) * 10000) / 10000 : null
99
+ const regression = successDelta !== null && successDelta <= -SUCCESS_REGRESSION_PP
100
+ const success = {
101
+ rate: successRate,
102
+ samples: scored.length,
103
+ scorer: 'same-as-control',
104
+ control: controlRate,
105
+ delta: successDelta,
106
+ /** §10.1:差值 ≥2pp 下降 ⇒ 触发降级审查。 */
107
+ reviewRequired: regression,
108
+ thresholdPp: SUCCESS_REGRESSION_PP,
109
+ }
110
+
111
+ // ---- GDR:每次复用尝试为分母 ----
112
+ const attempts = Math.max(0, num(input?.guard?.attempts))
113
+ const hits = Math.max(0, num(input?.guard?.hits))
114
+ const byReason = { ...(input?.guard?.byReason ?? {}) }
115
+ const gdr = {
116
+ attempts,
117
+ hits,
118
+ hitRate: attempts > 0 ? Math.round((hits / attempts) * 10000) / 10000 : null,
119
+ byReason,
120
+ denominator: 'reuse-attempts',
121
+ }
122
+ if (attempts === 0) missing.push('gdr')
123
+
124
+ // ---- CCS:替换数 + 证书完整率(必须 100% 可回放)----
125
+ const dispatched = certificates.filter((cert) => cert?.dispatched !== false)
126
+ const REQUIRED = ['edgeId', 'toolName', 'schemaHash', 'guardExpr', 'receiptDigest', 'replacedCallbackId', 'taskId']
127
+ const complete = certificates.filter((cert) => REQUIRED.every((key) => String(cert?.[key] ?? '').trim() !== '') && cert?.viaNativeDispatcher === true)
128
+ const reconciled = certificates.filter((cert) => {
129
+ if (typeof input?.traceCheck !== 'function') return true
130
+ try { return input.traceCheck(String(cert?.taskId ?? ''))?.reconciled === true } catch { return false }
131
+ })
132
+ const completeness = certificates.length > 0 ? Math.round((complete.length / certificates.length) * 10000) / 10000 : null
133
+ const ccs = {
134
+ substitutions: dispatched.length,
135
+ certificates: certificates.length,
136
+ completeness,
137
+ replayable: completeness === 1,
138
+ reconciled: certificates.length === 0 ? null : reconciled.length === certificates.length,
139
+ auditVerified: input?.audit?.ok !== false,
140
+ }
141
+ if (certificates.length === 0) missing.push('ccs')
142
+
143
+ // ---- 节省归因:按模块分列 + unattributed ----
144
+ const byModule = {}
145
+ for (const key of MODULES) byModule[key] = 0
146
+ for (const entry of entries) {
147
+ if (entry?.account !== 'execution' || !(num(entry?.savedTokens) > 0)) continue
148
+ const moduleKey = MODULES.includes(String(entry.module)) ? String(entry.module) : 'unattributed'
149
+ byModule[moduleKey] += Math.max(0, num(entry.savedTokens))
150
+ }
151
+ const attribution = { byModule, total: Object.values(byModule).reduce((sum, value) => sum + value, 0) }
152
+ if (attribution.total === 0) missing.push('attribution')
153
+
154
+ // ---- governance:作者 / RSI 离线 / 选择与验证 ----
155
+ const governanceByKind = { author: 0, rsiOffline: 0, selection: 0, other: 0 }
156
+ for (const entry of entries) {
157
+ if (entry?.account !== 'governance') continue
158
+ const tokens = Math.max(0, num(entry.total))
159
+ const kind = String(entry.meta?.kind ?? entry.source ?? 'other')
160
+ if (kind.includes('edg') || kind.includes('author')) governanceByKind.author += tokens
161
+ else if (kind.includes('rsi') || String(entry.source) === 'offline') governanceByKind.rsiOffline += tokens
162
+ else if (kind.includes('select') || kind.includes('valid') || kind.includes('load')) governanceByKind.selection += tokens
163
+ else governanceByKind.other += tokens
164
+ }
165
+ const governance = {
166
+ ...governanceByKind,
167
+ total: Object.values(governanceByKind).reduce((sum, value) => sum + value, 0),
168
+ }
169
+
170
+ // ---- 耗时:中位数 + p95 ----
171
+ const durations = tasks.map((task) => num(task?.elapsedMs)).filter((value) => value > 0)
172
+ const latency = {
173
+ medianMs: durations.length > 0 ? median(durations) : null,
174
+ p95Ms: durations.length > 0 ? p95(durations) : null,
175
+ samples: durations.length,
176
+ }
177
+ if (durations.length === 0) missing.push('latency')
178
+
179
+ const uniqueMissing = METRIC_KEYS.filter((key) => missing.includes(key))
180
+ return Object.freeze({
181
+ ok: true,
182
+ complete: uniqueMissing.length === 0,
183
+ missing: uniqueMissing,
184
+ tokensPerTask: Object.freeze({ value: tokensPerTask, includesRetries: true, includesFailures: true, samples: taskTokens.length }),
185
+ success: Object.freeze(success),
186
+ gdr: Object.freeze(gdr),
187
+ ccs: Object.freeze(ccs),
188
+ attribution: Object.freeze({ ...attribution, byModule: Object.freeze({ ...byModule }) }),
189
+ governance: Object.freeze(governance),
190
+ latency: Object.freeze(latency),
191
+ control: Object.freeze({ successRate: controlRate, tokensPerTask: Number.isFinite(Number(input?.control?.tokensPerTask)) ? Number(input.control.tokensPerTask) : null }),
192
+ })
193
+ }
@@ -0,0 +1,206 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (c) 2026 dsh-Master contributors — see LICENSE-CORE.
3
+ // @ts-nocheck — host JS→TS 迁移阶段:运行时行为优先,类型注解逐步补齐(worker/ 仍严格检查)。
4
+
5
+ /**
6
+ * 3.3.0 · milestones(里程碑与依赖顺序,设计 §14)。
7
+ *
8
+ * 规格原文:
9
+ *
10
+ * | 阶段 | 内容 | 产出 |
11
+ * |---|---|---|
12
+ * | M1 | 系统层:ledger/budget/policy/audit + 4 切点接入(observe) | 记账报表 + 预算闸测试 |
13
+ * | M2 | 模块层 A:A1 紧凑输出、A2 渐进纰漏(shadow→enforce 白名单) | 工具面 token 下降 + 归因 |
14
+ * | M3 | 模块层 B:B1 EDG、B2 GDR(shadow 计算证书,不替换) | 命中率与「本可节省」报表 |
15
+ * | M4 | B3 CCS enforce(白名单)+ 自动回退 | 真实替换 + 证书链 + 成功率对照 |
16
+ * | M5 | A3 批量(若宿主支持)、B4 RSI 离线流程、与瓮城回流/信息栏对接 | 离线候选冻结清单 + 面板 |
17
+ *
18
+ * 依赖顺序:**M1 必须先行**(没有账本后续收益不可信);**M4 必须等 M3 的守卫准确率数据**。
19
+ *
20
+ * 本文件把这张表变成机器可读的里程碑状态:每个阶段分「实现」与「效果证据」两件事判定,
21
+ * 缺数据只能说 `effect-unmeasured`,**不许把"实现了"说成"达标了"**。
22
+ */
23
+
24
+ export const MILESTONE_IDS = Object.freeze(['M1', 'M2', 'M3', 'M4', 'M5'])
25
+
26
+ /** M4 放行前,M3 需要给出的守卫准确率最小样本量(与 policy.enforce 门禁一致)。 */
27
+ export const M3_MIN_SAMPLES = 20
28
+ /** M4 放行前要求的最小守卫准确率(与 ENFORCE_GATE.minAccuracy 一致)。 */
29
+ export const M3_MIN_ACCURACY = 0.98
30
+
31
+ /**
32
+ * M5 回流放行条件:至少 3 条事件拿到对端回执,且对端自报身份在白名单内。
33
+ * 自环(同一进程里服务端+客户端)与未知 peer 一律不算达标。
34
+ */
35
+ export const M5_MIN_RELAY_EVENTS = 3
36
+ export const M5_RELAY_PEERS = Object.freeze(['dsh-fang-bundle', 'contract-server'])
37
+
38
+ export const MILESTONES = Object.freeze([
39
+ Object.freeze({
40
+ id: 'M1',
41
+ title: '系统层:ledger/budget/policy/audit + 4 切点接入(observe)',
42
+ deliverable: '记账报表 + 预算闸测试',
43
+ dependsOn: [],
44
+ /** 实现落点(文件存在即视为实现完成;行为由测试保证)。 */
45
+ implementation: ['src/cost/ledger.ts', 'src/cost/budget.ts', 'src/cost/policy.ts', 'src/cost/audit.ts', 'worker/cost-hooks.ts'],
46
+ tests: ['test/cost-ledger.test.mjs', 'test/cost-budget.test.mjs', 'test/cost-policy.test.mjs', 'test/cost-audit.test.mjs', 'test/worker-cost-hooks.test.ts', 'test/worker-budget-guard.test.ts'],
47
+ }),
48
+ Object.freeze({
49
+ id: 'M2',
50
+ title: '模块层 A:A1 紧凑输出、A2 渐进纰漏(shadow→enforce 白名单)',
51
+ deliverable: '工具面 token 下降 + 归因',
52
+ dependsOn: ['M1'],
53
+ implementation: ['worker/tool-compact.ts', 'worker/tool-disclosure.ts', 'src/cost/attribution.ts'],
54
+ tests: ['test/worker-tool-compact.test.ts', 'test/worker-tool-disclosure.test.ts', 'test/cost-attribution.test.mjs', 'test/cost-disclosure.test.mjs'],
55
+ }),
56
+ Object.freeze({
57
+ id: 'M3',
58
+ title: '模块层 B:B1 EDG、B2 GDR(shadow 计算证书,不替换)',
59
+ deliverable: '命中率与「本可节省」报表',
60
+ dependsOn: ['M1'],
61
+ implementation: ['worker/edg.ts', 'worker/gdr.ts'],
62
+ tests: ['test/worker-edg.test.ts', 'test/worker-gdr.test.ts', 'test/worker-decision-spi.test.ts'],
63
+ }),
64
+ Object.freeze({
65
+ id: 'M4',
66
+ title: 'B3 CCS enforce(白名单)+ 自动回退',
67
+ deliverable: '真实替换 + 证书链 + 成功率对照',
68
+ dependsOn: ['M3'],
69
+ implementation: ['worker/ccs.ts'],
70
+ tests: ['test/worker-ccs.test.ts', 'test/cost-substitution.test.mjs', 'test/invariants.test.ts'],
71
+ }),
72
+ Object.freeze({
73
+ id: 'M5',
74
+ title: 'A3 批量(若宿主支持)、B4 RSI 离线流程、与瓮城回流/信息栏对接',
75
+ deliverable: '离线候选冻结清单 + 面板',
76
+ dependsOn: ['M1'],
77
+ implementation: ['worker/tool-batch.ts', 'src/cost/rsi.ts', 'scripts/rsi-offline.mjs', 'client.js'],
78
+ tests: ['test/worker-tool-batch.test.ts', 'test/cost-rsi.test.mjs'],
79
+ }),
80
+ ])
81
+
82
+ /**
83
+ * 评估里程碑。
84
+ *
85
+ * @param {{
86
+ * files?: string[], // 仓库里实际存在的文件(相对路径)
87
+ * guard?: {samples?: number, accuracy?: number}, // M3 的守卫准确率数据(来自 shadow 报告)
88
+ * forkExperiment?: {ok?: boolean}, // M4 的成功率对照(来自 gate 的 G5)
89
+ * fangRelay?: {available?: boolean, peer?: string, events?: number,
90
+ * sameProcess?: boolean, transport?: string, contractVersion?: string,
91
+ * reason?: string}, // M5 的回流对端证据(独立进程实测)
92
+ * }} evidence
93
+ */
94
+ export function evaluateMilestones(evidence = {}) {
95
+ const files = new Set(Array.isArray(evidence.files) ? evidence.files : [])
96
+ const rows = []
97
+ const done = new Set()
98
+ let relayEvidence = null
99
+
100
+ for (const milestone of MILESTONES) {
101
+ const missingImplementation = milestone.implementation.filter((file) => !files.has(file))
102
+ const missingTests = milestone.tests.filter((file) => !files.has(file))
103
+ const dependenciesMet = milestone.dependsOn.every((id) => done.has(id))
104
+ const implemented = missingImplementation.length === 0 && missingTests.length === 0
105
+
106
+ let effect = 'unmeasured'
107
+ const reasons = []
108
+ if (!implemented) {
109
+ reasons.push(`missing:${[...missingImplementation, ...missingTests].join('|')}`)
110
+ }
111
+ if (!dependenciesMet) {
112
+ reasons.push(`dependency-unmet:${milestone.dependsOn.filter((id) => !done.has(id)).join('|')}`)
113
+ }
114
+
115
+ // 各阶段的效果证据要求
116
+ if (implemented && dependenciesMet) {
117
+ if (milestone.id === 'M1') {
118
+ effect = 'verified' // 系统层效果由测试直接证明(记账、预算闸)
119
+ } else if (milestone.id === 'M2') {
120
+ effect = evidence.attributionObserved === true ? 'verified' : 'unmeasured'
121
+ if (effect === 'unmeasured') reasons.push('effect-needs-real-token-drop')
122
+ } else if (milestone.id === 'M3') {
123
+ const samples = Math.max(0, Number(evidence?.guard?.samples ?? 0) || 0)
124
+ const accuracy = Number(evidence?.guard?.accuracy ?? 0) || 0
125
+ if (samples >= M3_MIN_SAMPLES && accuracy >= M3_MIN_ACCURACY) effect = 'verified'
126
+ else reasons.push(`effect-needs-guard-accuracy(samples=${samples}, accuracy=${accuracy})`)
127
+ } else if (milestone.id === 'M4') {
128
+ if (evidence?.forkExperiment?.ok === true) effect = 'verified'
129
+ else reasons.push('effect-needs-fork-success-comparison')
130
+ } else if (milestone.id === 'M5') {
131
+ // 回流必须有**对端回执**才算达标:可用 + 非自环 + 对端自报身份在白名单 + 至少 3 条事件。
132
+ const relay = evidence?.fangRelay && typeof evidence.fangRelay === 'object' ? evidence.fangRelay : null
133
+ const available = relay?.available === true
134
+ const peer = String(relay?.peer ?? '')
135
+ const events = Math.max(0, Number(relay?.events ?? 0) || 0)
136
+ if (!available) {
137
+ reasons.push(`fang-relay-unavailable:${String(relay?.reason ?? 'not-provided')}`)
138
+ } else if (relay?.sameProcess === true) {
139
+ reasons.push('fang-relay-self-loop')
140
+ } else if (!M5_RELAY_PEERS.includes(peer)) {
141
+ reasons.push(`fang-relay-peer-invalid:${peer || 'missing'}`)
142
+ } else if (events < M5_MIN_RELAY_EVENTS) {
143
+ reasons.push(`fang-relay-no-receipts(events=${events})`)
144
+ } else {
145
+ effect = 'verified'
146
+ }
147
+ relayEvidence = Object.freeze({
148
+ available,
149
+ peer: peer || null,
150
+ events,
151
+ sameProcess: relay?.sameProcess === true,
152
+ transport: relay?.transport ?? null,
153
+ contractVersion: relay?.contractVersion ?? relay?.peerContractVersion ?? null,
154
+ })
155
+ }
156
+ }
157
+
158
+ const status = !implemented
159
+ ? 'not-implemented'
160
+ : !dependenciesMet
161
+ ? 'blocked-by-dependency'
162
+ : effect === 'verified' ? 'done' : 'implemented-effect-unmeasured'
163
+
164
+ rows.push(Object.freeze({
165
+ id: milestone.id,
166
+ title: milestone.title,
167
+ deliverable: milestone.deliverable,
168
+ dependsOn: milestone.dependsOn,
169
+ implemented,
170
+ dependenciesMet,
171
+ effect,
172
+ status,
173
+ reasons,
174
+ /** M5:回流对端证据(peer 自报 + 事件数 + 是否自环);其它阶段为 null。 */
175
+ relay: relayEvidence,
176
+ /** 剩余缺口说明(不隐藏)。 */
177
+ notes: milestone.id === 'M5' && relayEvidence?.peer === 'contract-server'
178
+ ? ['peer=contract-server:契约对端独立进程实测,dsh-fang-bundle 采用 contract 1.1 后应变为 peer=dsh-fang-bundle']
179
+ : [],
180
+ }))
181
+ if (status === 'done') done.add(milestone.id)
182
+ }
183
+
184
+ // 依赖顺序的静态校验:M1 必须最先;M4 只依赖 M3。
185
+ const orderProblems = []
186
+ const m1 = MILESTONES.find((item) => item.id === 'M1')
187
+ if (!m1 || m1.dependsOn.length !== 0) orderProblems.push('M1-must-be-first')
188
+ const m4 = MILESTONES.find((item) => item.id === 'M4')
189
+ if (!m4 || !m4.dependsOn.includes('M3')) orderProblems.push('M4-must-depend-on-M3')
190
+
191
+ const allImplemented = rows.every((row) => row.implemented)
192
+ const allDone = rows.every((row) => row.status === 'done')
193
+ return Object.freeze({
194
+ ok: orderProblems.length === 0 && allDone,
195
+ implementedAll: allImplemented,
196
+ doneAll: allDone,
197
+ orderProblems,
198
+ rows: Object.freeze(rows),
199
+ /** 摘要在面板/报表用:实现了多少、真达标多少。 */
200
+ summary: Object.freeze({
201
+ implemented: rows.filter((row) => row.implemented).length,
202
+ verified: rows.filter((row) => row.status === 'done').length,
203
+ total: rows.length,
204
+ }),
205
+ })
206
+ }
@@ -0,0 +1,13 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (c) 2026 dsh-Master contributors — see LICENSE-CORE.
3
+ // @ts-nocheck
4
+ /** 3.3.0 §11:`src/cost/modules/` 入口(模块清单;可执行实现在 worker/,见 manifests.ts 说明)。 */
5
+ export {
6
+ MODULE_KIND,
7
+ MODULE_LAYER,
8
+ MODULE_MODES,
9
+ MODULE_MANIFESTS,
10
+ MODULE_IDS,
11
+ manifestOf,
12
+ validateManifests,
13
+ } from './manifests.ts'
@@ -0,0 +1,129 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (c) 2026 dsh-Master contributors — see LICENSE-CORE.
3
+ // @ts-nocheck — host JS→TS 迁移阶段:运行时行为优先,类型注解逐步补齐(worker/ 仍严格检查)。
4
+
5
+ /**
6
+ * 3.3.0 §11:模块清单(`src/cost/modules/`)。
7
+ *
8
+ * **进程边界说明(重要)**:A/B 类模块的可执行实现运行在 **fork worker 进程**里,
9
+ * 因此实体代码在 `worker/`(`tool-compact.ts`、`tool-disclosure.ts`、`tool-batch.ts`、
10
+ * `edg.ts`、`gdr.ts`、`ccs.ts`)。本目录提供的是**宿主侧清单**:模块 id、层级、默认档、
11
+ * 开关环境变量、实现文件与文档锚点 —— 供编排器、面板与门禁统一引用,
12
+ * 避免"宿主侧再抄一份实现"导致两套语义。
13
+ */
14
+
15
+ export const MODULE_KIND = Object.freeze({ tool: 'tool', decision: 'decision' })
16
+ export const MODULE_LAYER = Object.freeze({ a: 'a', b: 'b' })
17
+ export const MODULE_MODES = Object.freeze(['off', 'shadow', 'enforce'])
18
+
19
+ /** 7 个模块(A1–A3 / B1–B4)的宿主侧清单。 */
20
+ export const MODULE_MANIFESTS = Object.freeze([
21
+ Object.freeze({
22
+ id: 'a1-compact',
23
+ kind: 'tool',
24
+ layer: 'a',
25
+ name: '工具输出紧凑化',
26
+ implementation: 'worker/tool-compact.ts',
27
+ defaultMode: 'shadow',
28
+ envFlag: 'DSH_MASTER_COMPACT_MODE',
29
+ surfaces: ['worker'],
30
+ changesObservableSemantics: false,
31
+ docs: 'docs/DESIGN-v3.3.0-cost-optimization.md#a1-tool-compact工具输出紧凑化',
32
+ }),
33
+ Object.freeze({
34
+ id: 'a2-disclosure',
35
+ kind: 'tool',
36
+ layer: 'a',
37
+ name: '渐进式纰漏',
38
+ implementation: 'worker/tool-disclosure.ts',
39
+ defaultMode: 'off',
40
+ envFlag: 'DSH_MASTER_DISCLOSURE',
41
+ surfaces: ['worker'],
42
+ changesObservableSemantics: false,
43
+ docs: 'docs/DESIGN-v3.3.0-cost-optimization.md#a2-tool-disclosure渐进式纰漏',
44
+ }),
45
+ Object.freeze({
46
+ id: 'a3-batch',
47
+ kind: 'tool',
48
+ layer: 'a',
49
+ name: '批量调用引导',
50
+ implementation: 'worker/tool-batch.ts',
51
+ defaultMode: 'shadow',
52
+ envFlag: 'DSH_MASTER_BATCH',
53
+ surfaces: ['worker'],
54
+ changesObservableSemantics: false,
55
+ docs: 'docs/DESIGN-v3.3.0-cost-optimization.md#a3-tool-batch批量调用',
56
+ }),
57
+ Object.freeze({
58
+ id: 'b1-edg',
59
+ kind: 'decision',
60
+ layer: 'b',
61
+ name: '可执行决策图',
62
+ implementation: 'worker/edg.ts',
63
+ defaultMode: 'off',
64
+ envFlag: 'DSH_MASTER_EDG',
65
+ surfaces: ['worker'],
66
+ changesObservableSemantics: false,
67
+ docs: 'docs/DESIGN-v3.3.0-cost-optimization.md#b1-edg可执行决策图',
68
+ }),
69
+ Object.freeze({
70
+ id: 'b2-gdr',
71
+ kind: 'decision',
72
+ layer: 'b',
73
+ name: '回执守卫',
74
+ implementation: 'worker/gdr.ts',
75
+ defaultMode: 'off',
76
+ envFlag: 'DSH_MASTER_GDR',
77
+ surfaces: ['worker'],
78
+ changesObservableSemantics: false,
79
+ docs: 'docs/DESIGN-v3.3.0-cost-optimization.md#b2-gdr回执守卫',
80
+ }),
81
+ Object.freeze({
82
+ id: 'b3-ccs',
83
+ kind: 'decision',
84
+ layer: 'b',
85
+ name: '认证回调替换',
86
+ implementation: 'worker/ccs.ts',
87
+ defaultMode: 'off',
88
+ envFlag: 'DSH_MASTER_CCS',
89
+ surfaces: ['worker'],
90
+ /** 替换会**跳过模型推理**:只在 enforce + 白名单下发生。 */
91
+ changesObservableSemantics: false,
92
+ docs: 'docs/DESIGN-v3.3.0-cost-optimization.md#b3-ccs认证回调替换',
93
+ }),
94
+ Object.freeze({
95
+ id: 'b4-rsi',
96
+ kind: 'decision',
97
+ layer: 'b',
98
+ name: '离线自改进',
99
+ implementation: 'src/cost/rsi.ts',
100
+ defaultMode: 'off',
101
+ envFlag: null,
102
+ surfaces: ['host'],
103
+ changesObservableSemantics: false,
104
+ docs: 'docs/DESIGN-v3.3.0-cost-optimization.md#b4-rsi离线自改进',
105
+ }),
106
+ ])
107
+
108
+ export const MODULE_IDS = Object.freeze(MODULE_MANIFESTS.map((manifest) => manifest.id))
109
+
110
+ export function manifestOf(id) {
111
+ return MODULE_MANIFESTS.find((manifest) => manifest.id === String(id)) ?? null
112
+ }
113
+
114
+ /** 校验清单自身:7 个模块、id 唯一、默认档合法。 */
115
+ export function validateManifests(manifests = MODULE_MANIFESTS) {
116
+ const problems = []
117
+ const ids = new Set()
118
+ for (const manifest of manifests) {
119
+ if (!manifest?.id) problems.push('missing-id')
120
+ if (ids.has(manifest?.id)) problems.push(`duplicate-id:${manifest.id}`)
121
+ ids.add(manifest?.id)
122
+ if (!MODULE_MODES.includes(manifest?.defaultMode)) problems.push(`bad-default-mode:${manifest?.id}`)
123
+ if (!MODULE_KIND[manifest?.kind]) problems.push(`bad-kind:${manifest?.id}`)
124
+ if (!MODULE_LAYER[manifest?.layer]) problems.push(`bad-layer:${manifest?.id}`)
125
+ if (manifest?.changesObservableSemantics !== false) problems.push(`observable-semantics:${manifest?.id}`)
126
+ }
127
+ if (manifests.length !== 7) problems.push(`module-count:${manifests.length}!=7`)
128
+ return { ok: problems.length === 0, problems }
129
+ }