memory-eternal 0.2.0 → 0.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.
- package/index.js +10 -1
- package/lib/client.js +5 -5
- package/lib/db.js +52 -0
- package/lib/vault.js +16 -6
- package/package.json +1 -1
- package/web/app.js +8 -8
package/lib/db.js
CHANGED
|
@@ -70,9 +70,61 @@ function initTables(db) {
|
|
|
70
70
|
useful INTEGER NOT NULL DEFAULT 0,
|
|
71
71
|
created_at TEXT DEFAULT (datetime('now'))
|
|
72
72
|
);
|
|
73
|
+
|
|
74
|
+
-- 审核日志:记录所有 status 变更,不可篡改
|
|
75
|
+
CREATE TABLE IF NOT EXISTS audit_log (
|
|
76
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
77
|
+
card_path TEXT NOT NULL,
|
|
78
|
+
old_status TEXT,
|
|
79
|
+
new_status TEXT NOT NULL,
|
|
80
|
+
changed_by TEXT DEFAULT 'system',
|
|
81
|
+
reason TEXT DEFAULT '',
|
|
82
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
83
|
+
);
|
|
73
84
|
`)
|
|
74
85
|
}
|
|
75
86
|
|
|
87
|
+
/**
|
|
88
|
+
* 审核守卫:根据 config 表的审核规则决定新卡 status。
|
|
89
|
+
* 所有写入路径必须经过此函数,杜绝越权。
|
|
90
|
+
*
|
|
91
|
+
* 规则:
|
|
92
|
+
* - config 表无 auditMode 记录 → 用 fallback(调用方传入的 status)
|
|
93
|
+
* - auditMode = 'none' → approved(全部免审)
|
|
94
|
+
* - auditMode = 'all' → 检查免审白名单,命中→approved,否则→pending
|
|
95
|
+
*
|
|
96
|
+
* @param {string} root - vault 根目录
|
|
97
|
+
* @param {string} kind - 卡片类型
|
|
98
|
+
* @param {string} submittedBy - 提交者
|
|
99
|
+
* @param {string} fallback - 调用方传入的 status(无配置时使用)
|
|
100
|
+
* @returns {'pending' | 'approved'}
|
|
101
|
+
*/
|
|
102
|
+
export function enforceAudit(root, kind, submittedBy, fallback = 'pending') {
|
|
103
|
+
const db = getDb(root)
|
|
104
|
+
const get = (key) => { try { return db.prepare('SELECT value FROM config WHERE key = ?').get(key)?.value } catch { return null } }
|
|
105
|
+
const mode = get('auditMode')
|
|
106
|
+
// 无配置 → 用调用方传入的 status
|
|
107
|
+
if (!mode) return fallback
|
|
108
|
+
if (mode === 'none') return 'approved'
|
|
109
|
+
const agentsRaw = get('auditExemptAgents') || '[]'
|
|
110
|
+
const kindsRaw = get('auditExemptKinds') || '[]'
|
|
111
|
+
let agents = [], kinds = []
|
|
112
|
+
try { agents = JSON.parse(agentsRaw) } catch { agents = agentsRaw.split(',').map(s => s.trim()).filter(Boolean) }
|
|
113
|
+
try { kinds = JSON.parse(kindsRaw) } catch { kinds = kindsRaw.split(',').map(s => s.trim()).filter(Boolean) }
|
|
114
|
+
if (agents.includes('__all__') || agents.includes(submittedBy)) return 'approved'
|
|
115
|
+
if (kinds.includes('__all__') || kinds.includes(kind)) return 'approved'
|
|
116
|
+
return 'pending'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** 写入审核配置到 config 表(供 index.js 同步 DSH settings)。 */
|
|
120
|
+
export function setAuditConfig(root, { auditMode, auditExemptAgents, auditExemptKinds }) {
|
|
121
|
+
const db = getDb(root)
|
|
122
|
+
const upsert = db.prepare(`INSERT INTO config (key, value, updated_at) VALUES (?, ?, datetime('now')) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`)
|
|
123
|
+
if (auditMode !== undefined) upsert.run('auditMode', String(auditMode))
|
|
124
|
+
if (auditExemptAgents !== undefined) upsert.run('auditExemptAgents', JSON.stringify(auditExemptAgents))
|
|
125
|
+
if (auditExemptKinds !== undefined) upsert.run('auditExemptKinds', JSON.stringify(auditExemptKinds))
|
|
126
|
+
}
|
|
127
|
+
|
|
76
128
|
/** 从 .md 文件迁移到 SQLite(幂等:已存在的卡跳过)。 */
|
|
77
129
|
export async function migrateFromMarkdown(root) {
|
|
78
130
|
const db = getDb(root)
|
package/lib/vault.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// 底层依赖 lib/db.js 的 getDb(root) → DatabaseSync 实例。
|
|
5
5
|
|
|
6
6
|
import path from 'node:path'
|
|
7
|
-
import { getDb } from './db.js'
|
|
7
|
+
import { getDb, enforceAudit } from './db.js'
|
|
8
8
|
|
|
9
9
|
/** 主题目录根。 */
|
|
10
10
|
export const KIND_ROOTS = {
|
|
@@ -227,29 +227,39 @@ export async function deleteCard(root, rel, { permanent = false } = {}) {
|
|
|
227
227
|
return { ok: true, soft: true }
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
/** 恢复软删卡:status=approved,清除 deleted_at
|
|
230
|
+
/** 恢复软删卡:status=approved,清除 deleted_at。写审计日志。 */
|
|
231
231
|
export async function restoreCard(root, rel) {
|
|
232
232
|
const db = getDb(root)
|
|
233
233
|
const now = new Date().toISOString()
|
|
234
|
+
const existing = db.prepare('SELECT status FROM cards WHERE path = ?').get(rel)
|
|
235
|
+
if (!existing) throw new Error('卡片不存在: ' + rel)
|
|
234
236
|
const info = db.prepare('UPDATE cards SET status = ?, deleted_at = NULL, updated_at = ? WHERE path = ?').run('approved', now, rel)
|
|
235
237
|
if (info.changes === 0) throw new Error('卡片不存在: ' + rel)
|
|
238
|
+
db.prepare('INSERT INTO audit_log (card_path, old_status, new_status, changed_by, reason) VALUES (?, ?, ?, ?, ?)').run(rel, existing.status, 'approved', 'restore', '回收站恢复')
|
|
236
239
|
return { ok: true, restored: true }
|
|
237
240
|
}
|
|
238
241
|
|
|
239
|
-
/**
|
|
240
|
-
export async function setCardStatus(root, rel, status) {
|
|
242
|
+
/** 更新卡状态(审核操作)。写审计日志,确保可追溯。 */
|
|
243
|
+
export async function setCardStatus(root, rel, status, { changedBy = 'system', reason = '' } = {}) {
|
|
241
244
|
const db = getDb(root)
|
|
242
245
|
const now = new Date().toISOString()
|
|
246
|
+
const existing = db.prepare('SELECT status FROM cards WHERE path = ?').get(rel)
|
|
247
|
+
if (!existing) throw new Error('卡片不存在: ' + rel)
|
|
243
248
|
const info = db.prepare('UPDATE cards SET status = ?, updated_at = ? WHERE path = ?').run(status, now, rel)
|
|
244
249
|
if (info.changes === 0) throw new Error('卡片不存在: ' + rel)
|
|
250
|
+
// 审计日志:记录每次 status 变更
|
|
251
|
+
db.prepare('INSERT INTO audit_log (card_path, old_status, new_status, changed_by, reason) VALUES (?, ?, ?, ?, ?)').run(rel, existing.status, status, changedBy, reason)
|
|
245
252
|
return { ok: true, status }
|
|
246
253
|
}
|
|
247
254
|
|
|
248
|
-
/** 原子写卡;先去重,命中返回 {duplicate}
|
|
255
|
+
/** 原子写卡;先去重,命中返回 {duplicate}。审核守卫在数据库层强制。 */
|
|
249
256
|
export async function writeCard(root, { kind, title, tags = [], body, source = '', status = 'pending', submittedBy = '', severity = 'info', reason = '', deletedAt = '' }, { threshold = 0.62, dedup = true } = {}) {
|
|
250
257
|
const db = getDb(root)
|
|
251
258
|
const kindRoot = KIND_ROOTS[kind] || KIND_ROOTS.knowledge
|
|
252
259
|
|
|
260
|
+
// 审核守卫:数据库层强制,调用方传的 status 仅作 fallback
|
|
261
|
+
const enforcedStatus = enforceAudit(root, kind, submittedBy, status)
|
|
262
|
+
|
|
253
263
|
if (dedup) {
|
|
254
264
|
const hit = await dedupCheck(root, body, threshold)
|
|
255
265
|
if (hit) return { ok: false, duplicate: { ...hit, path: hit.path } }
|
|
@@ -275,7 +285,7 @@ export async function writeCard(root, { kind, title, tags = [], body, source = '
|
|
|
275
285
|
JSON.stringify(tags),
|
|
276
286
|
body.trim(),
|
|
277
287
|
summary,
|
|
278
|
-
|
|
288
|
+
enforcedStatus,
|
|
279
289
|
source,
|
|
280
290
|
submittedBy,
|
|
281
291
|
severity,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory-eternal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "记忆核心(Memory Eternal):自研的 DeepSeek Harness 记忆插件,不移植任何既有记忆框架——对话结束后自动沉淀知识卡到本地 Markdown Vault(自研去重、自研 CJK 检索、可 git 管理),设置页提供图形化知识库(统计 / 搜索 / 知识图谱 + 侧边栏一键弹窗),Agent 通过 memory_recall 工具按需召回历史上下文。零人工干预。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|