clearai-dsh 0.1.6 → 0.2.0

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.
@@ -0,0 +1,202 @@
1
+ /**
2
+ * ClearAI 的**宿主不变量**(`@deepseek-ai/dsh-invariants` 的伴生件)。
3
+ *
4
+ * 这一份与上一版的根本差别:它**不再自己折**。上一版为了"落账之前判"维护了一套
5
+ * plans/steps/forks/branches/… 的索引,上岗第一小时就因为与主投影**对不上**被修了两次
6
+ * ——那不是业务有错,是**第二套解释器**必然漂移。现在状态直接用生产折法
7
+ * (`fold.js` 的 `applyEvent`)推进,契约只在生产状态与候选变更上判:
8
+ *
9
+ * · 状态只有一个权威(投影),这里只是把它**再跑一遍**——同一份代码,不可能漂;
10
+ * · 本文件独有的只剩**时机**(落账之前)与**判据**(五条契约);
11
+ * · 唯一的私有累积是 admitted 步骤集合(折法刻意把 `admission/checked` 留在台账层,
12
+ * 投影里没有它——那不是解释形状,是一行 `add(step)`)。
13
+ *
14
+ * 三条纪律:
15
+ * · **只判契约,不判判断**:这里只问"这条事实能不能和已经落下的事实同时成立"。
16
+ * · **已经发生的失败必须允许入账**:评估者崩溃、落盘失败是事实,拦下它们
17
+ * 就是"只允许好看的事实进入账本"——那本身是不实陈述。
18
+ * · **诊断面不许假装承重**:没挂这个服务的部署里它是零成本的一行判断。
19
+ */
20
+ import { applyEvent, emptyState } from './fold.js'
21
+
22
+ /** 归属包名:违反时报出去的是这个名字(宿主据此过滤与归属)。 */
23
+ const PACKAGE_NAME = 'clearai-dsh'
24
+ /** Cordis 伴生插件名。 */
25
+ const name = 'clearai-invariant'
26
+ /** 服务由宿主组合提供;没有它时这一行进等待,不报错。 */
27
+ const inject = ['invariants']
28
+
29
+ /** 一条 fact 里的 mutations:两种装载点(插件消息的段、工具结果的 meta)。 */
30
+ function mutationsOf(event) {
31
+ const out = []
32
+ const source = event?.data?.source
33
+ if (source !== null && typeof source === 'object' && Array.isArray(source.sections)) {
34
+ const section = source.sections.find((item) => item?.name === 'clearai/mutations')
35
+ if (section !== undefined && typeof section.text === 'string') {
36
+ try {
37
+ const payload = JSON.parse(section.text)
38
+ if (Array.isArray(payload?.mutations)) out.push(...payload.mutations)
39
+ } catch {
40
+ /* 坏 payload 由折法决定怎么办;这里不猜。 */
41
+ }
42
+ }
43
+ }
44
+ const meta = event?.data?.meta
45
+ if (meta !== null && meta !== undefined && typeof meta === 'object' && meta.kind === 'clearai') {
46
+ if (Array.isArray(meta.mutations)) out.push(...meta.mutations)
47
+ else if (meta.mutation !== undefined) out.push(meta.mutation)
48
+ }
49
+ return out
50
+ }
51
+
52
+ /**
53
+ * **伪步骤**:目标轴(`goal:<id>`)与世界线轴(`<forkId>:<branchId>`)的裁决也带 `step`,
54
+ * 但它们本来就不在 `plan/created` 里登记。计划步骤 id 的取字纪律是字母/数字/下划线/短横
55
+ * (见内核 `STEP_SCHEMA`),两条伪轴一律带冒号。
56
+ */
57
+ const isPseudoStep = (step) => String(step).includes(':')
58
+
59
+ const LEVEL_RANK = { L0: 0, L1: 1, L2: 2, L3: 3, L4: 4 }
60
+
61
+ /** 在**生产状态**里找一个计划步骤(裸 id;这是 `view.stepIndex` 的同一份事实)。 */
62
+ function findStep(state, step) {
63
+ if (step === null || step === undefined || isPseudoStep(step)) return undefined
64
+ for (const plan of state?.plans ?? []) {
65
+ const found = (plan.steps ?? []).find((item) => String(item.id) === String(step))
66
+ if (found !== undefined) return found
67
+ }
68
+ return undefined
69
+ }
70
+
71
+ /**
72
+ * 五条契约,逐条判。输入是**生产折法**算出的 `before`(落这条之前的状态)、候选事件的
73
+ * 变更、与 admitted 累积。违反就 `fail(消息)`;判据查不到的那一半**安静地不判**
74
+ * (查不到 ≠ 不存在——判据比事实窄就是误伤,这一条是被真跑教出来的)。
75
+ */
76
+ function judge(before, mutations, admittedIn, fail) {
77
+ const plans = Array.isArray(before?.plans) ? before.plans : []
78
+ const hasAnyStep = plans.some((plan) => Array.isArray(plan.steps) && plan.steps.length > 0)
79
+ /**
80
+ * **本批累积**:一次交付的变更里,`admission/checked` 与 `step/advanced`、
81
+ * `audit/dispatched` 与 `audit/settled` 常常**同一批**落(内核就是这么发的——
82
+ * 派遣先落、结论在同一笔里回填)。判据只看"之前"就会把这批自己拦下,
83
+ * 所以这里把两条累积随循环一起长:顺序仍然要求(先派遣后结算),只是允许在同批。
84
+ */
85
+ const admitted = new Set(admittedIn ?? [])
86
+ const auditsSeen = new Set((Array.isArray(before?.audits) ? before.audits : []).map((item) => String(item.id)))
87
+ const scoutsSeen = new Set((Array.isArray(before?.scouts) ? before.scouts : []).map((item) => String(item.id)))
88
+ for (const mutation of mutations) {
89
+ if (mutation === null || typeof mutation !== 'object' || typeof mutation.t !== 'string') continue
90
+ const t = mutation.t
91
+ const step = mutation.step === undefined || mutation.step === null ? null : String(mutation.step)
92
+
93
+ // ── 契约①:引用完整性(查生产状态,不是另一份索引) ────────────────────
94
+ if (step !== null && !isPseudoStep(step) && hasAnyStep && ['evidence/recorded', 'observation/recorded', 'step/advanced', 'scout/dispatched'].includes(t)) {
95
+ if (findStep(before, step) === undefined) fail(`引用完整性:${t} 指向不存在的步骤 ${step}——事实不能指向还没登记的东西`)
96
+ }
97
+
98
+ // ── 契约②:准入先于推进(同批允许先准入后推进;跨批看 admitted 累积) ────────
99
+ if (t === 'admission/checked' && step !== null) admitted.add(step)
100
+ if (t === 'step/advanced' && step !== null && !isPseudoStep(step) && !admitted.has(step)) {
101
+ fail(`准入先于推进:step/advanced(${step}) 之前没有该步的 admission/checked`)
102
+ }
103
+
104
+ // ── 契约③:结算必有派遣(生产状态 + 本批已见的派遣) ─────────────────────
105
+ if (t === 'audit/settled' && !auditsSeen.has(String(mutation.id))) {
106
+ fail(`结算必有派遣:audit/settled(${String(mutation.id)}) 之前没有 audit/dispatched`)
107
+ }
108
+ if (t === 'scout/settled' && !scoutsSeen.has(String(mutation.id))) {
109
+ fail(`结算必有派遣:scout/settled(${String(mutation.id)}) 之前没有 scout/dispatched`)
110
+ }
111
+ if (t === 'audit/dispatched' && mutation.id !== undefined && mutation.id !== null) auditsSeen.add(String(mutation.id))
112
+ if (t === 'scout/dispatched' && mutation.id !== undefined && mutation.id !== null) scoutsSeen.add(String(mutation.id))
113
+
114
+ // ── 契约④:升格有据(目标登记的门槛 vs 已落账的支持证据) ─────────────────
115
+ if (t === 'fact/promoted') {
116
+ const goal = before?.goal ?? null
117
+ const need = LEVEL_RANK[String(goal?.promote_at_level ?? '')]
118
+ if (goal !== null && need !== undefined) {
119
+ /** 顺着 假设(claim 匹配)→ 步骤(tests.hypothesis)→ 证据(support) 这条边走。 */
120
+ const hypothesis = (Array.isArray(before?.hypotheses) ? before.hypotheses : []).find((item) => String(item.claim ?? '') === String(mutation.text ?? ''))
121
+ const hypId = hypothesis === undefined ? null : String(hypothesis.id)
122
+ const stepIds = new Set(
123
+ hypId === null ? [] : plans.flatMap((plan) => (plan.steps ?? []).filter((s) => String(s?.tests?.hypothesis ?? '') === hypId).map((s) => String(s.id))),
124
+ )
125
+ const support = (Array.isArray(before?.evidence) ? before.evidence : []).filter((item) => String(item.verdict) === 'support' && (hypId === null || stepIds.has(String(item.step))))
126
+ const best = support.reduce((max, item) => Math.max(max, LEVEL_RANK[String(item.level)] ?? -1), -1)
127
+ if (best < need) fail(`升格有据:fact/promoted 的最高支持等级(${best === -1 ? '无' : `L${best}`})没到目标登记的 promote_at_level(${String(goal.promote_at_level)})`)
128
+ }
129
+ }
130
+
131
+ // ── 契约⑤:事实棘轮(生产折法对降级是**静默忽略**的;这里是唯一的检测点) ────
132
+ if (t === 'step/advanced' && step !== null) {
133
+ const found = findStep(before, step)
134
+ if (found !== undefined && String(found.status) === 'void') {
135
+ fail(`事实棘轮:步骤(${step})已作废,不能再被推进——已经落下的事实不能被改写`)
136
+ }
137
+ }
138
+ }
139
+ }
140
+
141
+ /**
142
+ * 安装件:宿主把 `(childCtx, fail)` 交给我们。
143
+ * 状态用**生产折法**推进(`applyEvent`);候选先叠在副本上,判不过就不污染。
144
+ */
145
+ const install = Object.assign(
146
+ (ctx, fail) => {
147
+ const states = new WeakMap()
148
+ const admitted = new WeakMap()
149
+ const staged = new WeakMap()
150
+ const seed = (session) => {
151
+ let state = emptyState()
152
+ const adm = new Set()
153
+ for (const event of session.snapshotEvents()) {
154
+ state = applyEvent(state, event)
155
+ for (const mutation of mutationsOf(event)) {
156
+ if (mutation?.t === 'admission/checked' && mutation.step !== undefined && mutation.step !== null) adm.add(String(mutation.step))
157
+ }
158
+ }
159
+ states.set(session, state)
160
+ admitted.set(session, adm)
161
+ return state
162
+ }
163
+ ctx.sessions.list().forEach(seed)
164
+ ctx.on('session/created', (session) => seed(session), { global: true })
165
+ ctx.on(
166
+ 'internal/dispatch',
167
+ (_mode, eventName, args) => {
168
+ if (eventName !== 'session/event') return
169
+ const [session, event] = args
170
+ const mutations = mutationsOf(event)
171
+ if (mutations.length === 0) return
172
+ const before = states.get(session) ?? emptyState()
173
+ judge(before, mutations, admitted.get(session) ?? new Set(), fail)
174
+ // 判过了才推进:候选状态与累积都改在副本上。
175
+ const candidate = applyEvent(before, event)
176
+ const adm = new Set(admitted.get(session) ?? new Set())
177
+ for (const mutation of mutations) {
178
+ if (mutation?.t === 'admission/checked' && mutation.step !== undefined && mutation.step !== null) adm.add(String(mutation.step))
179
+ }
180
+ staged.set(event, { session, state: candidate, admitted: adm })
181
+ },
182
+ { global: true },
183
+ )
184
+ ctx.on(
185
+ 'session/event',
186
+ (session, event) => {
187
+ const candidate = staged.get(event)
188
+ if (candidate === undefined) return
189
+ staged.delete(event)
190
+ states.set(session, candidate.state)
191
+ admitted.set(session, candidate.admitted)
192
+ },
193
+ { global: true },
194
+ )
195
+ },
196
+ { inject: ['sessions'] },
197
+ )
198
+
199
+ /** 把这一包的不变量注册进宿主(`apply` 是伴生插件的入口)。 */
200
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
201
+
202
+ export { PACKAGE_NAME, apply, inject, install, judge, mutationsOf, name }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clearai-dsh",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "description": "ClearAI: The Epistemic Loop, native to DSH.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -14,7 +14,8 @@
14
14
  ".": "./lib/host.js",
15
15
  "./client": "./lib/client.js",
16
16
  "./cordis.patch.yml": "./cordis.patch.yml",
17
- "./package.json": "./package.json"
17
+ "./package.json": "./package.json",
18
+ "./invariant": "./lib/invariant.js"
18
19
  },
19
20
  "bin": {
20
21
  "clearai-dsh": "bin/clearai.mjs"
@@ -111,7 +111,7 @@
111
111
 
112
112
  # ── 认识论内核(本预设的灵魂) ───────────────────────────────────────────────
113
113
 
114
- # 22 件意图工具(目标 2 + 计划 8 + 世界线 6 + 侦察 2 + 外脑 2 + 账本 2,见 `MECHANISM_TOOLS`)
114
+ # 29 件意图工具(目标 2 + 计划 8 + 世界线 6 + 侦察 2 + 外脑 2 + 账本 2 + 领域语言 7,见 `MECHANISM_TOOLS`)
115
115
  # + 一个 guard + 每回合派生的运行态卡 + 只增不删的台账 + 面板数据路由。
116
116
  # 逐条对照 docs/loop-philosophy.md 的五条哲学,见 ./plugins/clearai-kernel.js 的文件头。
117
117
  #
@@ -188,8 +188,8 @@
188
188
  # 当场抛错——未知机制 / 未知工具 / 未知段 / 已关机制却仍列着它的工具,四种错法都在装配期炸,
189
189
  # 而不是静默少装一件工具、等某一轮才发现。
190
190
  #
191
- # 这里只写**机制开关**;tools / sections 缺省 = 目录全量(22 件意图工具、23 段提示词定义、
192
- # 同一时刻 22 段在场——澄清协议那一段由 autonomy 在两套措辞里收敛)。
191
+ # 这里只写**机制开关**;tools / sections 缺省 = 目录全量(29 件意图工具、24 段提示词定义、
192
+ # 同一时刻 23 段在场——澄清协议那一段由 autonomy 在两套措辞里收敛)。
193
193
  # 这些数字不靠人眼维持:`node tools/verify-truth-table.mjs` 会拿代码算出来的数核对它们。
194
194
  # 要裁剪就把 tools 或 sections 显式写出来:
195
195
  # tools: [SetGoal, CreatePlan, AdvancePlan, ...] # 名字必须都在工具目录里