@kira905/dsh-memory-toolkit 0.1.2 → 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.
- package/CHANGELOG.md +130 -1
- package/README.en.md +225 -2
- package/README.md +208 -2
- package/cordis.patch.yml +32 -0
- package/lib/deploy-guard.mjs +463 -275
- package/lib/guard-http.mjs +59 -62
- package/lib/pitfall-form-map.mjs +94 -84
- package/lib/pitfall-score.mjs +262 -185
- package/lib/session-file-name.mjs +116 -100
- package/lib/zstd-frames.mjs +88 -67
- package/memory-db-tools/README.md +29 -1
- package/memory-db-tools/audit-analyze.mjs +1 -1
- package/memory-db-tools/audit-export.mjs +60 -16
- package/memory-db-tools/audit-struct-probe.mjs +29 -8
- package/memory-db-tools/lib/known-devices.mjs +137 -0
- package/memory-engine/README.md +58 -4
- package/memory-engine/bench/bench.mjs +1 -1
- package/memory-engine/cli/band-sentinel.mjs +164 -0
- package/memory-engine/cli/distill-rollback.mjs +112 -44
- package/memory-engine/cli/distill-sessions.mjs +237 -0
- package/memory-engine/cli/distill.mjs +196 -95
- package/memory-engine/cli/doc-index-rebuild.mjs +142 -108
- package/memory-engine/cli/doc-index.mjs +47 -38
- package/memory-engine/cli/domain-tags.mjs +246 -162
- package/memory-engine/cli/layer-migrate.mjs +176 -0
- package/memory-engine/cli/layer-move.mjs +189 -0
- package/memory-engine/cli/pitfall-hook.mjs +136 -70
- package/memory-engine/cli/recall.mjs +243 -110
- package/memory-engine/cli/selfcheck.mjs +93 -44
- package/memory-engine/cli/serve-start.mjs +221 -140
- package/memory-engine/cli/sweep.mjs +83 -44
- package/memory-engine/cli/sync.mjs +93 -57
- package/memory-engine/config/audit-devices.json +6 -0
- package/memory-engine/config/bands.json +7 -0
- package/memory-engine/config/layers.json +7 -0
- package/memory-engine/dict/LICENSE-/350/257/215/345/205/270.md +53 -0
- package/memory-engine/dict/lexicon-config.json +11 -0
- package/memory-engine/dict//350/257/215/345/205/270.txt +109750 -0
- package/memory-engine/dict//350/257/215/351/242/221.txt +20000 -0
- package/memory-engine/lib/band-sentinel.mjs +417 -0
- package/memory-engine/lib/cli-strict.mjs +128 -0
- package/memory-engine/lib/cli-util.mjs +147 -125
- package/memory-engine/lib/config.mjs +83 -96
- package/memory-engine/lib/dirty-ledger.mjs +127 -94
- package/memory-engine/lib/distill-rollback.mjs +173 -133
- package/memory-engine/lib/distill.mjs +878 -585
- package/memory-engine/lib/doc-index-store.mjs +208 -235
- package/memory-engine/lib/doc-index.mjs +351 -350
- package/memory-engine/lib/doc-scan.mjs +178 -156
- package/memory-engine/lib/doc-summarize.mjs +220 -205
- package/memory-engine/lib/domain-tags.mjs +358 -277
- package/memory-engine/lib/embedder.mjs +175 -194
- package/memory-engine/lib/hashes.mjs +22 -7
- package/memory-engine/lib/layer-migrate.mjs +330 -0
- package/memory-engine/lib/layer-move.mjs +307 -0
- package/memory-engine/lib/layers.mjs +191 -0
- package/memory-engine/lib/lexicon-recall.mjs +473 -0
- package/memory-engine/lib/memory-source.mjs +78 -80
- package/memory-engine/lib/pitfall-source.mjs +115 -114
- package/memory-engine/lib/policy-source.mjs +279 -261
- package/memory-engine/lib/recall-log.mjs +285 -0
- package/memory-engine/lib/recall.mjs +565 -240
- package/memory-engine/lib/selfcheck.mjs +142 -106
- package/memory-engine/lib/session-distill.mjs +603 -0
- package/memory-engine/lib/session-source.mjs +421 -0
- package/memory-engine/lib/sweep.mjs +107 -130
- package/memory-engine/lib/sync.mjs +110 -140
- package/memory-engine/lib/tick-hook.mjs +123 -148
- package/memory-engine/lib/vectors-store.mjs +213 -188
- package/memory-engine/test/12-layering.test.mjs +539 -0
- package/memory-engine/test/13-session-distill.test.mjs +616 -0
- package/memory-engine/tools/bridge-smoke.mjs +94 -55
- package/memory-engine/tools/fetch-model.mjs +167 -87
- package/memory-engine/tools/smoke-recall.mjs +94 -38
- package/memory-engine/tools/verify-recall-chain.mjs +413 -0
- package/package.json +11 -1
|
@@ -1,218 +1,193 @@
|
|
|
1
|
-
//
|
|
1
|
+
// 坑库向量同步的「置标记 → 消费」钩子(方案 §A.2 第 10 行 · A 点)
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// 要解决的问题:坑库文件变了以后,谁去触发 `source='pitfall'` 的增量编码?
|
|
4
|
+
// 这里选的是**松耦合信号**:坑库 job 跑完只落一个标记(不直接编码、不拖慢 job),
|
|
5
|
+
// 消费方(本模块)看到标记才跑一次坑源增量同步。
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
7
|
+
// ⚠ 全方案唯一"有意双写者"的一处:置标记方与清标记方各写各的,**不共用同一把锁**
|
|
8
|
+
// (跨进程共用锁会把握手串成串行)。正确性靠三件事兜住:
|
|
9
|
+
// ① 置标记方:临时文件 + rename 原子落盘,且**必写 `updatedAt`**;
|
|
10
|
+
// ② 清标记方:原子读改写,且**只在标记 `updatedAt` 早于本次消费起点时才清**;
|
|
11
|
+
// ③ 并发插进新标记时宁可这轮不清(留待下次),绝不丢标记。
|
|
12
12
|
import fs from 'node:fs';
|
|
13
13
|
import os from 'node:os';
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { runIncrementalSync } from './sync.mjs';
|
|
16
16
|
|
|
17
17
|
export const MARKER_SCHEMA = 1;
|
|
18
|
-
|
|
18
|
+
// 标记文件默认名(已登记在管家的状态文件清单里)
|
|
19
19
|
export const MARKER_FILENAME = 'butler-pitfall-vector-dirty.json';
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
// 默认落点:<DSH_HOME>/<标记文件名>(可注入,方便夹具与 CLI 指到别处)
|
|
22
22
|
export function markerPathFor(dshHome = process.env.DSH_HOME || path.join(os.homedir(), '.dsh')) {
|
|
23
23
|
return path.join(dshHome, MARKER_FILENAME);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// Windows 上覆盖一个正被别人读/改名的目标会概率性抛这几个码(没有 flock 语义,只能重试)
|
|
27
|
+
const RENAME_RACE_CODES = new Set(['EPERM', 'EBUSY', 'EACCES', 'EEXIST']);
|
|
28
|
+
|
|
29
|
+
// 同步 API 里没法 await:只能空转,毫秒级,代价可忽略
|
|
30
|
+
function sleepSync(ms) {
|
|
31
|
+
const deadline = Date.now() + ms;
|
|
32
|
+
while (Date.now() < deadline) { /* 空转 */ }
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
function renameWithRetry(from, to, { tries = 50, waitMs = 2 } = {}) {
|
|
36
|
-
let lastErr = null;
|
|
37
|
-
for (let i = 0; i < tries; i += 1) {
|
|
35
|
+
// 有限次重试的 rename。原子性不变:仍是"临时文件 → rename"两步,
|
|
36
|
+
// 读者要么看到旧内容、要么看到新内容,永远看不到半截。
|
|
37
|
+
function renameSyncWithRetry(from, to, { tries = 50, waitMs = 2 } = {}) {
|
|
38
|
+
let lastError = null;
|
|
39
|
+
for (let attempt = 0; attempt < tries; attempt += 1) {
|
|
38
40
|
try {
|
|
39
41
|
fs.renameSync(from, to);
|
|
40
42
|
return true;
|
|
41
|
-
} catch (
|
|
42
|
-
|
|
43
|
-
if (!
|
|
44
|
-
|
|
45
|
-
while (Date.now() < until) {
|
|
46
|
-
/* 忙等几毫秒(同步 API,无法 await) */
|
|
47
|
-
}
|
|
43
|
+
} catch (err) {
|
|
44
|
+
lastError = err;
|
|
45
|
+
if (!RENAME_RACE_CODES.has(err && err.code)) throw err;
|
|
46
|
+
sleepSync(waitMs);
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
|
-
throw
|
|
49
|
+
throw lastError;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 标记体公共前缀:所有落盘形状都从这里长出来,字段顺序保持一致
|
|
53
|
+
function stampPayload(fields) {
|
|
54
|
+
return { schemaVersion: MARKER_SCHEMA, ...fields };
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
|
|
57
|
+
// 原子写 JSON(临时文件 + 同卷 rename;读者不会看到半截 JSON)
|
|
54
58
|
export function atomicWriteJson(p, obj) {
|
|
55
59
|
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
56
|
-
const
|
|
57
|
-
fs.writeFileSync(
|
|
58
|
-
|
|
60
|
+
const staged = `${p}.tmp-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
|
|
61
|
+
fs.writeFileSync(staged, `${JSON.stringify(obj, null, 2)}\n`, 'utf8');
|
|
62
|
+
renameSyncWithRetry(staged, p);
|
|
59
63
|
return p;
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
// 统一的"没读到"形状(三处出口共用,字段顺序稳定)
|
|
67
|
+
function absent(error) {
|
|
68
|
+
return { present: false, marker: null, error };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 读标记。**永不抛错**:半截 JSON / 权限问题一律转成 error,由调用方决定 fail-open 还是 fail-closed。
|
|
66
72
|
export function readMarker(p) {
|
|
67
73
|
let raw;
|
|
68
74
|
try {
|
|
69
75
|
raw = fs.readFileSync(p, 'utf8');
|
|
70
|
-
} catch (
|
|
71
|
-
if (
|
|
72
|
-
return
|
|
76
|
+
} catch (err) {
|
|
77
|
+
if (err && err.code === 'ENOENT') return absent(null);
|
|
78
|
+
return absent(String(err && err.message ? err.message : err));
|
|
73
79
|
}
|
|
74
80
|
try {
|
|
75
81
|
return { present: true, marker: JSON.parse(raw), error: null };
|
|
76
|
-
} catch (
|
|
77
|
-
return
|
|
82
|
+
} catch (err) {
|
|
83
|
+
return absent(`半截/非法 JSON:${String(err && err.message ? err.message : err)}`);
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
* 置标记(**置标记方 API**;`pitfall` job 末尾一行调用)。
|
|
83
|
-
* @param {string} p 标记文件路径
|
|
84
|
-
* @param {{at?:number, reason?:string, by?:string, counters?:object}} [opts] `at` 必写(= 本次置标记时刻)
|
|
85
|
-
*/
|
|
87
|
+
// 置标记(置标记方 API,坑库 job 末尾一行调用);`at` 必写 = 本次置标记时刻
|
|
86
88
|
export function markDirty(p, opts = {}) {
|
|
87
89
|
const at = Number(opts.at ?? Date.now());
|
|
88
|
-
|
|
89
|
-
schemaVersion: MARKER_SCHEMA,
|
|
90
|
+
const fields = {
|
|
90
91
|
updatedAt: at,
|
|
91
92
|
at: new Date(at).toISOString(),
|
|
92
93
|
reason: opts.reason ?? null,
|
|
93
94
|
by: opts.by ?? null,
|
|
94
95
|
device: os.hostname(),
|
|
95
96
|
counters: opts.counters ?? null,
|
|
96
|
-
}
|
|
97
|
+
};
|
|
98
|
+
atomicWriteJson(p, stampPayload(fields));
|
|
97
99
|
return { ok: true, path: p, updatedAt: at };
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
// 删中间痕迹(失败也不影响判定结果)
|
|
103
|
+
function dropQuietly(file) {
|
|
104
|
+
try { fs.rmSync(file, { force: true }); } catch { /* 忽略 */ }
|
|
105
|
+
}
|
|
106
|
+
|
|
100
107
|
/**
|
|
101
|
-
*
|
|
108
|
+
* 清标记(消费方 API):原子读改写 + stale 判定。
|
|
102
109
|
* · 标记缺失 ⇒ 什么都不做
|
|
103
|
-
* ·
|
|
104
|
-
* ·
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* @returns {{cleared:boolean, reason:string, consumedUpdatedAt?:number, marker?:object}}
|
|
110
|
+
* · updatedAt >= consumeStart ⇒ 保留(那是并发新写的,不是这轮要消费的那份)
|
|
111
|
+
* · updatedAt < consumeStart ⇒ 摘除:先写 consumed 落点,再把主文件 rename 过去(同卷 rename 原子)
|
|
112
|
+
* 残余竞态窗:复读与 rename 之间仍有极小窗口(Windows 上没有 flock 可替代);
|
|
113
|
+
* 两边实际频率差三个数量级以上(生产置标记 30 分钟一次 / 消费秒级),实际不构成丢标记。
|
|
108
114
|
*/
|
|
109
115
|
export function clearMarkerIfStale(p, consumeStart) {
|
|
110
|
-
const
|
|
111
|
-
if (!
|
|
112
|
-
const at = Number(first.marker?.updatedAt || 0);
|
|
113
|
-
if (!(at < Number(consumeStart))) return { cleared: false, reason: 'fresh-marker-kept', marker: first.marker };
|
|
114
|
-
|
|
115
|
-
const consumed = `${p}.consumed`;
|
|
116
|
-
atomicWriteJson(consumed, {
|
|
117
|
-
schemaVersion: MARKER_SCHEMA,
|
|
118
|
-
clearedAt: Date.now(),
|
|
119
|
-
consumedUpdatedAt: at,
|
|
120
|
-
});
|
|
116
|
+
const head = readMarker(p);
|
|
117
|
+
if (!head.present) return { cleared: false, reason: head.error ? 'unreadable' : 'no-marker' };
|
|
121
118
|
|
|
122
|
-
const
|
|
123
|
-
if (!
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
119
|
+
const seenAt = Number(head.marker?.updatedAt || 0);
|
|
120
|
+
if (!(seenAt < Number(consumeStart))) {
|
|
121
|
+
return { cleared: false, reason: 'fresh-marker-kept', marker: head.marker };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const consumedFile = `${p}.consumed`;
|
|
125
|
+
atomicWriteJson(consumedFile, stampPayload({ clearedAt: Date.now(), consumedUpdatedAt: seenAt }));
|
|
126
|
+
|
|
127
|
+
// 复读一次:这中间可能有人又置了新标记
|
|
128
|
+
const recheck = readMarker(p);
|
|
129
|
+
if (!recheck.present) {
|
|
130
|
+
dropQuietly(consumedFile);
|
|
129
131
|
return { cleared: false, reason: 'already-gone' };
|
|
130
132
|
}
|
|
131
|
-
if (!(Number(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
} catch {
|
|
135
|
-
/* ignore */
|
|
136
|
-
}
|
|
137
|
-
return { cleared: false, reason: 'fresh-marker-kept-on-recheck', marker: second.marker };
|
|
133
|
+
if (!(Number(recheck.marker?.updatedAt || 0) < Number(consumeStart))) {
|
|
134
|
+
dropQuietly(consumedFile);
|
|
135
|
+
return { cleared: false, reason: 'fresh-marker-kept-on-recheck', marker: recheck.marker };
|
|
138
136
|
}
|
|
139
137
|
try {
|
|
140
|
-
|
|
141
|
-
} catch (
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
} catch {
|
|
145
|
-
/* ignore */
|
|
146
|
-
}
|
|
147
|
-
return { cleared: false, reason: `rename-failed:${e && e.code ? e.code : String(e)}` };
|
|
138
|
+
renameSyncWithRetry(p, consumedFile);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
dropQuietly(consumedFile);
|
|
141
|
+
return { cleared: false, reason: `rename-failed:${err && err.code ? err.code : String(err)}` };
|
|
148
142
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
143
|
+
dropQuietly(consumedFile);
|
|
144
|
+
return { cleared: true, reason: 'stale-marker-consumed', consumedUpdatedAt: seenAt };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 来源守卫:只认坑源。若被别的来源调进来,本函数会一边按 pitfall 命名空间写向量、
|
|
148
|
+
// 一边扫别人的行 ⇒ 静默污染命名空间。所以来源名不对就**拒跑**(宁可少跑一轮,也不脏库)。
|
|
149
|
+
function assertPitfallSource(source) {
|
|
150
|
+
const name = source && source.sourceName ? String(source.sourceName) : null;
|
|
151
|
+
if (name === 'pitfall') return name;
|
|
152
|
+
throw new Error(
|
|
153
|
+
`runPitfallHook 只接受坑源(sourceName='pitfall'),收到 ${name ?? '(未知来源/无 sourceName)'}`
|
|
154
|
+
+ ' ⇒ 拒跑(否则会把该来源的行错标成 pitfall 向量)',
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 按本轮结果决定标记去留:dry-run 不动;同步失败保留;成功才清
|
|
159
|
+
function settleMarker({ markerPath, consumeStart, dryRun, stats, log }) {
|
|
160
|
+
if (dryRun) return { cleared: false, reason: 'dry-run' };
|
|
161
|
+
if (stats.error) {
|
|
162
|
+
log(`[tick-hook] 同步失败 ⇒ **保留标记**下轮重试:${stats.error}`);
|
|
163
|
+
return { cleared: false, reason: 'sync-failed-keep-marker' };
|
|
153
164
|
}
|
|
154
|
-
|
|
165
|
+
const outcome = clearMarkerIfStale(markerPath, consumeStart);
|
|
166
|
+
log(`[tick-hook] 清标记:${JSON.stringify(outcome)}`);
|
|
167
|
+
return outcome;
|
|
155
168
|
}
|
|
156
169
|
|
|
157
|
-
|
|
158
|
-
* 消费一轮:有标记 ⇒ 跑坑源增量同步 ⇒ 成功后清标记(失败**不清**,下轮重试)。
|
|
159
|
-
* @param {object} p
|
|
160
|
-
* @param {string} p.markerPath
|
|
161
|
-
* @param {object} p.store / p.source / p.embedder / p.modelId / p.dim
|
|
162
|
-
*/
|
|
170
|
+
// 消费一轮:有标记 ⇒ 跑坑源增量同步 ⇒ 成功才清标记(失败保留,下轮重试)
|
|
163
171
|
export async function runPitfallHook({
|
|
164
|
-
markerPath,
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
embedder,
|
|
168
|
-
modelId,
|
|
169
|
-
dim,
|
|
170
|
-
batchSize = 50,
|
|
171
|
-
full = false,
|
|
172
|
-
dryRun = false,
|
|
173
|
-
log = () => {},
|
|
174
|
-
now = () => Date.now(),
|
|
172
|
+
markerPath, store, source, embedder, modelId, dim,
|
|
173
|
+
batchSize = 50, full = false, dryRun = false,
|
|
174
|
+
log = () => {}, now = () => Date.now(),
|
|
175
175
|
}) {
|
|
176
176
|
const consumeStart = now();
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
`runPitfallHook 只接受坑源(sourceName='pitfall'),收到 ${srcName ?? '(未知来源/无 sourceName)'}`
|
|
184
|
-
+ ' ⇒ 拒跑(否则会把该来源的行错标成 pitfall 向量)',
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
const r = readMarker(markerPath);
|
|
188
|
-
if (r.error) {
|
|
189
|
-
log(`[tick-hook] 标记文件读不动(本轮跳过、不清除):${r.error}`);
|
|
190
|
-
return { ok: false, skipped: 'marker-unreadable', error: r.error, consumeStart };
|
|
177
|
+
assertPitfallSource(source);
|
|
178
|
+
|
|
179
|
+
const marker = readMarker(markerPath);
|
|
180
|
+
if (marker.error) {
|
|
181
|
+
log(`[tick-hook] 标记文件读不动(本轮跳过、不清除):${marker.error}`);
|
|
182
|
+
return { ok: false, skipped: 'marker-unreadable', error: marker.error, consumeStart };
|
|
191
183
|
}
|
|
192
|
-
if (!
|
|
184
|
+
if (!marker.present) return { ok: true, skipped: 'no-marker', consumeStart };
|
|
193
185
|
|
|
194
|
-
log(`[tick-hook] 标记在位(updatedAt=${
|
|
186
|
+
log(`[tick-hook] 标记在位(updatedAt=${marker.marker?.updatedAt ?? '?'},reason=${marker.marker?.reason ?? '-'})⇒ 跑坑源增量同步`);
|
|
195
187
|
const stats = await runIncrementalSync({
|
|
196
|
-
source,
|
|
197
|
-
|
|
198
|
-
embedder,
|
|
199
|
-
modelId,
|
|
200
|
-
dim,
|
|
201
|
-
sourceName: 'pitfall',
|
|
202
|
-
batchSize,
|
|
203
|
-
full,
|
|
204
|
-
dryRun,
|
|
205
|
-
log,
|
|
206
|
-
now,
|
|
188
|
+
source, store, embedder, modelId, dim,
|
|
189
|
+
sourceName: 'pitfall', batchSize, full, dryRun, log, now,
|
|
207
190
|
});
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
else if (stats.error) {
|
|
211
|
-
log(`[tick-hook] 同步失败 ⇒ **保留标记**下轮重试:${stats.error}`);
|
|
212
|
-
cleared = { cleared: false, reason: 'sync-failed-keep-marker' };
|
|
213
|
-
} else {
|
|
214
|
-
cleared = clearMarkerIfStale(markerPath, consumeStart);
|
|
215
|
-
log(`[tick-hook] 清标记:${JSON.stringify(cleared)}`);
|
|
216
|
-
}
|
|
217
|
-
return { ok: !stats.error, consumeStart, marker: r.marker, stats, cleared };
|
|
191
|
+
const cleared = settleMarker({ markerPath, consumeStart, dryRun, stats, log });
|
|
192
|
+
return { ok: !stats.error, consumeStart, marker: marker.marker, stats, cleared };
|
|
218
193
|
}
|