@a9i5k4/dsh-auto-memory 3.1.5 → 3.1.6
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/docs/CONTINUITY-FLOW.md +6 -6
- package/docs/HANDBOOK.md +54 -53
- package/docs/M-CM-PLAN.md +1 -1
- package/docs/M-CM-STATE.md +1 -1
- package/docs/M-CM7-HANDOFF-LAYERED-RETRIEVAL.md +1 -1
- package/docs/M3B-CONTRACT.md +7 -7
- package/docs/M7-AUTONOMOUS-STATE.md +1 -1
- package/docs/M7-TASKSET-DISPATCH.md +1 -1
- package/docs/PROMPT-PACK-LAYERED-RECALL.md +3 -3
- package/docs/PROMPT-SET-STRICT.md +3 -3
- package/docs/RELEASE-GO-NOGO.md +1 -1
- package/docs/STATUS-BOARD.md +1 -1
- package/docs/USER-GUIDE.en.md +21 -21
- package/docs/USER-GUIDE.zh-CN.md +21 -21
- package/docs/WHITEPAPER.md +1 -1
- package/docs/prompts/FIX-AGENT-P12-FULL-REGRESSION.md +1 -1
- package/docs/prompts/LIVE-VERIFY-ZCODE.md +2 -2
- package/docs/prompts/P2-semantic-recall.md +2 -2
- package/docs/prompts/P7-write-fix.md +1 -1
- package/docs/prompts/ZCODE-DROPIN.md +1 -1
- package/lib/acceptance.js +6 -6
- package/lib/activation-host.js +15 -14
- package/lib/activation-inbox-state.js +4 -4
- package/lib/activation-inbox.js +44 -44
- package/lib/board-mode.js +3 -3
- package/lib/client.js +193 -34
- package/lib/config-io.js +12 -12
- package/lib/context-bridge.js +22 -22
- package/lib/context-host.js +24 -23
- package/lib/datadir.js +95 -0
- package/lib/degrade.js +21 -21
- package/lib/dsh-home.js +8 -8
- package/lib/engine-identity.js +12 -12
- package/lib/engine-switch.js +3 -3
- package/lib/episodic-store.js +13 -13
- package/lib/evidence-agg.js +4 -4
- package/lib/evidence-store.js +12 -12
- package/lib/fact-store.js +47 -47
- package/lib/handoff-anchor.js +4 -4
- package/lib/hub-io.js +356 -3
- package/lib/index-sync.js +5 -5
- package/lib/index.js +381 -236
- package/lib/intent-clean-safe.js +3 -3
- package/lib/intent-clean.js +1 -1
- package/lib/l0-extract.js +15 -15
- package/lib/l0-index-sync.js +8 -8
- package/lib/l0-index.js +11 -11
- package/lib/ledger-criteria.js +8 -8
- package/lib/m4-corpus.js +1 -1
- package/lib/m7-index-sync-host.js +2 -2
- package/lib/m7-wire.js +24 -24
- package/lib/memory-anchor.js +1 -1
- package/lib/memory-envelope.js +15 -15
- package/lib/memory-hub.js +95 -50
- package/lib/memory-importance.js +7 -7
- package/lib/memory-mutation.js +5 -5
- package/lib/note-status-apply.js +2 -2
- package/lib/note-status.js +7 -7
- package/lib/policies/activation_policy_v2.json +2 -2
- package/lib/policies/recall_intent_lr_v1.json +1 -1
- package/lib/procedure-observation.js +2 -2
- package/lib/procedure-store.js +117 -28
- package/lib/python-setup.js +6 -5
- package/lib/python-sidecar-client.js +26 -26
- package/lib/recall-fusion.js +12 -12
- package/lib/recall-stats.js +36 -1
- package/lib/rerank-host.js +11 -11
- package/lib/rules-edit.js +9 -9
- package/lib/rules-layer.js +26 -26
- package/lib/semantic-decide.js +7 -7
- package/lib/semantic-js.js +13 -13
- package/lib/shadow-host.js +7 -6
- package/lib/shadow-retrieval.js +40 -40
- package/lib/skill-export-host.js +7 -7
- package/lib/skill-export.js +6 -6
- package/lib/state-commit.js +10 -10
- package/lib/storage-manage.js +6 -6
- package/lib/tier-layer-inject.js +28 -28
- package/lib/tier0-catalog.js +4 -4
- package/lib/wb-contract.js +45 -45
- package/lib/wb-sidecar.js +11 -11
- package/package.json +3 -4
- package/python/m7_activation_features_v2.py +7 -7
- package/python/m7_embedding_v1.py +12 -12
- package/python/policies/activation_policy_v2.json +2 -2
- package/python/policies/decision-record-activation-v2-delta-exp-override-20260824.json +1 -1
- package/python/policies/decision-record-reasoning-kind-admission-20260826.json +2 -2
- package/python/policies/decision-record-stale-gate-per-candidate-20260825.json +2 -2
- package/python/policies/recall_intent_lr_v1.json +1 -1
- package/python/verify_policy_artifact.py +3 -3
- package/python/worker_semantic_v1.py +18 -18
- package/python/worker_v1.py +18 -18
package/lib/intent-clean-safe.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* ★ 本次改动的根因(issue #30 / ③ Hermes 遗留 / H-3):
|
|
7
7
|
* 旧实现在 `:17` 用**一条字面量行首白名单** `/^(?:current runtime context\.|current dsh file policy:)/i`
|
|
8
|
-
* 识别信封 ⇒ 只能挡住两个当期已知形态。真机取证(`~/.dsh/memory/hub/procedures.json`,
|
|
8
|
+
* 识别信封 ⇒ 只能挡住两个当期已知形态。真机取证(`~/.dsh/memory/hub-pre/procedures.json`,
|
|
9
9
|
* 10 条 procedure 里 7 条 observed、其中 4 条 title 就是运行时信封)实测漏网 4 类:
|
|
10
10
|
* · `Approval prompts are disabled in this session: …`
|
|
11
11
|
* · `{"path":"D:\\…`(工具回包的 JSON 转储)
|
|
@@ -55,7 +55,7 @@ const HARNESS_HEAD_ZH_RE = /^(?:当前|批准|沙箱|策略|运行时|会话|权
|
|
|
55
55
|
* **发现过程**:⑨ 探针逐条检验真机 `procedures.json` 的 11 条 title,
|
|
56
56
|
* 发现**时间戳最新的一条**(2026-09-19)title 是 `[Retrieved memory refe` ——
|
|
57
57
|
* 它正是**本插件自己注入的「记忆召回」块标记**
|
|
58
|
-
* (`activation-inbox.js:58` 的 `
|
|
58
|
+
* (`activation-inbox.js:58` 的 `TAIL_MARKER_LINE_PRE_V1`)。
|
|
59
59
|
*
|
|
60
60
|
* ⇒ **自污染闭环**:插件注入召回块 → 召回块随 userText 进 episode →
|
|
61
61
|
* `crossFeed()` 把它当成技能 title ⇒ 又回到「技能审批队列」。
|
|
@@ -246,7 +246,7 @@ export function stripRuntimeIntentPre(text) {
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
/** 导出判据本身,供套件直接断言(避免只能通过整串行为间接验证)。 */
|
|
249
|
-
export const
|
|
249
|
+
export const RUNTIME_ENVELOPE_PRE_V1 = Object.freeze({
|
|
250
250
|
EXACT_ENVELOPE_RE,
|
|
251
251
|
HARNESS_HEAD_RE,
|
|
252
252
|
HARNESS_HEAD_ZH_RE,
|
package/lib/intent-clean.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* M8 采集侧 intent 清洗(2026-08-30 P1,docs/HANDOFF-M8-M9-M10.md §2 P1)。
|
|
3
3
|
*
|
|
4
4
|
* 背景:episode 的 intent 来自 consolidateTurn 取到的「本轮最后一条 user 文本」,而该文本
|
|
5
|
-
* 在真实运行时常被三种形态污染(已实录于 ~/.dsh/memory/hub/episodes.json):
|
|
5
|
+
* 在真实运行时常被三种形态污染(已实录于 ~/.dsh/memory/hub-pre/episodes.json):
|
|
6
6
|
* ① harness 注入的上下文快照 —— 以 "Current runtime context. This snapshot supersedes…" 开头
|
|
7
7
|
* ② 工具回包 —— role 也是 user,但 eventType='tool/result',正文是 JSON 转储
|
|
8
8
|
* ③ 行号引用文本 —— 同属工具回包("436: ## 2026-08-25\n437: …")
|
package/lib/l0-extract.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* L0 抽取纯核心(
|
|
2
|
+
* L0 抽取纯核心(l0_extract_pre_v1)—— 分层语义唤回的地基。
|
|
3
3
|
*
|
|
4
4
|
* 2026-09-08 建立。目的:为每条记忆生成廉价摘要(L0),使检索可先在小空间
|
|
5
5
|
* 收敛候选,再按 id 下钻原文,从而把 token 开销与索引构建成本降约一个数量级
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* 空串/空数组),不抛异常。所有新增文本 UTF-8 无 BOM。
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
-
export const L0_EXTRACT_VERSION = '
|
|
32
|
+
export const L0_EXTRACT_VERSION = 'l0_extract_pre_v1'
|
|
33
33
|
|
|
34
34
|
/** 锚点:`<!-- memory:mem_<32hex> -->`(允许空白浮动)。 */
|
|
35
35
|
const MEM_ANCHOR_RE = /<!--\s*memory:(mem_[0-9a-f]{32})\s*-->/g
|
|
@@ -103,7 +103,7 @@ export const L0_STATUSES = Object.freeze(['current', 'superseded', 'retracted'])
|
|
|
103
103
|
export const L0_DEFAULT_LAYER = 'log'
|
|
104
104
|
|
|
105
105
|
/** 层级契约版本(与 L0_EXTRACT_VERSION 分开:后者是抽取算法版本,已有断言锁定,不动)。 */
|
|
106
|
-
export const L0_LAYER_VERSION = '
|
|
106
|
+
export const L0_LAYER_VERSION = 'l0_layer_pre_v1'
|
|
107
107
|
|
|
108
108
|
const clean = (s) => String(s == null ? '' : s).replace(/\u0000/g, '').trim()
|
|
109
109
|
|
|
@@ -157,15 +157,15 @@ export function classifyLayerPre(source) {
|
|
|
157
157
|
|
|
158
158
|
/** R4-B(2026-09-18):分层**呈现**用的层序(最高优先在前)。
|
|
159
159
|
*
|
|
160
|
-
* 与 `recall-fusion.js:
|
|
160
|
+
* 与 `recall-fusion.js:FUSION_LAYER_ORDER_PRE_V1` / `tier-layer-inject.js:TIER_LAYER_ORDER_PRE_V1`
|
|
161
161
|
* **同序但独立声明** —— 本仓既有约定:跨模块不共享同一常量对象,避免一处改动静默改变另一处语义;
|
|
162
162
|
* 三者相等由断言锁定(见 smoke-test-r4-recall-layers-pre.mjs)。 */
|
|
163
|
-
export const
|
|
163
|
+
export const L0_LAYER_DISPLAY_ORDER_PRE_V1 = Object.freeze(['project', 'whiteboard', 'user', 'reflection', 'log'])
|
|
164
164
|
|
|
165
165
|
/** R4-B:层 → 呈现标题。措辞刻意让「结论」与「流水」一眼可分 —— 这正是检索区分度问题的靶心:
|
|
166
166
|
* 语义臂内部不分层(`index.js` 纯分数 sort)时,模型看到的 MEMORY.md(结论)与 2026-09-xx.md(流水)
|
|
167
167
|
* 在视觉上完全同级,含金量被数量淹没。 */
|
|
168
|
-
export const
|
|
168
|
+
export const L0_LAYER_LABELS_PRE_V1 = Object.freeze({
|
|
169
169
|
project: '结论层 · 项目笔记',
|
|
170
170
|
user: '结论层 · 用户级记忆',
|
|
171
171
|
whiteboard: '结论层 · 白板/账本',
|
|
@@ -174,7 +174,7 @@ export const L0_LAYER_LABELS_V1 = Object.freeze({
|
|
|
174
174
|
})
|
|
175
175
|
|
|
176
176
|
/** 判不出层时的兜底标题:**不猜层**,如实说"未分层",且固定排在最后(避免给出错误的层次暗示)。 */
|
|
177
|
-
export const
|
|
177
|
+
export const L0_LAYER_UNKNOWN_LABEL_PRE_V1 = '未分层'
|
|
178
178
|
|
|
179
179
|
/**
|
|
180
180
|
* R4-B(2026-09-18):把检索命中**按层分组**(**只改呈现,不改排序**)。
|
|
@@ -182,7 +182,7 @@ export const L0_LAYER_UNKNOWN_LABEL_V1 = '未分层'
|
|
|
182
182
|
* 硬契约(与本仓"回滚必须逐字节相同"纪律对齐):
|
|
183
183
|
* ① **不增删条目**:输出各组条目总数 = 入参长度,且**层内保持入参原相对顺序**
|
|
184
184
|
* ⇒ 调用方无需改排序;`#finalRank` 标签仍在行内,排序信息可完全还原;
|
|
185
|
-
* ② 组顺序 = `
|
|
185
|
+
* ② 组顺序 = `L0_LAYER_DISPLAY_ORDER_PRE_V1`;判不出层的固定归**末组**;
|
|
186
186
|
* ③ 空入参 / 非数组 / 取层函数抛错 → **永不抛**(fail-soft:呈现层失败绝不打断检索)。
|
|
187
187
|
*
|
|
188
188
|
* 调用方职责:**只有一组时不打标题** ⇒ 单一层(如纯日志命中)的输出与旧版逐字节相同。
|
|
@@ -210,14 +210,14 @@ export function groupL0ByLayerPre(items, layerOf) {
|
|
|
210
210
|
// `l && ...` 分支**当前不可达** —— 保留它是有意的前向兼容:将来 L0_LAYERS 扩容时,
|
|
211
211
|
// 旧版调用方不会把新层误并入"未分层",而是照实单独成组。
|
|
212
212
|
// (变异演示已证实:改这一段不会让任何断言变红 —— 它确实不参与当下语义。)
|
|
213
|
-
const ordered =
|
|
213
|
+
const ordered = L0_LAYER_DISPLAY_ORDER_PRE_V1.filter((l) => byLayer.has(l))
|
|
214
214
|
for (const l of byLayer.keys()) if (l && ordered.indexOf(l) === -1) ordered.push(l)
|
|
215
215
|
for (const l of ordered) {
|
|
216
|
-
groups.push({ layer: l, label:
|
|
216
|
+
groups.push({ layer: l, label: L0_LAYER_LABELS_PRE_V1[l] || l, items: byLayer.get(l) })
|
|
217
217
|
}
|
|
218
218
|
// 未分层固定末组(不猜层)—— **不走通用流程**:它不属层词表,也不该排在结论层之前。
|
|
219
219
|
if (byLayer.has('')) {
|
|
220
|
-
groups.push({ layer: '', label:
|
|
220
|
+
groups.push({ layer: '', label: L0_LAYER_UNKNOWN_LABEL_PRE_V1, items: byLayer.get('') })
|
|
221
221
|
}
|
|
222
222
|
} catch (_) { /* fail-soft:分组失败即降级为"无分组",调用方按单组处理,检索不受影响 */ }
|
|
223
223
|
return groups
|
|
@@ -275,8 +275,8 @@ export function isRetrievablePre(record) {
|
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
/** 检索侧标记语的取值域(枚举类常量须配断言兜底 —— 本仓纪律)。 */
|
|
278
|
-
export const
|
|
279
|
-
export const
|
|
278
|
+
export const L0_SUPERSEDED_MARK_PRE_V1 = '⚠已作废'
|
|
279
|
+
export const L0_RETRACTED_MARK_PRE_V1 = '⚠已撤回'
|
|
280
280
|
|
|
281
281
|
/**
|
|
282
282
|
* 为「返回但标记」生成**呈现后缀**(R4-A 的可见面)。
|
|
@@ -298,13 +298,13 @@ export function supersededMarkPre(record) {
|
|
|
298
298
|
const idOf = (v) => (/^mem_[0-9a-f]{32}$/.test(String(v || '').trim()) ? String(v).trim() : '')
|
|
299
299
|
if (record.status === 'superseded') {
|
|
300
300
|
const safe = idOf(record.supersededBy)
|
|
301
|
-
return ' ' +
|
|
301
|
+
return ' ' + L0_SUPERSEDED_MARK_PRE_V1 + (safe ? '(已被 ' + safe + ' 取代)' : '(已被更新结论取代)')
|
|
302
302
|
}
|
|
303
303
|
if (record.status === 'retracted') {
|
|
304
304
|
const safe = idOf(record.supersededBy)
|
|
305
305
|
const reason = record.reason ? String(record.reason).replace(/[\r\n]+/g, ' ').trim().slice(0, 80) : ''
|
|
306
306
|
const tail = (reason ? '原因:' + reason + ';' : '') + (safe ? '更正见 ' + safe : '已被撤回')
|
|
307
|
-
return ' ' +
|
|
307
|
+
return ' ' + L0_RETRACTED_MARK_PRE_V1 + '(' + tail + ')'
|
|
308
308
|
}
|
|
309
309
|
return ''
|
|
310
310
|
} catch (_) { return '' } // fail-soft:标记失败绝不影响检索
|
package/lib/l0-index-sync.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* L0 索引接线(
|
|
2
|
+
* L0 索引接线(l0_index_sync_pre_v1)—— 三层检索契约 C3
|
|
3
3
|
* (`docs/internal/THREE-LAYER-CONTRACT.md` §7 C3;接口规范 `SEMANTIC-ARCHITECTURE-SPEC.md` S1/S9)。
|
|
4
4
|
*
|
|
5
5
|
* 背景:`lib/l0-index.js`(L0 自己的向量索引,增量)长期**零引用**——文件在、能力在、
|
|
@@ -8,18 +8,18 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ── 文件布局与命名(契约 C3)────────────────────────────────────────────
|
|
10
10
|
* <dir>/l0-index-<workspaceKey 短哈希>-<layer>.json
|
|
11
|
-
* 例:~/.dsh/memory/semantic/l0/l0-index-9f2c1ab34de5-project.json
|
|
11
|
+
* 例:~/.dsh/memory/semantic-pre/l0/l0-index-9f2c1ab34de5-project.json
|
|
12
12
|
*
|
|
13
13
|
* 为什么**按层各一份文件**、而不是「一个工作区一份合并文件」(与任务书的字面表述有偏差,
|
|
14
14
|
* 这是**能力边界**而非偷懒,见下):
|
|
15
|
-
* `l0-index` 的 `buildFull`/`update` 每次调用只接受**单一 `layer`**
|
|
15
|
+
* `l0-index-pre` 的 `buildFull`/`update` 每次调用只接受**单一 `layer`**
|
|
16
16
|
* (内部是 `buildL0IndexPre(text, { layer })` → 该次调用产出的**全部**条目同层)。
|
|
17
17
|
* 工作区 L0 语料天然多层(project / user / log / reflection / whiteboard 五种来源同批),
|
|
18
18
|
* 因此「一次调用产出一份含真实层归属的单文件」在该 API 下**无法表达**:
|
|
19
19
|
* - 传合并文本 + 任选一个 layer → 其余层的条目**全部落成错的层**(契约 I4 直接违反,
|
|
20
20
|
* 这正是契约 C3 行里点名的「`:147/172` 未传层 → 会全落默认 log」同类错误);
|
|
21
21
|
* - 沿用模块原样不动(任务书要求)时,唯一能保住层归属的实现就是**按层分文件**。
|
|
22
|
-
* 若日后要「一份合并文件」,需要二选一:①给 `l0-index` 增一个多来源入口(改模块);
|
|
22
|
+
* 若日后要「一份合并文件」,需要二选一:①给 `l0-index-pre` 增一个多来源入口(改模块);
|
|
23
23
|
* ②在接线侧自己合并条目(等于把 `assemble()` 复制一份 → 同一文件格式两个写者,风险更高)。
|
|
24
24
|
* 两条都未做,交由契约方裁定(见交付报告「未完成/存疑」)。
|
|
25
25
|
*
|
|
@@ -37,7 +37,7 @@ import { createHash } from 'node:crypto'
|
|
|
37
37
|
import { L0_LAYERS } from './l0-extract.js'
|
|
38
38
|
import { createL0IndexPre } from './l0-index.js'
|
|
39
39
|
|
|
40
|
-
export const L0_INDEX_SYNC_VERSION = '
|
|
40
|
+
export const L0_INDEX_SYNC_VERSION = 'l0_index_sync_pre_v1'
|
|
41
41
|
|
|
42
42
|
/** 索引文件名前缀(契约 C3 命名:`l0-index-<workspaceKey 短哈希>`)。 */
|
|
43
43
|
export const L0_INDEX_FILE_PREFIX = 'l0-index-'
|
|
@@ -63,7 +63,7 @@ export function l0IndexFileNamePre(workspaceKey, layer, chars = L0_INDEX_WS_HASH
|
|
|
63
63
|
* 工厂:L0 索引同步器(IO / 嵌入 / 读文本 全注入)。
|
|
64
64
|
*
|
|
65
65
|
* @param {object} opts
|
|
66
|
-
* @param {{readJson(path):any, writeJson(path,obj):void}} opts.io 落盘 IO(同 l0-index 契约;
|
|
66
|
+
* @param {{readJson(path):any, writeJson(path,obj):void}} opts.io 落盘 IO(同 l0-index-pre 契约;
|
|
67
67
|
* readJson 对缺失文件返回 null 或抛 ENOENT 均可被模块层容错)。
|
|
68
68
|
* @param {{embedPassages(texts:string[]):Promise<Float32Array[]|number[][]>}} opts.embedder
|
|
69
69
|
* 端侧嵌入通道(宿主机注入 `_jsSemantic.embedPassages`;测试注入假 embedder)。
|
|
@@ -80,7 +80,7 @@ export function createL0IndexSyncPre(opts = {}) {
|
|
|
80
80
|
const engineIdentity = typeof opts.engineIdentity === 'string' ? opts.engineIdentity : ''
|
|
81
81
|
const engineIdentityGate = opts.engineIdentityGate
|
|
82
82
|
const crossIdReuse = opts.crossIdReuse
|
|
83
|
-
// 工厂级配置错误直接抛(调用方组装错,不属运行期 fail-soft 范畴)——与 l0-index 同口径。
|
|
83
|
+
// 工厂级配置错误直接抛(调用方组装错,不属运行期 fail-soft 范畴)——与 l0-index-pre 同口径。
|
|
84
84
|
if (!io || typeof io.readJson !== 'function' || typeof io.writeJson !== 'function') {
|
|
85
85
|
throw new Error('l0-index-sync-pre: io.readJson/io.writeJson required')
|
|
86
86
|
}
|
|
@@ -101,7 +101,7 @@ export function createL0IndexSyncPre(opts = {}) {
|
|
|
101
101
|
* @param {object} o
|
|
102
102
|
* @param {boolean} o.enabled 总开关 —— 非 true 直接返回 disabled,**零 IO**(宿主 `l0IndexEnabled`,默认 true)。
|
|
103
103
|
* @param {string} o.workspaceKey 工作区键(进文件名短哈希)。
|
|
104
|
-
* @param {string} o.dir 索引目录(<dshHome>/memory/semantic/l0)。
|
|
104
|
+
* @param {string} o.dir 索引目录(<dshHome>/memory/semantic-pre/l0)。
|
|
105
105
|
* @param {Array<{layer:string, path:string, text?:string}>} o.sources 来源(text 已给则不读盘)。
|
|
106
106
|
* @param {number} [o.maxChars] / @param {number} [o.minChars] 透传 l0-extract-pre 的抽取参数。
|
|
107
107
|
* @param {()=>number} [o.now] 时间注入(测试确定性)。
|
package/lib/l0-index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* L0 向量索引(
|
|
2
|
+
* L0 向量索引(l0_index_pre_v1)—— 为 T1 产出的 L0 建立向量索引,供语义检索使用。
|
|
3
3
|
*
|
|
4
4
|
* 2026-09-09 建立(P1)。参照 OpenViking「Vector Index 只存 URI+向量+元数据,不含文件内容」:
|
|
5
5
|
* 每条目仅 {id, vector, l0, source, l0Hash, updatedAt},**绝不存记忆原文**。
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
*
|
|
14
14
|
* 身份与版本约定(沿用仓库惯例):
|
|
15
15
|
* - l0Hash = sha256(l0),增量重算的唯一判据(l0 不变 → 跳过该条)
|
|
16
|
-
* - l0IndexVersion = '
|
|
17
|
-
* (前缀
|
|
16
|
+
* - l0IndexVersion = 'l0idx_pre_' + first32hex(sha256(canonical sorted [id,l0Hash] tuples))
|
|
17
|
+
* (前缀 l0idx_pre_ 有意区别于 corpus 的 idx_pre_ memoryIndexVersion,避免两套版本语义混淆)
|
|
18
18
|
* - 向量维度由 embedder 决定(真实 C2 引擎为 384 维已归一化),本模块不硬编码维度
|
|
19
19
|
* - 向量以纯 number 数组存盘(JSON 安全);embedder 返回 Float32Array 时经 Array.from 转换
|
|
20
20
|
*
|
|
@@ -25,7 +25,7 @@ import { createHash } from 'node:crypto'
|
|
|
25
25
|
import { buildL0IndexPre, L0_LAYERS, L0_STATUSES, L0_DEFAULT_LAYER } from './l0-extract.js'
|
|
26
26
|
import { isEngineIdentityPre } from './engine-identity.js'
|
|
27
27
|
|
|
28
|
-
export const L0_INDEX_VERSION = '
|
|
28
|
+
export const L0_INDEX_VERSION = 'l0_index_pre_v1'
|
|
29
29
|
export const L0_INDEX_SCHEMA_VERSION = 1
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -55,7 +55,7 @@ function normalizeVector(vec) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
* l0IndexVersion:canonical sorted [id,l0Hash,**layer,status**] tuples → '
|
|
58
|
+
* l0IndexVersion:canonical sorted [id,l0Hash,**layer,status**] tuples → 'l0idx_pre_' + first32hex(sha256)。
|
|
59
59
|
* 同内容同版本(确定性);任一条目 l0 变化、层归属或状态变化、条目增删 → 版本变化。
|
|
60
60
|
* (C3:层/状态进身份,否则「同一条记忆换层」不会触发版本变化 → 下游缓存会拿到过期归属。)
|
|
61
61
|
*/
|
|
@@ -68,7 +68,7 @@ export function computeL0IndexVersionPre(entries) {
|
|
|
68
68
|
normStatusPre(e && e.status),
|
|
69
69
|
])
|
|
70
70
|
.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
|
|
71
|
-
return '
|
|
71
|
+
return 'l0idx_pre_' + sha256Hex(JSON.stringify(canon)).slice(0, 32)
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
@@ -92,7 +92,7 @@ export function createL0IndexPre(opts = {}) {
|
|
|
92
92
|
const crossIdReuse = opts.crossIdReuse !== false
|
|
93
93
|
if (!io || typeof io.readJson !== 'function' || typeof io.writeJson !== 'function') {
|
|
94
94
|
// fail closed:工厂级配置错误直接抛(调用方组装错误,不属于运行期 fail-soft 范畴)
|
|
95
|
-
throw new Error('l0-index: io.readJson/io.writeJson required')
|
|
95
|
+
throw new Error('l0-index-pre: io.readJson/io.writeJson required')
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/** fail-soft 读取+整文件校验。任何异常/不符 → {ok:false, reason, entries:[]}。 */
|
|
@@ -101,7 +101,7 @@ export function createL0IndexPre(opts = {}) {
|
|
|
101
101
|
const obj = io.readJson(path)
|
|
102
102
|
if (!obj || typeof obj !== 'object') return { ok: false, reason: 'missing', entries: [] }
|
|
103
103
|
if (obj.schemaVersion !== L0_INDEX_SCHEMA_VERSION) return { ok: false, reason: 'schema', entries: [] }
|
|
104
|
-
if (typeof obj.l0IndexVersion !== 'string' || !obj.l0IndexVersion.startsWith('
|
|
104
|
+
if (typeof obj.l0IndexVersion !== 'string' || !obj.l0IndexVersion.startsWith('l0idx_pre_')) {
|
|
105
105
|
return { ok: false, reason: 'version', entries: [] }
|
|
106
106
|
}
|
|
107
107
|
// ★P2 T2-9 引擎身份门:文件声明的引擎身份与当前引擎不一致 ⇒ 整文件判不可用,
|
|
@@ -180,11 +180,11 @@ export function createL0IndexPre(opts = {}) {
|
|
|
180
180
|
if (needEmbed.length) {
|
|
181
181
|
const vecs = await embedder.embedPassages(needEmbed.map((x) => x.text))
|
|
182
182
|
if (!Array.isArray(vecs) || vecs.length !== needEmbed.length) {
|
|
183
|
-
throw new Error('l0-index: embedder returned ' + (Array.isArray(vecs) ? vecs.length : 'non-array') + ' vectors for ' + needEmbed.length + ' passages')
|
|
183
|
+
throw new Error('l0-index-pre: embedder returned ' + (Array.isArray(vecs) ? vecs.length : 'non-array') + ' vectors for ' + needEmbed.length + ' passages')
|
|
184
184
|
}
|
|
185
185
|
needEmbed.forEach((x, i) => {
|
|
186
186
|
const v = normalizeVector(vecs[i])
|
|
187
|
-
if (!v) throw new Error('l0-index: embedder produced invalid vector at index ' + i)
|
|
187
|
+
if (!v) throw new Error('l0-index-pre: embedder produced invalid vector at index ' + i)
|
|
188
188
|
vecsByEmbeddedHash.set(x.hash, v)
|
|
189
189
|
})
|
|
190
190
|
}
|
|
@@ -199,7 +199,7 @@ export function createL0IndexPre(opts = {}) {
|
|
|
199
199
|
else reusedByHash++
|
|
200
200
|
} else {
|
|
201
201
|
vector = vecsByEmbeddedHash.get(hash)
|
|
202
|
-
if (!vector) throw new Error('l0-index: missing vector for ' + it.id)
|
|
202
|
+
if (!vector) throw new Error('l0-index-pre: missing vector for ' + it.id)
|
|
203
203
|
}
|
|
204
204
|
const prev = prevById ? prevById.get(it.id) : null
|
|
205
205
|
// updatedAt 语义:整条(同 id 同 hash)复用时保留原时间;跨 id 复用属"新条目指向旧向量",按新条记时。
|
package/lib/ledger-criteria.js
CHANGED
|
@@ -20,15 +20,15 @@ export { checkHandoffCriteriaPre as checkHandoffCriteriaContractPre, checkPlanCr
|
|
|
20
20
|
import { parseHandoffLedgerPre } from './handoff-anchor.js'
|
|
21
21
|
|
|
22
22
|
/** 交接账本四段权威标题(与 handoff-anchor.js 权重表同源)。 */
|
|
23
|
-
export const
|
|
23
|
+
export const HANDOFF_SECTIONS_PRE_V1 = Object.freeze(['任务状态', '目标', '已试方案与失败原因', '进度与下一步'])
|
|
24
24
|
|
|
25
25
|
/** 占位符黑名单(借 dsh-graph CRITERIA_PLACEHOLDERS 技巧, 方案 H3)。 */
|
|
26
|
-
export const
|
|
26
|
+
export const CRITERIA_PLACEHOLDERS_PRE_V1 = Object.freeze(['(待补充)', '(待补充)', 'TODO', '同上', '略', 'N/A', 'n/a'])
|
|
27
27
|
|
|
28
28
|
/** 账本硬上限(H4, 与 sanitizeForWrite 8000 同源, 先于它执行)。 */
|
|
29
|
-
export const
|
|
29
|
+
export const LEDGER_MAX_CHARS_PRE_V1 = 8000
|
|
30
30
|
/** 白板硬上限(P-H2, 200000)。 */
|
|
31
|
-
export const
|
|
31
|
+
export const PLAN_MAX_CHARS_PRE_V1 = 200000
|
|
32
32
|
|
|
33
33
|
function nz(s) { return String(s || '') }
|
|
34
34
|
|
|
@@ -40,7 +40,7 @@ function sectionBodies(parsed) {
|
|
|
40
40
|
function isPlaceholderLine(line) {
|
|
41
41
|
const t = nz(line).trim()
|
|
42
42
|
if (!t) return false
|
|
43
|
-
return
|
|
43
|
+
return CRITERIA_PLACEHOLDERS_PRE_V1.some((p) => t === p || t === '-' + p || t === '*' + p)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** H2: 每段 body 非空(≥1 非空行且合计 ≥20 字符)。 */
|
|
@@ -64,7 +64,7 @@ export function checkHandoffCriteriaPre(text) {
|
|
|
64
64
|
|
|
65
65
|
// H1 四段标题齐全且逐字匹配
|
|
66
66
|
const titles = parsed ? sectionBodies(parsed).map((x) => x.title) : []
|
|
67
|
-
const missing =
|
|
67
|
+
const missing = HANDOFF_SECTIONS_PRE_V1.filter((t) => !titles.includes(t))
|
|
68
68
|
hard.push({ id: 'H1', pass: !!parsed && missing.length === 0, missing: parsed ? missing.map((t) => '缺段:' + t) : ['解析失败(无任何 ## 段)'] })
|
|
69
69
|
|
|
70
70
|
// H2 每段非空
|
|
@@ -78,7 +78,7 @@ export function checkHandoffCriteriaPre(text) {
|
|
|
78
78
|
|
|
79
79
|
// H4 总长 ≤8000
|
|
80
80
|
const len = nz(text).length
|
|
81
|
-
hard.push({ id: 'H4', pass: len <=
|
|
81
|
+
hard.push({ id: 'H4', pass: len <= LEDGER_MAX_CHARS_PRE_V1, missing: len > LEDGER_MAX_CHARS_PRE_V1 ? ['总长 ' + len + ' > ' + LEDGER_MAX_CHARS_PRE_V1] : [] })
|
|
82
82
|
|
|
83
83
|
// S1 每段 ≤5 行(软)
|
|
84
84
|
const overLong = parsed ? sectionBodies(parsed).filter((x) => x.body.filter((l) => l.trim()).length > 5).map((x) => x.title) : []
|
|
@@ -119,7 +119,7 @@ export function checkPlanCriteriaPre(text) {
|
|
|
119
119
|
const nonEmptySections = sections.filter((s) => s.body.join('').replace(/\s/g, '').length >= 20)
|
|
120
120
|
hard.push({ id: 'P-H1', pass: nonEmptySections.length >= 1, missing: nonEmptySections.length === 0 ? ['无非空 ## 顶层节(全部节被老化走或空白板)'] : [] })
|
|
121
121
|
// P-H2 长度
|
|
122
|
-
hard.push({ id: 'P-H2', pass: src.length <=
|
|
122
|
+
hard.push({ id: 'P-H2', pass: src.length <= PLAN_MAX_CHARS_PRE_V1, missing: src.length > PLAN_MAX_CHARS_PRE_V1 ? ['总长 ' + src.length + ' > ' + PLAN_MAX_CHARS_PRE_V1] : [] })
|
|
123
123
|
// P-S1 前瞻内容(软)
|
|
124
124
|
const s1pass = /下一步|待办|计划|todo/i.test(src)
|
|
125
125
|
soft.push({ id: 'P-S1', pass: s1pass, detail: s1pass ? '' : '建议含前瞻内容(下一步/待办/计划)' })
|
package/lib/m4-corpus.js
CHANGED
|
@@ -17,7 +17,7 @@ import { readFileSync, existsSync, statSync, realpathSync } from 'node:fs'
|
|
|
17
17
|
import path from 'node:path'
|
|
18
18
|
import { createHash } from 'node:crypto'
|
|
19
19
|
import { parseSidecar } from './memory-anchor.js'
|
|
20
|
-
import { memoryIndexVersion as computeIndexVersion,
|
|
20
|
+
import { memoryIndexVersion as computeIndexVersion, SHADOW_LEXICAL_BUDGET_PRE_V1 as BUDGET } from './shadow-retrieval.js'
|
|
21
21
|
|
|
22
22
|
const sha256Hex = (buf) => createHash('sha256').update(buf).digest('hex')
|
|
23
23
|
const INDEX_MAX_FILE_BYTES = 5 * 1024 * 1024
|
|
@@ -26,7 +26,7 @@ import { buildIndexSyncPlansPre, sendIndexSyncPlanPre } from './index-sync.js'
|
|
|
26
26
|
import { workspaceRefOf } from './evidence-store.js'
|
|
27
27
|
import { createEngineSwitchPre } from './engine-switch.js'
|
|
28
28
|
|
|
29
|
-
export const M7_INDEX_SYNC_HOST_POLICY_VERSION = '
|
|
29
|
+
export const M7_INDEX_SYNC_HOST_POLICY_VERSION = 'm7_index_sync_host_pre_v1'
|
|
30
30
|
const MAX_DROPS = 16
|
|
31
31
|
|
|
32
32
|
export function createIndexSyncHostPre(opts = {}) {
|
|
@@ -86,7 +86,7 @@ export function createIndexSyncHostPre(opts = {}) {
|
|
|
86
86
|
if (!snapshot || !snapshot.records || !snapshot.records.length) return { ok: false, ready: false, reason: 'empty-corpus' }
|
|
87
87
|
const wsRef = workspaceRefOf(paths.workspaceKey)
|
|
88
88
|
const miv = String(snapshot.memoryIndexVersion || '')
|
|
89
|
-
if (!miv.startsWith('
|
|
89
|
+
if (!miv.startsWith('idx_pre_')) return { ok: false, ready: false, reason: 'bad-miv' }
|
|
90
90
|
const epoch = currentEpoch()
|
|
91
91
|
const k = keyOf(wsRef, scope)
|
|
92
92
|
const cached = readyCache.get(k)
|
package/lib/m7-wire.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 零 IO、零依赖(node:crypto);本模块不 spawn、不监听、不读文件、不改 M5/M6 schema。
|
|
4
4
|
*
|
|
5
5
|
* 组成:
|
|
6
|
-
* 1) 协议常量(
|
|
6
|
+
* 1) 协议常量(m7_wire_pre_v1 / 传输预算 / 帧类型两个不相交集合 / 请求→响应对应)
|
|
7
7
|
* 2) canonical JSON + SHA-256(JS 与 Python worker 的逐字节一致实现;排序键、无空白、UTF-8)
|
|
8
8
|
* 3) M7TransportFramePre envelope validator(fail closed;方向门)
|
|
9
9
|
* 4) SemanticRecordPre / IndexSyncBegin/Page/Commit payload validators(M7-1)
|
|
@@ -16,11 +16,11 @@ const sha256Hex = (buf) => createHash('sha256').update(buf).digest('hex')
|
|
|
16
16
|
const sha256Str = (s) => sha256Hex(Buffer.from(String(s), 'utf8'))
|
|
17
17
|
const first32 = (h) => h.slice(0, 32)
|
|
18
18
|
|
|
19
|
-
export const
|
|
20
|
-
export const
|
|
19
|
+
export const M7_WIRE_PROTOCOL_VERSION_PRE_V1 = 'm7_wire_pre_v1'
|
|
20
|
+
export const M7_INDEX_POLICY_VERSION_PRE_V1 = 'index_sync_pre_v1'
|
|
21
21
|
|
|
22
22
|
/** §7 传输预算(冻结;变更必须升级协议版本)。 */
|
|
23
|
-
export const
|
|
23
|
+
export const M7_TRANSPORT_BUDGET_PRE_V1 = Object.freeze({
|
|
24
24
|
schemaVersion: 1,
|
|
25
25
|
maxLineBytes: 256 * 1024,
|
|
26
26
|
requestTimeoutMs: 5000,
|
|
@@ -31,17 +31,17 @@ export const M7_TRANSPORT_BUDGET_V1 = Object.freeze({
|
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
/** JS→Python frame 类型(§7.2;index_sync_* 属 M7-1;recall_rank 属 P13 recall C3 语义臂)。 */
|
|
34
|
-
export const
|
|
34
|
+
export const JS_FRAME_TYPES_PRE_V1 = Object.freeze([
|
|
35
35
|
'health', 'context_push', 'index_sync_begin', 'index_sync_page', 'index_sync_commit',
|
|
36
36
|
'cancel', 'close_session', 'recall_rank',
|
|
37
37
|
])
|
|
38
38
|
/** Python→JS frame 类型。 */
|
|
39
|
-
export const
|
|
39
|
+
export const PY_FRAME_TYPES_PRE_V1 = Object.freeze([
|
|
40
40
|
'health_result', 'context_ack', 'index_ack', 'activation_request', 'error', 'recall_rank_result',
|
|
41
41
|
])
|
|
42
|
-
const ALL_FRAME_TYPES = new Set([...
|
|
42
|
+
const ALL_FRAME_TYPES = new Set([...JS_FRAME_TYPES_PRE_V1, ...PY_FRAME_TYPES_PRE_V1])
|
|
43
43
|
/** 请求→响应 type 对应(cancel/close_session 刻意无响应帧)。 */
|
|
44
|
-
export const
|
|
44
|
+
export const RESPONSE_TYPE_FOR_PRE_V1 = Object.freeze({
|
|
45
45
|
health: 'health_result',
|
|
46
46
|
context_push: 'context_ack',
|
|
47
47
|
index_sync_begin: 'index_ack',
|
|
@@ -50,7 +50,7 @@ export const RESPONSE_TYPE_FOR_V1 = Object.freeze({
|
|
|
50
50
|
recall_rank: 'recall_rank_result',
|
|
51
51
|
})
|
|
52
52
|
|
|
53
|
-
// ========== canonical JSON(与 python/
|
|
53
|
+
// ========== canonical JSON(与 python/worker_pre_v1.py 逐字节一致) ==========
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* 确定性 canonical JSON:对象键递归排序、无空白、非 ASCII 原样 UTF-8、undefined 剔除。
|
|
@@ -82,15 +82,15 @@ export function sha256Canonical(value) {
|
|
|
82
82
|
export function validateTransportFramePre(frame, opts = {}) {
|
|
83
83
|
const p = []
|
|
84
84
|
if (!frame || typeof frame !== 'object' || Array.isArray(frame)) return { ok: false, reason: 'not-object' }
|
|
85
|
-
if (frame.protocolVersion !==
|
|
85
|
+
if (frame.protocolVersion !== M7_WIRE_PROTOCOL_VERSION_PRE_V1) p.push('protocolVersion')
|
|
86
86
|
if (typeof frame.frameId !== 'string' || !frame.frameId) p.push('frameId')
|
|
87
87
|
if (typeof frame.requestId !== 'string') p.push('requestId')
|
|
88
88
|
if (typeof frame.workerEpoch !== 'string' || !frame.workerEpoch) p.push('workerEpoch')
|
|
89
89
|
if (!ALL_FRAME_TYPES.has(frame.type)) p.push('type')
|
|
90
90
|
if (!frame.payload || typeof frame.payload !== 'object' || Array.isArray(frame.payload)) p.push('payload')
|
|
91
91
|
if (typeof frame.sentAt !== 'number' || !Number.isFinite(frame.sentAt)) p.push('sentAt')
|
|
92
|
-
if (!p.length && opts.direction === 'in' && !
|
|
93
|
-
if (!p.length && opts.direction === 'out' && !
|
|
92
|
+
if (!p.length && opts.direction === 'in' && !PY_FRAME_TYPES_PRE_V1.includes(frame.type)) p.push('direction')
|
|
93
|
+
if (!p.length && opts.direction === 'out' && !JS_FRAME_TYPES_PRE_V1.includes(frame.type)) p.push('direction')
|
|
94
94
|
if (p.length) return { ok: false, reason: 'invalid:' + p.join(',') }
|
|
95
95
|
return { ok: true, frame }
|
|
96
96
|
}
|
|
@@ -103,8 +103,8 @@ export function makeRequestFramePre(input) {
|
|
|
103
103
|
const requestId = String((input && input.requestId) || '')
|
|
104
104
|
const sentAt = Number(input && input.sentAt)
|
|
105
105
|
const frame = {
|
|
106
|
-
protocolVersion:
|
|
107
|
-
frameId: '
|
|
106
|
+
protocolVersion: M7_WIRE_PROTOCOL_VERSION_PRE_V1,
|
|
107
|
+
frameId: 'frm_pre_' + first32(sha256Str(JSON.stringify(['m7-frame-pre-v1', type, requestId, sentAt]))),
|
|
108
108
|
requestId,
|
|
109
109
|
workerEpoch: String((input && input.workerEpoch) || ''),
|
|
110
110
|
type,
|
|
@@ -119,7 +119,7 @@ export function makeRequestFramePre(input) {
|
|
|
119
119
|
|
|
120
120
|
const MEMORY_ID_RE = /^mem_[0-9a-f]{32}$/
|
|
121
121
|
const HEX64_RE = /^[0-9a-f]{64}$/
|
|
122
|
-
const IDX_VERSION_RE = /^
|
|
122
|
+
const IDX_VERSION_RE = /^idx_pre_[0-9a-f]{32}$/
|
|
123
123
|
const WORKSPACE_REF_RE = /^wsr_[0-9a-f]{32}$/
|
|
124
124
|
/** 与 M5/M6 同一相对引用白名单(user:/workspace:/workspace-log:+文件名)。 */
|
|
125
125
|
const SOURCE_REF_RE = new RegExp('^(user|workspace|workspace-log):[A-Za-z0-9._\\u4e00-\\u9fff-]+$')
|
|
@@ -142,7 +142,7 @@ export function validateSemanticRecordPre(rec) {
|
|
|
142
142
|
if (rec.heading !== undefined && rec.heading !== null && typeof rec.heading !== 'string') p.push('heading')
|
|
143
143
|
if (typeof rec.text !== 'string') p.push('text')
|
|
144
144
|
if (rec.occurredAt !== undefined && rec.occurredAt !== null && !Number.isFinite(rec.occurredAt)) p.push('occurredAt')
|
|
145
|
-
if (typeof rec.chunkId !== 'string' || !rec.chunkId.startsWith('
|
|
145
|
+
if (typeof rec.chunkId !== 'string' || !rec.chunkId.startsWith('chk_pre_')) p.push('chunkId')
|
|
146
146
|
if (!Number.isInteger(rec.chunkOrdinal) || rec.chunkOrdinal < 0) p.push('chunkOrdinal')
|
|
147
147
|
if (!Number.isInteger(rec.chunkCount) || rec.chunkCount < 1 || rec.chunkOrdinal >= rec.chunkCount) p.push('chunkCount')
|
|
148
148
|
if (p.length) return { ok: false, reason: 'invalid:' + p.join(',') }
|
|
@@ -154,7 +154,7 @@ export function validateIndexSyncBeginPre(pl) {
|
|
|
154
154
|
const p = []
|
|
155
155
|
if (!pl || typeof pl !== 'object') return { ok: false, reason: 'not-object' }
|
|
156
156
|
if (pl.schemaVersion !== 1) p.push('schemaVersion')
|
|
157
|
-
if (typeof pl.syncId !== 'string' || !pl.syncId.startsWith('
|
|
157
|
+
if (typeof pl.syncId !== 'string' || !pl.syncId.startsWith('syn_pre_')) p.push('syncId')
|
|
158
158
|
if (typeof pl.workspaceRef !== 'string' || !WORKSPACE_REF_RE.test(pl.workspaceRef)) p.push('workspaceRef')
|
|
159
159
|
if (pl.scope !== 'Workspace' && pl.scope !== 'User') p.push('scope')
|
|
160
160
|
if (typeof pl.memoryIndexVersion !== 'string' || !IDX_VERSION_RE.test(pl.memoryIndexVersion)) p.push('memoryIndexVersion')
|
|
@@ -170,7 +170,7 @@ export function validateIndexSyncBeginPre(pl) {
|
|
|
170
170
|
}
|
|
171
171
|
if (!Number.isInteger(pl.recordCount) || pl.recordCount < 0) p.push('recordCount')
|
|
172
172
|
if (!Number.isInteger(pl.pageCount) || pl.pageCount < 0) p.push('pageCount')
|
|
173
|
-
if (pl.indexPolicyVersion !==
|
|
173
|
+
if (pl.indexPolicyVersion !== M7_INDEX_POLICY_VERSION_PRE_V1) p.push('indexPolicyVersion')
|
|
174
174
|
if (!p.length && (pl.recordCount === 0) !== (pl.pageCount === 0)) p.push('count-consistency')
|
|
175
175
|
if (p.length) return { ok: false, reason: 'invalid:' + p.join(',') }
|
|
176
176
|
return { ok: true, payload: pl }
|
|
@@ -181,7 +181,7 @@ export function validateIndexSyncPagePre(pl) {
|
|
|
181
181
|
const p = []
|
|
182
182
|
if (!pl || typeof pl !== 'object') return { ok: false, reason: 'not-object' }
|
|
183
183
|
if (pl.schemaVersion !== 1) p.push('schemaVersion')
|
|
184
|
-
if (typeof pl.syncId !== 'string' || !pl.syncId.startsWith('
|
|
184
|
+
if (typeof pl.syncId !== 'string' || !pl.syncId.startsWith('syn_pre_')) p.push('syncId')
|
|
185
185
|
if (!Number.isInteger(pl.pageNo) || pl.pageNo < 0) p.push('pageNo')
|
|
186
186
|
if (!Number.isInteger(pl.pageCount) || pl.pageCount < 0) p.push('pageCount')
|
|
187
187
|
if (typeof pl.pageDigest !== 'string' || !HEX64_RE.test(pl.pageDigest)) p.push('pageDigest')
|
|
@@ -196,7 +196,7 @@ export function validateIndexSyncCommitPre(pl) {
|
|
|
196
196
|
const p = []
|
|
197
197
|
if (!pl || typeof pl !== 'object') return { ok: false, reason: 'not-object' }
|
|
198
198
|
if (pl.schemaVersion !== 1) p.push('schemaVersion')
|
|
199
|
-
if (typeof pl.syncId !== 'string' || !pl.syncId.startsWith('
|
|
199
|
+
if (typeof pl.syncId !== 'string' || !pl.syncId.startsWith('syn_pre_')) p.push('syncId')
|
|
200
200
|
if (typeof pl.memoryIndexVersion !== 'string' || !IDX_VERSION_RE.test(pl.memoryIndexVersion)) p.push('memoryIndexVersion')
|
|
201
201
|
if (typeof pl.finalDigest !== 'string' || !HEX64_RE.test(pl.finalDigest)) p.push('finalDigest')
|
|
202
202
|
if (p.length) return { ok: false, reason: 'invalid:' + p.join(',') }
|
|
@@ -208,7 +208,7 @@ export function validateIndexAckPayloadPre(pl) {
|
|
|
208
208
|
const p = []
|
|
209
209
|
if (!pl || typeof pl !== 'object') return { ok: false, reason: 'not-object' }
|
|
210
210
|
if (pl.schemaVersion !== 1) p.push('schemaVersion')
|
|
211
|
-
if (typeof pl.syncId !== 'string' || !pl.syncId.startsWith('
|
|
211
|
+
if (typeof pl.syncId !== 'string' || !pl.syncId.startsWith('syn_pre_')) p.push('syncId')
|
|
212
212
|
if (!['begin', 'page', 'commit'].includes(pl.phase)) p.push('phase')
|
|
213
213
|
if (typeof pl.accepted !== 'boolean') p.push('accepted')
|
|
214
214
|
if (pl.accepted === false && (typeof pl.reason !== 'string' || !pl.reason)) p.push('reason')
|
|
@@ -224,12 +224,12 @@ export function validateIndexAckPayloadPre(pl) {
|
|
|
224
224
|
|
|
225
225
|
/** 派生 chunk 身份(占位 chunking=整记录单 chunk;M7-2 tokenizer 落地前冻结此派生规则)。 */
|
|
226
226
|
export function buildChunkIdPre(memoryId, recordDigest) {
|
|
227
|
-
return '
|
|
227
|
+
return 'chk_pre_' + first32(sha256Str(JSON.stringify(['semantic-chunk-pre-v1', String(memoryId || ''), String(recordDigest || '')])))
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
/** syncId:由 workspaceRef+scope+memoryIndexVersion+recordCount 确定(同快照重放同 id)。 */
|
|
231
231
|
export function buildSyncIdPre(workspaceRef, scope, memoryIndexVersion, recordCount) {
|
|
232
|
-
return '
|
|
232
|
+
return 'syn_pre_' + first32(sha256Str(JSON.stringify(['index-sync-pre-v1', String(workspaceRef || ''), String(scope || ''), String(memoryIndexVersion || ''), Number(recordCount) | 0])))
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
/** pageDigest = sha256(canonical(records 数组))。 */
|
|
@@ -243,7 +243,7 @@ export function computePageDigestPre(records) {
|
|
|
243
243
|
*/
|
|
244
244
|
export function computeFinalDigestPre(input) {
|
|
245
245
|
return sha256Canonical({
|
|
246
|
-
kind: '
|
|
246
|
+
kind: 'index_sync_final_pre_v1',
|
|
247
247
|
syncId: String(input.syncId || ''),
|
|
248
248
|
memoryIndexVersion: String(input.memoryIndexVersion || ''),
|
|
249
249
|
workspaceRef: String(input.workspaceRef || ''),
|
package/lib/memory-anchor.js
CHANGED
|
@@ -22,7 +22,7 @@ import { createHash, randomUUID } from 'node:crypto'
|
|
|
22
22
|
import { splitByteLines, INDEX_MAX_FILE_BYTES } from './memory-index.js'
|
|
23
23
|
|
|
24
24
|
export const SIDECAR_SCHEMA_VERSION = 1
|
|
25
|
-
export const SIDECAR_NAMESPACE = 'dsh-auto-memory'
|
|
25
|
+
export const SIDECAR_NAMESPACE = 'dsh-auto-memory-pre'
|
|
26
26
|
export const ANCHOR_PREFIX = 'memory:'
|
|
27
27
|
export const MEMORY_ID_RE = /^mem_[0-9a-f]{32}$/
|
|
28
28
|
export const MARKER_RE = /^<!-- memory:(mem_[0-9a-f]{32}) -->$/
|