@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.
Files changed (92) hide show
  1. package/docs/CONTINUITY-FLOW.md +6 -6
  2. package/docs/HANDBOOK.md +54 -53
  3. package/docs/M-CM-PLAN.md +1 -1
  4. package/docs/M-CM-STATE.md +1 -1
  5. package/docs/M-CM7-HANDOFF-LAYERED-RETRIEVAL.md +1 -1
  6. package/docs/M3B-CONTRACT.md +7 -7
  7. package/docs/M7-AUTONOMOUS-STATE.md +1 -1
  8. package/docs/M7-TASKSET-DISPATCH.md +1 -1
  9. package/docs/PROMPT-PACK-LAYERED-RECALL.md +3 -3
  10. package/docs/PROMPT-SET-STRICT.md +3 -3
  11. package/docs/RELEASE-GO-NOGO.md +1 -1
  12. package/docs/STATUS-BOARD.md +1 -1
  13. package/docs/USER-GUIDE.en.md +21 -21
  14. package/docs/USER-GUIDE.zh-CN.md +21 -21
  15. package/docs/WHITEPAPER.md +1 -1
  16. package/docs/prompts/FIX-AGENT-P12-FULL-REGRESSION.md +1 -1
  17. package/docs/prompts/LIVE-VERIFY-ZCODE.md +2 -2
  18. package/docs/prompts/P2-semantic-recall.md +2 -2
  19. package/docs/prompts/P7-write-fix.md +1 -1
  20. package/docs/prompts/ZCODE-DROPIN.md +1 -1
  21. package/lib/acceptance.js +6 -6
  22. package/lib/activation-host.js +15 -14
  23. package/lib/activation-inbox-state.js +4 -4
  24. package/lib/activation-inbox.js +44 -44
  25. package/lib/board-mode.js +3 -3
  26. package/lib/client.js +193 -34
  27. package/lib/config-io.js +12 -12
  28. package/lib/context-bridge.js +22 -22
  29. package/lib/context-host.js +24 -23
  30. package/lib/datadir.js +95 -0
  31. package/lib/degrade.js +21 -21
  32. package/lib/dsh-home.js +8 -8
  33. package/lib/engine-identity.js +12 -12
  34. package/lib/engine-switch.js +3 -3
  35. package/lib/episodic-store.js +13 -13
  36. package/lib/evidence-agg.js +4 -4
  37. package/lib/evidence-store.js +12 -12
  38. package/lib/fact-store.js +47 -47
  39. package/lib/handoff-anchor.js +4 -4
  40. package/lib/hub-io.js +356 -3
  41. package/lib/index-sync.js +5 -5
  42. package/lib/index.js +381 -236
  43. package/lib/intent-clean-safe.js +3 -3
  44. package/lib/intent-clean.js +1 -1
  45. package/lib/l0-extract.js +15 -15
  46. package/lib/l0-index-sync.js +8 -8
  47. package/lib/l0-index.js +11 -11
  48. package/lib/ledger-criteria.js +8 -8
  49. package/lib/m4-corpus.js +1 -1
  50. package/lib/m7-index-sync-host.js +2 -2
  51. package/lib/m7-wire.js +24 -24
  52. package/lib/memory-anchor.js +1 -1
  53. package/lib/memory-envelope.js +15 -15
  54. package/lib/memory-hub.js +95 -50
  55. package/lib/memory-importance.js +7 -7
  56. package/lib/memory-mutation.js +5 -5
  57. package/lib/note-status-apply.js +2 -2
  58. package/lib/note-status.js +7 -7
  59. package/lib/policies/activation_policy_v2.json +2 -2
  60. package/lib/policies/recall_intent_lr_v1.json +1 -1
  61. package/lib/procedure-observation.js +2 -2
  62. package/lib/procedure-store.js +117 -28
  63. package/lib/python-setup.js +6 -5
  64. package/lib/python-sidecar-client.js +26 -26
  65. package/lib/recall-fusion.js +12 -12
  66. package/lib/recall-stats.js +36 -1
  67. package/lib/rerank-host.js +11 -11
  68. package/lib/rules-edit.js +9 -9
  69. package/lib/rules-layer.js +26 -26
  70. package/lib/semantic-decide.js +7 -7
  71. package/lib/semantic-js.js +13 -13
  72. package/lib/shadow-host.js +7 -6
  73. package/lib/shadow-retrieval.js +40 -40
  74. package/lib/skill-export-host.js +7 -7
  75. package/lib/skill-export.js +6 -6
  76. package/lib/state-commit.js +10 -10
  77. package/lib/storage-manage.js +6 -6
  78. package/lib/tier-layer-inject.js +28 -28
  79. package/lib/tier0-catalog.js +4 -4
  80. package/lib/wb-contract.js +45 -45
  81. package/lib/wb-sidecar.js +11 -11
  82. package/package.json +3 -4
  83. package/python/m7_activation_features_v2.py +7 -7
  84. package/python/m7_embedding_v1.py +12 -12
  85. package/python/policies/activation_policy_v2.json +2 -2
  86. package/python/policies/decision-record-activation-v2-delta-exp-override-20260824.json +1 -1
  87. package/python/policies/decision-record-reasoning-kind-admission-20260826.json +2 -2
  88. package/python/policies/decision-record-stale-gate-per-candidate-20260825.json +2 -2
  89. package/python/policies/recall_intent_lr_v1.json +1 -1
  90. package/python/verify_policy_artifact.py +3 -3
  91. package/python/worker_semantic_v1.py +18 -18
  92. package/python/worker_v1.py +18 -18
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 引擎身份(engine_identity_v1)—— P2「引擎隔离」的地基(V2-P2 卡 / 评审 §3.5)。
2
+ * 引擎身份(engine_identity_pre_v1)—— P2「引擎隔离」的地基(V2-P2 卡 / 评审 §3.5)。
3
3
  *
4
4
  * 为什么需要它(权威依据):
5
5
  * V2-P2 卡明确指出:单个 `PROVIDER_ID_INT8`(`python-setup.js:55`)**不足以**标识
@@ -9,7 +9,7 @@
9
9
  * 模型与权重摘要 · tokenizer 版本 · 精度格式 · 维度 · 池化 · 归一化 · 输入处理版本。
10
10
  *
11
11
  * 本模块只做三件事(纯函数、零 IO、零依赖,便于宿主与测试共用):
12
- * 1) `computeEngineIdentityPre(desc)` —— 宽身份描述 → 稳定身份串 `engid_<32hex>`
12
+ * 1) `computeEngineIdentityPre(desc)` —— 宽身份描述 → 稳定身份串 `engid_pre_<32hex>`
13
13
  * (canonical 排序 JSON + sha256,同输入逐字节同输出;任一字段变化 → 身份必变)。
14
14
  * 2) `aliasKeyPre(engineIdentity, chunkId)` —— 两级引用的**第一级**:
15
15
  * `engineIdentity + chunkId → alias`。记录变化导致未改块重新编号(chunkId 变)时,
@@ -25,14 +25,14 @@
25
25
  */
26
26
  import { createHash } from 'node:crypto'
27
27
 
28
- export const ENGINE_IDENTITY_VERSION = 'engine_identity_v1'
28
+ export const ENGINE_IDENTITY_VERSION = 'engine_identity_pre_v1'
29
29
 
30
- /** 身份串前缀:有意与 corpus 的 `idx_` / L0 的 `l0idx_` 区分,避免三套版本语义混淆。 */
31
- export const ENGINE_IDENTITY_PREFIX = 'engid_'
30
+ /** 身份串前缀:有意与 corpus 的 `idx_pre_` / L0 的 `l0idx_pre_` 区分,避免三套版本语义混淆。 */
31
+ export const ENGINE_IDENTITY_PREFIX = 'engid_pre_'
32
32
 
33
33
  /** 身份必须覆盖的字段(缺一即身份不完整;缺失字段按空串参与哈希,不给"省略=通过"的口子)。 */
34
34
  export const ENGINE_IDENTITY_FIELDS = Object.freeze([
35
- 'provider', // 通道/提供方标识(如 bge-m3-onnx-int8-v1 / js-e5-small-q8-v1)
35
+ 'provider', // 通道/提供方标识(如 bge-m3-onnx-int8-pre-v1 / js-e5-small-q8-pre-v1)
36
36
  'model', // 模型名(如 bge-m3 / multilingual-e5-small)
37
37
  'weightsDigest', // 权重摘要(sha256 或廉价指纹;见 weightsDigestOfFilePre 的口径说明)
38
38
  'tokenizer', // tokenizer 版本/来源(如 xenova-bge-m3-fast / e5-small-tokenizer-v1)
@@ -63,7 +63,7 @@ export function canonicalEngineDescPre(desc) {
63
63
  }
64
64
 
65
65
  /**
66
- * 宽身份计算:canonical 描述 → `engid_<first32hex(sha256)>`。
66
+ * 宽身份计算:canonical 描述 → `engid_pre_<first32hex(sha256)>`。
67
67
  * 任一字段(含维度、精度、tokenizer、输入版本)变化 → 身份必变;同输入确定性。
68
68
  */
69
69
  export function computeEngineIdentityPre(desc) {
@@ -73,7 +73,7 @@ export function computeEngineIdentityPre(desc) {
73
73
 
74
74
  /** 身份合法性:必须是本模块产出的前缀形态(防把任意字符串当身份塞进缓存)。 */
75
75
  export function isEngineIdentityPre(x) {
76
- return typeof x === 'string' && /^engid_[0-9a-f]{32}$/.test(x)
76
+ return typeof x === 'string' && /^engid_pre_[0-9a-f]{32}$/.test(x)
77
77
  }
78
78
 
79
79
  /** 两个身份是否同一引擎。非法的身份一律视为不匹配(fail closed:宁可重建,不冒串用风险)。 */
@@ -115,10 +115,10 @@ export function weightsDigestOfFilePre(stat, name) {
115
115
  }
116
116
 
117
117
  /** 端侧 JS 引擎(e5-small q8,384 维已归一化)的规范身份描述。 */
118
- export const JS_E5_IDENTITY_DESC_V1 = Object.freeze({
119
- provider: 'js-e5-small-q8-v1',
118
+ export const JS_E5_IDENTITY_DESC_PRE_V1 = Object.freeze({
119
+ provider: 'js-e5-small-q8-pre-v1',
120
120
  model: 'multilingual-e5-small',
121
- weightsDigest: 'manifest-e5-small-q8-v1', // 端侧资产清单版本;模型文件摘要由下载清单锁定
121
+ weightsDigest: 'manifest-e5-small-q8-pre-v1', // 端侧资产清单版本;模型文件摘要由下载清单锁定
122
122
  tokenizer: 'e5-small-tokenizer-v1',
123
123
  precision: 'q8',
124
124
  dim: 384,
@@ -130,7 +130,7 @@ export const JS_E5_IDENTITY_DESC_V1 = Object.freeze({
130
130
  /** Python 引擎(bge-m3 onnx int8,1024 维)的身份描述构造器(weightsDigest 由调用方给指纹)。 */
131
131
  export function pyBgeM3IdentityDescPre(weightsDigest) {
132
132
  return {
133
- provider: 'bge-m3-onnx-int8-v1',
133
+ provider: 'bge-m3-onnx-int8-pre-v1',
134
134
  model: 'bge-m3',
135
135
  weightsDigest: weightsDigest || '',
136
136
  tokenizer: 'xenova-bge-m3-fast',
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 引擎切换状态机(engine_switch_v1)—— P2 / T2-9 切档隔离与进度条(V2-P2 卡 points 4/5)。
2
+ * 引擎切换状态机(engine_switch_pre_v1)—— P2 / T2-9 切档隔离与进度条(V2-P2 卡 points 4/5)。
3
3
  *
4
4
  * 权威依据(评审 §3.5 采纳条目 + V2-P2 卡):
5
5
  * · **切档 = 强制全量重建 + 进度条**(用户裁定):每次**实际切换**创建**新 rebuild generation**,
@@ -22,7 +22,7 @@
22
22
  * 零第三方依赖;IO 全注入(不传则纯内存);UTF-8 无 BOM。
23
23
  */
24
24
 
25
- export const ENGINE_SWITCH_VERSION = 'engine_switch_v1'
25
+ export const ENGINE_SWITCH_VERSION = 'engine_switch_pre_v1'
26
26
 
27
27
  /** 终态:到达后不再变化(除 begin 开新代)。 */
28
28
  const TERMINAL = new Set(['ready', 'failed', 'cancelled'])
@@ -117,7 +117,7 @@ export function createEngineSwitchPre(opts = {}) {
117
117
  const fromIdentity = String(input.fromIdentity || state.toIdentity || '').trim()
118
118
  const total = Number(input.total) > 0 ? Math.floor(Number(input.total)) : 0
119
119
  if (!switchId) return { ok: false, switchId: '', generation, reason: 'no-switch-id' }
120
- if (!/^engid_[0-9a-f]{32}$/.test(toIdentity)) return { ok: false, switchId, generation, reason: 'bad-identity' }
120
+ if (!/^engid_pre_[0-9a-f]{32}$/.test(toIdentity)) return { ok: false, switchId, generation, reason: 'bad-identity' }
121
121
  // 幂等:同 id 已完成 → 不重跑
122
122
  if (state.switchId === switchId && state.phase === 'ready' && state.toIdentity === toIdentity) {
123
123
  return { ok: true, switchId, generation: state.generation, reason: 'already-ready' }
@@ -26,21 +26,21 @@ import { createHash } from 'node:crypto'
26
26
 
27
27
  // ========== 冻结常量 ==========
28
28
 
29
- export const EPISODIC_POLICY_VERSION = 'episodic_store_v1'
30
- export const EPISODE_ID_PREFIX = 'epi_'
31
- export const EPISODE_ID_RE = /^epi_[0-9a-f]{32}$/
29
+ export const EPISODIC_POLICY_VERSION = 'episodic_store_pre_v1'
30
+ export const EPISODE_ID_PREFIX = 'epi_pre_'
31
+ export const EPISODE_ID_RE = /^epi_pre_[0-9a-f]{32}$/
32
32
 
33
33
  /** episode 累积上限(防无限增长;超过后最旧段被丢弃)。 */
34
- export const EPISODE_SEGMENT_CAP_V1 = 64
34
+ export const EPISODE_SEGMENT_CAP_PRE_V1 = 64
35
35
 
36
36
  /** 巩固后保留的最多 episode 数(超出按时间淘汰,保留最近)。 */
37
- export const EPISODE_RETENTION_V1 = 256
37
+ export const EPISODE_RETENTION_PRE_V1 = 256
38
38
 
39
39
  /** 巩固阈值:至少多少段才算一个可巩固 episode(少于=丢弃,噪声太多)。 */
40
- export const EPISODE_MIN_SEGMENTS_V1 = 2
40
+ export const EPISODE_MIN_SEGMENTS_PRE_V1 = 2
41
41
 
42
42
  /** outcome 枚举。 */
43
- export const EPISODE_OUTCOMES_V1 = Object.freeze(['unknown', 'success', 'failure', 'partial'])
43
+ export const EPISODE_OUTCOMES_PRE_V1 = Object.freeze(['unknown', 'success', 'failure', 'partial'])
44
44
 
45
45
  /**
46
46
  * EpisodeSegment 校验(append 输入)。
@@ -69,7 +69,7 @@ export function validateEpisodePre(ep) {
69
69
  if (!Array.isArray(ep.actions)) p.push('actions')
70
70
  if (!Array.isArray(ep.entities)) p.push('entities')
71
71
  if (!Array.isArray(ep.unresolved)) p.push('unresolved')
72
- if (!EPISODE_OUTCOMES_V1.includes(ep.outcome)) p.push('outcome')
72
+ if (!EPISODE_OUTCOMES_PRE_V1.includes(ep.outcome)) p.push('outcome')
73
73
  if (!Array.isArray(ep.provenance)) p.push('provenance')
74
74
  if (typeof ep.startedAt !== 'number' || !Number.isFinite(ep.startedAt)) p.push('startedAt')
75
75
  if (ep.consolidatedAt !== undefined && (typeof ep.consolidatedAt !== 'number' || !Number.isFinite(ep.consolidatedAt))) p.push('consolidatedAt')
@@ -89,9 +89,9 @@ export function createEpisodicStorePre(opts = {}) {
89
89
  const io = opts.io || { save() {}, load() { return null }, clear() {} }
90
90
  const nowFn = typeof opts.now === 'function' ? opts.now : () => Date.now()
91
91
  const cfg = Object.assign({
92
- segmentCap: EPISODE_SEGMENT_CAP_V1,
93
- retention: EPISODE_RETENTION_V1,
94
- minSegments: EPISODE_MIN_SEGMENTS_V1,
92
+ segmentCap: EPISODE_SEGMENT_CAP_PRE_V1,
93
+ retention: EPISODE_RETENTION_PRE_V1,
94
+ minSegments: EPISODE_MIN_SEGMENTS_PRE_V1,
95
95
  }, opts.config || {})
96
96
 
97
97
  let episodes = [] // 已巩固 episode(含 candidate 状态)
@@ -100,7 +100,7 @@ export function createEpisodicStorePre(opts = {}) {
100
100
  const stats = { segmentsAppended: 0, consolidated: 0, droppedTooShort: 0, retained: 0 }
101
101
 
102
102
  function defaultEpisodeId(sessionRef, startedAt) {
103
- const h = createHash('sha256').update(['episode-v1', String(sessionRef), String(startedAt)].join('\u0000')).digest('hex')
103
+ const h = createHash('sha256').update(['episode-pre-v1', String(sessionRef), String(startedAt)].join('\u0000')).digest('hex')
104
104
  return EPISODE_ID_PREFIX + h.slice(0, 32)
105
105
  }
106
106
 
@@ -335,7 +335,7 @@ export function createEpisodicStorePre(opts = {}) {
335
335
  // ---- 持久化 ----
336
336
  function snapshot() {
337
337
  return {
338
- schemaVersion: 1, namespace: 'dsh-auto-memory', policyVersion: EPISODIC_POLICY_VERSION,
338
+ schemaVersion: 1, namespace: 'dsh-auto-memory-pre', policyVersion: EPISODIC_POLICY_VERSION,
339
339
  savedAt: nowFn(),
340
340
  // ★ P2-2 修复:快照数组字段同样做副本(调用方改快照不得改脏库内 episode)
341
341
  episodes: episodes.map(cloneEpisode),
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * evidence-agg-pre —— evidence 事件 → 聚合 → importance 输入契约(M8-2b, 2026-09-09)。
3
3
  *
4
- * 管道:evidence/events/*.jsonl(写入侧 context-bridge-pre,只读)→ 有界扫描 →
4
+ * 管道:evidence-pre/events/*.jsonl(写入侧 context-bridge-pre,只读)→ 有界扫描 →
5
5
  * 按 memoryId 聚合六类计数 + distinctSessions → 交给 memory-importance-pre.computeImportancePre
6
6
  * → 作为 recall() L0 融合的加权因子之一(P8 融合入口)。
7
7
  *
@@ -14,10 +14,10 @@
14
14
  * - fail-soft:行损坏跳过;io 抛错由调用方处理(模块内不吞 IO 异常——io 是注入的,调用方决定降级)。
15
15
  */
16
16
 
17
- export const EVIDENCE_AGG_VERSION = 'evidence_agg_v1'
17
+ export const EVIDENCE_AGG_VERSION = 'evidence_agg_pre_v1'
18
18
 
19
19
  /** 有界读取默认值。 */
20
- export const EVIDENCE_AGG_DEFAULTS_V1 = Object.freeze({
20
+ export const EVIDENCE_AGG_DEFAULTS_PRE_V1 = Object.freeze({
21
21
  maxAgeDays: 7, // 文件名日期距 now 的最大天数
22
22
  maxLinesPerFile: 400, // 每文件末 N 行(沿用 index.js 证据读取范式 slice(-400))
23
23
  })
@@ -31,7 +31,7 @@ const KINDS = ['seen', 'read', 'cite', 'reuse', 'success', 'correction']
31
31
  * @returns {Array<object>} 解析后的事件对象(损坏行跳过;任何字段缺失由聚合层兜底)
32
32
  */
33
33
  export function scanEvidenceEventsPre(io, opts = {}) {
34
- const d = Object.assign({}, EVIDENCE_AGG_DEFAULTS_V1, opts)
34
+ const d = Object.assign({}, EVIDENCE_AGG_DEFAULTS_PRE_V1, opts)
35
35
  const now = d.now || Date.now
36
36
  const files = (io.listFiles() || []).slice().sort()
37
37
  const cutoff = now() - d.maxAgeDays * 86400000
@@ -16,9 +16,9 @@ const sha256Str = (s) => createHash('sha256').update(Buffer.from(String(s), 'utf
16
16
  const first32 = (h) => h.slice(0, 32)
17
17
 
18
18
  /** §10 持久化策略(冻结)。 */
19
- export const EVIDENCE_STORE_POLICY_V1 = Object.freeze({
19
+ export const EVIDENCE_STORE_POLICY_PRE_V1 = Object.freeze({
20
20
  schemaVersion: 1,
21
- storePolicyVersion: 'evidence_store_v1',
21
+ storePolicyVersion: 'evidence_store_pre_v1',
22
22
  keepDays: 30,
23
23
  maxTotalBytes: 32 * 1024 * 1024,
24
24
  eventMaxBytes: 16 * 1024,
@@ -32,12 +32,12 @@ export function canonicalWorkspaceKey(key) {
32
32
 
33
33
  /** §10 隐私投影:sessionId → plugin-local sessionRef(不可逆哈希;跨文件稳定)。 */
34
34
  export function sessionRefOf(sessionId) {
35
- return 'sesr_' + first32(sha256Str('evidence-sesref-v1\u0000' + String(sessionId || '')))
35
+ return 'sesr_' + first32(sha256Str('evidence-sesref-pre-v1\u0000' + String(sessionId || '')))
36
36
  }
37
37
 
38
38
  /** §10 隐私投影:workspace 绝对键 → 稳定 workspaceRef(不落盘任何绝对路径)。 */
39
39
  export function workspaceRefOf(workspaceKey) {
40
- return 'wsr_' + first32(sha256Str('evidence-wsref-v1\u0000' + canonicalWorkspaceKey(workspaceKey)))
40
+ return 'wsr_' + first32(sha256Str('evidence-wsref-pre-v1\u0000' + canonicalWorkspaceKey(workspaceKey)))
41
41
  }
42
42
 
43
43
  /**
@@ -48,11 +48,11 @@ export function workspaceRefOf(workspaceKey) {
48
48
  export function projectEvidenceForDurable(ev, opts = {}) {
49
49
  const v = validateAccessEvidencePre(ev)
50
50
  if (!v.ok) return { ok: false, reason: 'invalid-evidence:' + v.reason }
51
- const maxBytes = Number(opts.eventMaxBytes) || EVIDENCE_STORE_POLICY_V1.eventMaxBytes
51
+ const maxBytes = Number(opts.eventMaxBytes) || EVIDENCE_STORE_POLICY_PRE_V1.eventMaxBytes
52
52
  const projected = {
53
53
  schemaVersion: 1,
54
54
  namespace: NAMESPACE,
55
- storePolicyVersion: EVIDENCE_STORE_POLICY_V1.storePolicyVersion,
55
+ storePolicyVersion: EVIDENCE_STORE_POLICY_PRE_V1.storePolicyVersion,
56
56
  evidenceId: ev.evidenceId,
57
57
  kind: ev.kind,
58
58
  memoryId: ev.memoryId,
@@ -101,12 +101,12 @@ export function parseDurableEventLine(line) {
101
101
  * 表放**模块级**而非实例级:同进程多实例共享 ⇒ 不会各自持一份而双写。
102
102
  * 语义同 index.js 的 diagThrottled:首条立即透出,窗口内重复压掉。
103
103
  */
104
- const DIAG_THROTTLE_MS_V1 = 300000
104
+ const DIAG_THROTTLE_MS_PRE_V1 = 300000
105
105
  const _diagAt = new Map()
106
106
  function shouldReportDiag(key) {
107
107
  try {
108
108
  const now = Date.now()
109
- if (now - (Number(_diagAt.get(key)) || 0) < DIAG_THROTTLE_MS_V1) return false
109
+ if (now - (Number(_diagAt.get(key)) || 0) < DIAG_THROTTLE_MS_PRE_V1) return false
110
110
  _diagAt.set(key, now)
111
111
  } catch (_) { /* 计时失败不阻断上报 */ }
112
112
  return true
@@ -117,7 +117,7 @@ export class EvidenceEventStore {
117
117
  this.root = opts.root || null
118
118
  this.eventsDir = opts.eventsDir || (this.root ? path.join(this.root, 'events') : null)
119
119
  this._chain = Promise.resolve()
120
- this._appended = new BoundedIdSet(EVIDENCE_STORE_POLICY_V1.appendedIdCache)
120
+ this._appended = new BoundedIdSet(EVIDENCE_STORE_POLICY_PRE_V1.appendedIdCache)
121
121
  this._swept = false
122
122
  this.stats = { appended: 0, duplicates: 0, oversize: 0, invalid: 0, writeFailed: 0, sweptFiles: 0, lastWriteError: null }
123
123
  // ★P3-9(组2):可注入诊断通道(宿主不注入时 reportDiag 回落 console.error,行为向后兼容)。
@@ -189,7 +189,7 @@ export class EvidenceEventStore {
189
189
  files.sort((a, b) => a.mtimeMs - b.mtimeMs)
190
190
  for (const f of files) {
191
191
  const ageDays = (now - f.mtimeMs) / 86400000
192
- if (ageDays > EVIDENCE_STORE_POLICY_V1.keepDays || total > EVIDENCE_STORE_POLICY_V1.maxTotalBytes) {
192
+ if (ageDays > EVIDENCE_STORE_POLICY_PRE_V1.keepDays || total > EVIDENCE_STORE_POLICY_PRE_V1.maxTotalBytes) {
193
193
  try { rmSync(f.fp, { force: true }); this.stats.sweptFiles++; total -= f.size } catch (_) {}
194
194
  } else break
195
195
  }
@@ -295,11 +295,11 @@ export function rebuildAggregates(durableEvents, corpusRecords) {
295
295
  return { aggregates, byWorkspaceRef, duplicates, sessionScoped }
296
296
  }
297
297
 
298
- /** aggregate 快照持久化(evidence/aggregates/index.json;可重建派生物,仅调试/导出用)。 */
298
+ /** aggregate 快照持久化(evidence-pre/aggregates/index.json;可重建派生物,仅调试/导出用)。 */
299
299
  export function persistAggregatesSnapshot(root, aggregates) {
300
300
  const dir = path.join(root, 'aggregates')
301
301
  mkdirSync(dir, { recursive: true })
302
- const payload = { schemaVersion: 1, namespace: NAMESPACE, storePolicyVersion: EVIDENCE_STORE_POLICY_V1.storePolicyVersion, rebuiltAt: Date.now(), aggregates }
302
+ const payload = { schemaVersion: 1, namespace: NAMESPACE, storePolicyVersion: EVIDENCE_STORE_POLICY_PRE_V1.storePolicyVersion, rebuiltAt: Date.now(), aggregates }
303
303
  writeFileSync(path.join(dir, 'index.json'), JSON.stringify(payload, null, 2) + '\n', 'utf8')
304
304
  return path.join(dir, 'index.json')
305
305
  }
package/lib/fact-store.js CHANGED
@@ -39,38 +39,38 @@ import { createHash } from 'node:crypto'
39
39
  // ========== 冻结常量(变更必须升级 FACT_POLICY_VERSION) ==========
40
40
 
41
41
  /** Fact 策略版本。 */
42
- export const FACT_POLICY_VERSION = 'fact_store_v1'
42
+ export const FACT_POLICY_VERSION = 'fact_store_pre_v1'
43
43
 
44
44
  /** factId 前缀(确定性身份)。 */
45
- export const FACT_ID_PREFIX = 'fact_'
45
+ export const FACT_ID_PREFIX = 'fact_pre_'
46
46
 
47
47
  /** factId 长度前缀断言(与 memoryId/anchorId 同风格)。 */
48
- export const FACT_ID_RE = /^fact_[0-9a-f]{32}$/
48
+ export const FACT_ID_RE = /^fact_pre_[0-9a-f]{32}$/
49
49
 
50
50
  /** scope 枚举: 与 M5/M6 的 scope 对齐(User=跨工作区, Workspace=当前工作区)。 */
51
- export const FACT_SCOPES_V1 = Object.freeze(['User', 'Workspace'])
51
+ export const FACT_SCOPES_PRE_V1 = Object.freeze(['User', 'Workspace'])
52
52
 
53
53
  /** M8-1 认识论状态枚举: fact=证据/不可推导, observation=推断/可重算, directive=行为指令。 */
54
- export const FACT_EPISTEMIC_STATUSES_V1 = Object.freeze(['fact', 'observation', 'directive'])
54
+ export const FACT_EPISTEMIC_STATUSES_PRE_V1 = Object.freeze(['fact', 'observation', 'directive'])
55
55
 
56
56
  /** M8-1 趋势枚举: 相对上次确认的生命周期走向。 */
57
- export const FACT_TRENDS_V1 = Object.freeze(['new', 'strengthening', 'stable', 'weakening', 'stale'])
57
+ export const FACT_TRENDS_PRE_V1 = Object.freeze(['new', 'strengthening', 'stable', 'weakening', 'stale'])
58
58
 
59
59
  /** sourceClass 枚举: 与 m4-corpus-pre 的 sourceClass 对齐。 */
60
- export const FACT_SOURCE_CLASSES_V1 = Object.freeze([
60
+ export const FACT_SOURCE_CLASSES_PRE_V1 = Object.freeze([
61
61
  'user-memory', 'workspace-notes', 'workspace-log', 'semantic-candidate', 'profile-candidate',
62
62
  ])
63
63
 
64
64
  /** 来源类型: 用户明确声明(硬规则) vs 模型推断(candidate)。 */
65
- export const FACT_SOURCE_KINDS_V1 = Object.freeze(['explicit', 'inference'])
65
+ export const FACT_SOURCE_KINDS_PRE_V1 = Object.freeze(['explicit', 'inference'])
66
66
 
67
67
  /** 冲突处理结果枚举。 */
68
- export const UPSERT_OUTCOMES_V1 = Object.freeze([
68
+ export const UPSERT_OUTCOMES_PRE_V1 = Object.freeze([
69
69
  'created', 'merged', 'superseded', 'conflict-added', 'inference-blocked', 'expired-ignored',
70
70
  ])
71
71
 
72
72
  /** TTL 语义: 过期事实视为不存在(读取时过滤),但保留记录供审计。 */
73
- export const FACT_TTL_DEFAULT_V1 = 0 // 0 = 永不过期
73
+ export const FACT_TTL_DEFAULT_PRE_V1 = 0 // 0 = 永不过期
74
74
 
75
75
  /**
76
76
  * FactCandidate 校验(fail closed)。
@@ -79,12 +79,12 @@ export const FACT_TTL_DEFAULT_V1 = 0 // 0 = 永不过期
79
79
  export function validateFactCandidatePre(cand) {
80
80
  const p = []
81
81
  if (!cand || typeof cand !== 'object' || Array.isArray(cand)) return { ok: false, reason: 'not-object' }
82
- if (!FACT_SCOPES_V1.includes(cand.scope)) p.push('scope')
82
+ if (!FACT_SCOPES_PRE_V1.includes(cand.scope)) p.push('scope')
83
83
  if (typeof cand.subject !== 'string' || !cand.subject.trim()) p.push('subject')
84
84
  if (typeof cand.predicate !== 'string' || !cand.predicate.trim()) p.push('predicate')
85
85
  if (cand.object !== undefined && cand.object !== null && typeof cand.object !== 'string') p.push('object')
86
- if (!FACT_SOURCE_KINDS_V1.includes(cand.sourceKind)) p.push('sourceKind')
87
- if (cand.sourceClass !== undefined && !FACT_SOURCE_CLASSES_V1.includes(cand.sourceClass)) p.push('sourceClass')
86
+ if (!FACT_SOURCE_KINDS_PRE_V1.includes(cand.sourceKind)) p.push('sourceKind')
87
+ if (cand.sourceClass !== undefined && !FACT_SOURCE_CLASSES_PRE_V1.includes(cand.sourceClass)) p.push('sourceClass')
88
88
  if (cand.provenance !== undefined && (!Array.isArray(cand.provenance) || cand.provenance.some((s) => typeof s !== 'string'))) p.push('provenance')
89
89
  if (cand.confidence !== undefined && (typeof cand.confidence !== 'number' || !Number.isFinite(cand.confidence) || cand.confidence < 0 || cand.confidence > 1)) p.push('confidence')
90
90
  if (cand.ttl !== undefined && (typeof cand.ttl !== 'number' || !Number.isFinite(cand.ttl) || cand.ttl < 0)) p.push('ttl')
@@ -92,8 +92,8 @@ export function validateFactCandidatePre(cand) {
92
92
  for (const k of ['occurredAt', 'mentionedAt', 'ingestedAt']) {
93
93
  if (cand[k] !== undefined && (typeof cand[k] !== 'number' || !Number.isFinite(cand[k]) || cand[k] < 0)) p.push(k)
94
94
  }
95
- if (cand.epistemicStatus !== undefined && !FACT_EPISTEMIC_STATUSES_V1.includes(cand.epistemicStatus)) p.push('epistemicStatus')
96
- if (cand.trend !== undefined && !FACT_TRENDS_V1.includes(cand.trend)) p.push('trend')
95
+ if (cand.epistemicStatus !== undefined && !FACT_EPISTEMIC_STATUSES_PRE_V1.includes(cand.epistemicStatus)) p.push('epistemicStatus')
96
+ if (cand.trend !== undefined && !FACT_TRENDS_PRE_V1.includes(cand.trend)) p.push('trend')
97
97
  if (p.length) return { ok: false, reason: 'invalid:' + p.join(',') }
98
98
  return { ok: true, candidate: cand }
99
99
  }
@@ -106,7 +106,7 @@ export function validateFactPre(fact) {
106
106
  const p = []
107
107
  if (!fact || typeof fact !== 'object' || Array.isArray(fact)) return { ok: false, reason: 'not-object' }
108
108
  if (typeof fact.factId !== 'string' || !FACT_ID_RE.test(fact.factId)) p.push('factId')
109
- if (!FACT_SCOPES_V1.includes(fact.scope)) p.push('scope')
109
+ if (!FACT_SCOPES_PRE_V1.includes(fact.scope)) p.push('scope')
110
110
  if (typeof fact.subject !== 'string' || !fact.subject.trim()) p.push('subject')
111
111
  if (typeof fact.predicate !== 'string' || !fact.predicate.trim()) p.push('predicate')
112
112
  if (fact.object !== undefined && fact.object !== null && typeof fact.object !== 'string') p.push('object')
@@ -118,8 +118,8 @@ export function validateFactPre(fact) {
118
118
  for (const k of ['occurredAt', 'mentionedAt', 'ingestedAt']) {
119
119
  if (fact[k] !== undefined && (typeof fact[k] !== 'number' || !Number.isFinite(fact[k]))) p.push(k)
120
120
  }
121
- if (fact.epistemicStatus !== undefined && !FACT_EPISTEMIC_STATUSES_V1.includes(fact.epistemicStatus)) p.push('epistemicStatus')
122
- if (fact.trend !== undefined && !FACT_TRENDS_V1.includes(fact.trend)) p.push('trend')
121
+ if (fact.epistemicStatus !== undefined && !FACT_EPISTEMIC_STATUSES_PRE_V1.includes(fact.epistemicStatus)) p.push('epistemicStatus')
122
+ if (fact.trend !== undefined && !FACT_TRENDS_PRE_V1.includes(fact.trend)) p.push('trend')
123
123
  if (p.length) return { ok: false, reason: 'invalid:' + p.join(',') }
124
124
  return { ok: true, fact }
125
125
  }
@@ -139,15 +139,15 @@ export function isFactConflict(a, b) {
139
139
  // ========== B-3 事实性入口判据(保守版:只拒「明显不是事实陈述」的形态) ==========
140
140
 
141
141
  /** 陈述文本最小长度(去空白后);低于此值且呈散文形态才判为碎片。 */
142
- export const FACT_MIN_STATEMENT_CHARS_V1 = 6
142
+ export const FACT_MIN_STATEMENT_CHARS_PRE_V1 = 6
143
143
 
144
144
  /** 判据子类码(冻结;前端/工具按它给中文说明,不要按 code 反推语义)。 */
145
- export const FACTNESS_CODES_V1 = Object.freeze([
145
+ export const FACTNESS_CODES_PRE_V1 = Object.freeze([
146
146
  'factness-empty', 'factness-symbol-only', 'factness-question', 'factness-imperative', 'factness-too-short',
147
147
  ])
148
148
 
149
149
  /** 子类码 → 中文人话说明:既让模型知道「怎么改写成合法陈述」,也让人不看代码就看得懂。 */
150
- export const FACTNESS_MESSAGES_V1 = Object.freeze({
150
+ export const FACTNESS_MESSAGES_PRE_V1 = Object.freeze({
151
151
  'factness-empty': '内容为空白(没有任何字符),不是一条事实陈述。请写成「主体 + 谓词 (+ 宾语)」再入库,例如 主体=「项目」、谓词=「构建工具」、宾语=「esbuild」。',
152
152
  'factness-symbol-only': '内容只有标点、符号或表情,没有可读的文字或数字,不是一条事实陈述。请补上文字描述再入库,例如 主体=「项目」、谓词=「状态」、宾语=「进行中」。',
153
153
  'factness-question': '内容是一个疑问句(以「?」或「?」结尾,且整句没有陈述成分),不是一条事实陈述。请先把答案写成陈述句再入库,例如写「端口 默认值 3080」,而不是「端口是多少?」。',
@@ -156,7 +156,7 @@ export const FACTNESS_MESSAGES_V1 = Object.freeze({
156
156
  })
157
157
 
158
158
  /** 判据子类码 → stats 计数键(诊断面可分别读数)。 */
159
- const FACTNESS_STAT_KEYS_V1 = Object.freeze({
159
+ const FACTNESS_STAT_KEYS_PRE_V1 = Object.freeze({
160
160
  'factness-empty': 'factnessEmpty',
161
161
  'factness-symbol-only': 'factnessSymbolOnly',
162
162
  'factness-question': 'factnessQuestion',
@@ -165,35 +165,35 @@ const FACTNESS_STAT_KEYS_V1 = Object.freeze({
165
165
  })
166
166
 
167
167
  /** 认识论状态强弱序:推断(observation) < 明确陈述(fact/directive)。 */
168
- const FACT_EPISTEMIC_RANK_V1 = Object.freeze({ observation: 1, fact: 2, directive: 2 })
168
+ const FACT_EPISTEMIC_RANK_PRE_V1 = Object.freeze({ observation: 1, fact: 2, directive: 2 })
169
169
 
170
170
  /** 有词形字符(拉丁/数字/CJK/假名/谚文/西里尔/希腊/全角字母数字)才算「有内容」。 */
171
- const FACT_WORD_CHAR_RE_V1 = /[0-9A-Za-z\u00C0-\u024F\u0370-\u03FF\u0400-\u04FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A]/
171
+ const FACT_WORD_CHAR_RE_PRE_V1 = /[0-9A-Za-z\u00C0-\u024F\u0370-\u03FF\u0400-\u04FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A]/
172
172
 
173
173
  /** 句读/符号:字段里出现即视为「散文残片」,而不是结构化词条(空白 + ASCII 标点 + 中西标点/表情区)。 */
174
- const FACT_PROSE_PUNCT_RE_V1 = /[\s!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~\u2000-\u206F\u2190-\u2BFF\u3000-\u303F\uD800-\uDFFF\uFE10-\uFE1F\uFE30-\uFE6F\uFF01-\uFF0F\uFF1A-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65]/
174
+ const FACT_PROSE_PUNCT_RE_PRE_V1 = /[\s!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~\u2000-\u206F\u2190-\u2BFF\u3000-\u303F\uD800-\uDFFF\uFE10-\uFE1F\uFE30-\uFE6F\uFF01-\uFF0F\uFF1A-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65]/
175
175
 
176
176
  /** 只含中日韩文字(用于「英文祈使开头只在纯英文文本上生效」这条护栏)。 */
177
- const FACT_CJK_CHAR_RE_V1 = /[\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF\u3000-\u303F\uFF00-\uFFEF]/
177
+ const FACT_CJK_CHAR_RE_PRE_V1 = /[\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF\u3000-\u303F\uFF00-\uFFEF]/
178
178
 
179
179
  /** 疑问尾:「?」或「?」结尾(且整句无陈述成分时判为疑问)。 */
180
- const FACT_QUESTION_TAIL_RE_V1 = /[??]\s*$/
180
+ const FACT_QUESTION_TAIL_RE_PRE_V1 = /[??]\s*$/
181
181
 
182
182
  /** 陈述成分标记:出现任一即认为「有主谓/已成立」⇒ 一律放行(宁可放过不可误杀)。 */
183
- const FACT_CLAUSE_MARKER_RE_V1 = /(是|为|在|有|了|被|过|会|能|需|由|得|着|将|已|后|前|时|与|和|及|即|属|含|指|表示|代表|完成|支持|启用|关闭|默认|等于|位于|\b(is|are|was|were|be|been|has|have|had|will|would|can|could|must|should|equals?|means|contains|supports|defaults?|uses?)\b)/i
183
+ const FACT_CLAUSE_MARKER_RE_PRE_V1 = /(是|为|在|有|了|被|过|会|能|需|由|得|着|将|已|后|前|时|与|和|及|即|属|含|指|表示|代表|完成|支持|启用|关闭|默认|等于|位于|\b(is|are|was|were|be|been|has|have|had|will|would|can|could|must|should|equals?|means|contains|supports|defaults?|uses?)\b)/i
184
184
 
185
185
  /** 疑问/系词复合:先摘掉,免得「为什么」里的「为」被误当成陈述标记。 */
186
- const FACT_CLAUSE_STRIP_RE_V1 = /(为什么|为何|什么样|什么时候|因为|认为|以为|视为|作为|属于|分为)/g
186
+ const FACT_CLAUSE_STRIP_RE_PRE_V1 = /(为什么|为何|什么样|什么时候|因为|认为|以为|视为|作为|属于|分为)/g
187
187
 
188
188
  /** 祈使开头:这些形态**且整句无陈述成分**时才判为指令(请求式英文词始终生效)。 */
189
- const FACT_IMPERATIVE_OPENERS_V1 = Object.freeze([
189
+ const FACT_IMPERATIVE_OPENERS_PRE_V1 = Object.freeze([
190
190
  /^(帮我|帮忙|帮个忙|麻烦|拜托|烦请|给我|替我|请帮|请把|请将|请给|请写|请记|请查|请修|请改|请加|请添|请看|请做)/,
191
191
  /^把[^,,。.;;??!!]{0,12}(改成|改为|换成|换为|调成|调为|设置为|设为|调整|删除|删掉|去掉|加上|添加|补上|写上|写成)/,
192
192
  /^(please|pls|plz|help me|can you|could you|would you|i need you to|i want you to|go ahead and|make sure|remember to|do not|don't)\b/i,
193
193
  ])
194
194
 
195
195
  /** 裸动词祈使开头:**仅纯英文文本**才生效(中式事实句「Run 命令:…」不得被误杀)。 */
196
- const FACT_IMP_VERB_OPENERS_EN_V1 = /^(add|change|update|fix|remove|delete|rename|move|run|write|create|implement|refactor|ensure|revert|bump|install)\b/i
196
+ const FACT_IMP_VERB_OPENERS_EN_PRE_V1 = /^(add|change|update|fix|remove|delete|rename|move|run|write|create|implement|refactor|ensure|revert|bump|install)\b/i
197
197
 
198
198
  /** 去掉全部空白(长度与词形判定用)。 */
199
199
  function factFlatPre(text) {
@@ -202,14 +202,14 @@ function factFlatPre(text) {
202
202
 
203
203
  /** 整句是否带陈述成分(带 ⇒ 放行信号)。 */
204
204
  function factHasClausePre(text) {
205
- const s = String(text == null ? '' : text).replace(FACT_CLAUSE_STRIP_RE_V1, ' ')
206
- return FACT_CLAUSE_MARKER_RE_V1.test(s)
205
+ const s = String(text == null ? '' : text).replace(FACT_CLAUSE_STRIP_RE_PRE_V1, ' ')
206
+ return FACT_CLAUSE_MARKER_RE_PRE_V1.test(s)
207
207
  }
208
208
 
209
209
  /** 整句是否「只有疑问」:以 ?/? 结尾且没有陈述成分。 */
210
210
  function factQuestionOnlyPre(text) {
211
211
  const s = String(text == null ? '' : text).trim()
212
- if (!FACT_QUESTION_TAIL_RE_V1.test(s)) return false
212
+ if (!FACT_QUESTION_TAIL_RE_PRE_V1.test(s)) return false
213
213
  return !factHasClausePre(s)
214
214
  }
215
215
 
@@ -217,8 +217,8 @@ function factQuestionOnlyPre(text) {
217
217
  function factImperativeOnlyPre(text) {
218
218
  const s = String(text == null ? '' : text).trim()
219
219
  if (!s) return false
220
- if (FACT_IMPERATIVE_OPENERS_V1.some((re) => re.test(s))) return !factHasClausePre(s)
221
- if (!FACT_CJK_CHAR_RE_V1.test(s) && FACT_IMP_VERB_OPENERS_EN_V1.test(s)) return !factHasClausePre(s)
220
+ if (FACT_IMPERATIVE_OPENERS_PRE_V1.some((re) => re.test(s))) return !factHasClausePre(s)
221
+ if (!FACT_CJK_CHAR_RE_PRE_V1.test(s) && FACT_IMP_VERB_OPENERS_EN_PRE_V1.test(s)) return !factHasClausePre(s)
222
222
  return false
223
223
  }
224
224
 
@@ -227,7 +227,7 @@ function factTermLikeFieldPre(v) {
227
227
  if (typeof v !== 'string') return true
228
228
  const s = v.trim()
229
229
  if (!s) return true
230
- return !FACT_PROSE_PUNCT_RE_V1.test(s)
230
+ return !FACT_PROSE_PUNCT_RE_PRE_V1.test(s)
231
231
  }
232
232
 
233
233
  /** 三元组是否「结构化短词条」(每个字段都是词条形态)。 */
@@ -238,7 +238,7 @@ function factStructuredTuplePre(cand) {
238
238
 
239
239
  /** 组装一个拒绝判定(code + 中文人话 message)。 */
240
240
  function factnessVerdictPre(code) {
241
- return { ok: false, code, message: FACTNESS_MESSAGES_V1[code] || code }
241
+ return { ok: false, code, message: FACTNESS_MESSAGES_PRE_V1[code] || code }
242
242
  }
243
243
 
244
244
  /**
@@ -252,10 +252,10 @@ export function looksFactStatementPre(text) {
252
252
  const raw = String(text == null ? '' : text).trim()
253
253
  const flat = raw.replace(/\s+/g, '')
254
254
  if (!flat) return factnessVerdictPre('factness-empty')
255
- if (!FACT_WORD_CHAR_RE_V1.test(flat)) return factnessVerdictPre('factness-symbol-only')
255
+ if (!FACT_WORD_CHAR_RE_PRE_V1.test(flat)) return factnessVerdictPre('factness-symbol-only')
256
256
  if (factQuestionOnlyPre(raw)) return factnessVerdictPre('factness-question')
257
257
  if (factImperativeOnlyPre(raw)) return factnessVerdictPre('factness-imperative')
258
- if (flat.length < FACT_MIN_STATEMENT_CHARS_V1) return factnessVerdictPre('factness-too-short')
258
+ if (flat.length < FACT_MIN_STATEMENT_CHARS_PRE_V1) return factnessVerdictPre('factness-too-short')
259
259
  return { ok: true }
260
260
  }
261
261
 
@@ -302,7 +302,7 @@ export function looksFactCandidatePre(cand) {
302
302
  * 调用方显式给了合法值就尊重(本函数只在缺省时用);
303
303
  * 来源 = 用户明确陈述(sourceKind='explicit' / sourceClass='user-memory' / origin='user')→ 'fact'(明说);
304
304
  * 其余(模型推断、自动沉淀、judgement 候选)→ 'observation'(推断)。
305
- * ⚠️ 取值必须落在冻结枚举 FACT_EPISTEMIC_STATUSES_V1 内 —— 写 'inference'/'explicit'
305
+ * ⚠️ 取值必须落在冻结枚举 FACT_EPISTEMIC_STATUSES_PRE_V1 内 —— 写 'inference'/'explicit'
306
306
  * 这类枚举外的字面量会被 validateFactPre 判非法并**整条拒收**,不能用。
307
307
  */
308
308
  export function defaultEpistemicStatusPre(cand) {
@@ -341,7 +341,7 @@ export function createFactStorePre(opts = {}) {
341
341
  // ---- 确定性 factId(与 memoryId/anchorId 同风格) ----
342
342
  function defaultFactId(subject, predicate, object, scope) {
343
343
  const h = createHash('sha256').update(
344
- ['fact-v1', String(scope), String(subject), String(predicate), String(object == null ? '' : object)].join('\u0000')
344
+ ['fact-pre-v1', String(scope), String(subject), String(predicate), String(object == null ? '' : object)].join('\u0000')
345
345
  ).digest('hex')
346
346
  return FACT_ID_PREFIX + h.slice(0, 32)
347
347
  }
@@ -406,8 +406,8 @@ export function createFactStorePre(opts = {}) {
406
406
  if (c.epistemicStatus !== undefined) {
407
407
  // ★ B-4(2026-09-22):认识论状态跟随候选(入口 :~250 已补默认值),但**降级**要挡住 ——
408
408
  // 用户明确陈述(fact/directive)不得被后续模型推断(observation)覆盖(与上面 sourceKind 的提升同向)。
409
- const prevRank = FACT_EPISTEMIC_RANK_V1[prev.epistemicStatus] || 0
410
- const candRank = FACT_EPISTEMIC_RANK_V1[c.epistemicStatus] || 0
409
+ const prevRank = FACT_EPISTEMIC_RANK_PRE_V1[prev.epistemicStatus] || 0
410
+ const candRank = FACT_EPISTEMIC_RANK_PRE_V1[c.epistemicStatus] || 0
411
411
  if (prev.epistemicStatus === undefined || c.sourceKind === 'explicit' || candRank > prevRank) {
412
412
  prev.epistemicStatus = c.epistemicStatus
413
413
  }
@@ -442,7 +442,7 @@ export function createFactStorePre(opts = {}) {
442
442
  const factness = looksFactCandidatePre(c)
443
443
  if (!factness.ok) {
444
444
  stats.factnessRejected++
445
- const statKey = FACTNESS_STAT_KEYS_V1[factness.code]
445
+ const statKey = FACTNESS_STAT_KEYS_PRE_V1[factness.code]
446
446
  if (statKey) stats[statKey]++
447
447
  lastFactnessReject = { code: factness.code, message: factness.message, text: factStatementTextPre(c), at: now }
448
448
  return {
@@ -510,7 +510,7 @@ export function createFactStorePre(opts = {}) {
510
510
  sourceClass: c.sourceClass || (c.sourceKind === 'explicit' ? 'user-memory' : 'semantic-candidate'),
511
511
  provenance: c.provenance || [],
512
512
  confidence: c.confidence !== undefined ? c.confidence : null,
513
- confirmedAt: now, ttl: c.ttl !== undefined ? c.ttl : FACT_TTL_DEFAULT_V1,
513
+ confirmedAt: now, ttl: c.ttl !== undefined ? c.ttl : FACT_TTL_DEFAULT_PRE_V1,
514
514
  revoked: false,
515
515
  // M8-1 时间三价:入库时间必填(=本次确认);发生/陈述时间候选提供则透传,缺省 undefined(落盘时省略)
516
516
  ingestedAt: now,
@@ -737,7 +737,7 @@ export function createFactStorePre(opts = {}) {
737
737
  function snapshot(opts = {}) {
738
738
  const now = nowFn()
739
739
  return {
740
- schemaVersion: 1, namespace: 'dsh-auto-memory', policyVersion: FACT_POLICY_VERSION,
740
+ schemaVersion: 1, namespace: 'dsh-auto-memory-pre', policyVersion: FACT_POLICY_VERSION,
741
741
  savedAt: now,
742
742
  // ★ P2-2 修复:快照同样不得与库内对象共享 provenance 数组引用
743
743
  // (宿主读快照后 push 会改脏已入账记录)。
@@ -1,5 +1,5 @@
1
1
  /**
2
- * P6(2026-09-09) 账本权重化截断纯核心(handoff_ledger_weight_v1)。
2
+ * P6(2026-09-09) 账本权重化截断纯核心(handoff_ledger_weight_pre_v1)。
3
3
  *
4
4
  * 背景:buildContinueCarry 的 `ledger.slice(0, 8000)` 是位置截断,而账本段内异质——
5
5
  * 位置截可能把高价值段整体截掉(M-CM7 §C/§G4)。本模块按账本自身四段标题赋权:
@@ -12,17 +12,17 @@
12
12
  * - 只影响注入,不动文件原文。
13
13
  */
14
14
 
15
- export const HANDOFF_LEDGER_WEIGHT_VERSION = 'handoff_ledger_weight_v1'
15
+ export const HANDOFF_LEDGER_WEIGHT_VERSION = 'handoff_ledger_weight_pre_v1'
16
16
 
17
17
  /** 四段标题 → 权重(降序)。标题为账本中的原样标题行(半角 '## '+单空格,不含行尾 \r)。 */
18
- export const HANDOFF_LEDGER_SECTION_WEIGHTS_V1 = Object.freeze([
18
+ export const HANDOFF_LEDGER_SECTION_WEIGHTS_PRE_V1 = Object.freeze([
19
19
  Object.freeze({ title: '已试方案与失败原因', weight: 0.35 }),
20
20
  Object.freeze({ title: '进度与下一步', weight: 0.30 }),
21
21
  Object.freeze({ title: '目标', weight: 0.20 }),
22
22
  Object.freeze({ title: '任务状态', weight: 0.15 }),
23
23
  ])
24
24
 
25
- const TITLE_WEIGHT = new Map(HANDOFF_LEDGER_SECTION_WEIGHTS_V1.map((x) => [x.title, x.weight]))
25
+ const TITLE_WEIGHT = new Map(HANDOFF_LEDGER_SECTION_WEIGHTS_PRE_V1.map((x) => [x.title, x.weight]))
26
26
  /** 未知 '## ' 段:权重最低(最先被截),但仍保留标题与内容直到轮到它。 */
27
27
  const UNKNOWN_WEIGHT = 0.05
28
28
  const TRIM_MARK = '…(已按预算截断,全文见 handoff/ 最新账本)'