@kanadego/dsh-heartbeat 1.9.3 → 2.0.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/LICENSE +21 -21
- package/README.md +103 -110
- package/client.js +12 -0
- package/config/policy.json +74 -74
- package/config/profile-schema.json +306 -306
- package/dist/{bindings-225SI7KC.js → bindings-U2M3CO2D.js} +3 -3
- package/dist/{chunk-TY6UP74X.js → chunk-3DNGY4DS.js} +2 -2
- package/dist/{chunk-CUOSICYJ.js → chunk-544QOJN5.js} +397 -23
- package/dist/chunk-544QOJN5.js.map +1 -0
- package/dist/{chunk-TRO4E6WG.js → chunk-6MVKWICX.js} +587 -139
- package/dist/chunk-6MVKWICX.js.map +1 -0
- package/dist/{chunk-WLZSABBH.js → chunk-6XM6KY7M.js} +27 -10
- package/dist/chunk-6XM6KY7M.js.map +1 -0
- package/dist/chunk-AKIJ4LS2.js +680 -0
- package/dist/chunk-AKIJ4LS2.js.map +1 -0
- package/dist/{chunk-KB5SMG3F.js → chunk-DC5HMCJC.js} +3 -3
- package/dist/{chunk-3MDBU6WY.js → chunk-DZ7YCFGX.js} +37 -7
- package/dist/chunk-DZ7YCFGX.js.map +1 -0
- package/dist/{chunk-WRUTATW4.js → chunk-IBBAISSW.js} +13 -2
- package/dist/chunk-IBBAISSW.js.map +1 -0
- package/dist/{chunk-XYYEJK3W.js → chunk-IFRMKVIC.js} +82 -21
- package/dist/chunk-IFRMKVIC.js.map +1 -0
- package/dist/{chunk-XFNCTOW7.js → chunk-NCPKOWS7.js} +52 -4
- package/dist/chunk-NCPKOWS7.js.map +1 -0
- package/dist/{chunk-JDH2LDFS.js → chunk-TVX4HV4Y.js} +4 -6
- package/dist/{chunk-JDH2LDFS.js.map → chunk-TVX4HV4Y.js.map} +1 -1
- package/dist/cli/index.js +149 -26
- package/dist/cli/index.js.map +1 -1
- package/dist/{inbox-P26DDMWC.js → inbox-OWBK6PE2.js} +8 -7
- package/dist/index.d.ts +48 -315
- package/dist/index.js +259 -918
- package/dist/index.js.map +1 -1
- package/dist/migrate-TQ5PB2DI.js +27 -0
- package/dist/{orchestrator-ZPRGVDCB.js → orchestrator-P7AEWVYR.js} +23 -12
- package/dist/{pool-UTXQJJ5C.js → pool-2T6TVS2L.js} +4 -5
- package/dist/{snapshot-K5VHETTK.js → snapshot-F5B2DD56.js} +5 -5
- package/package.json +84 -83
- package/dist/chunk-3MDBU6WY.js.map +0 -1
- package/dist/chunk-CUOSICYJ.js.map +0 -1
- package/dist/chunk-IV2ZWQA3.js +0 -56
- package/dist/chunk-IV2ZWQA3.js.map +0 -1
- package/dist/chunk-K5Y6JP2B.js +0 -399
- package/dist/chunk-K5Y6JP2B.js.map +0 -1
- package/dist/chunk-TRO4E6WG.js.map +0 -1
- package/dist/chunk-WLZSABBH.js.map +0 -1
- package/dist/chunk-WRUTATW4.js.map +0 -1
- package/dist/chunk-XFNCTOW7.js.map +0 -1
- package/dist/chunk-XYYEJK3W.js.map +0 -1
- package/dist/migrate-D4VKAKYT.js +0 -21
- /package/dist/{bindings-225SI7KC.js.map → bindings-U2M3CO2D.js.map} +0 -0
- /package/dist/{chunk-TY6UP74X.js.map → chunk-3DNGY4DS.js.map} +0 -0
- /package/dist/{chunk-KB5SMG3F.js.map → chunk-DC5HMCJC.js.map} +0 -0
- /package/dist/{inbox-P26DDMWC.js.map → inbox-OWBK6PE2.js.map} +0 -0
- /package/dist/{migrate-D4VKAKYT.js.map → migrate-TQ5PB2DI.js.map} +0 -0
- /package/dist/{orchestrator-ZPRGVDCB.js.map → orchestrator-P7AEWVYR.js.map} +0 -0
- /package/dist/{pool-UTXQJJ5C.js.map → pool-2T6TVS2L.js.map} +0 -0
- /package/dist/{snapshot-K5VHETTK.js.map → snapshot-F5B2DD56.js.map} +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadJson,
|
|
3
|
+
matchFilesRecursive,
|
|
3
4
|
readText,
|
|
4
5
|
saveJson,
|
|
5
6
|
writeText
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-AKIJ4LS2.js";
|
|
7
8
|
import {
|
|
8
9
|
atomicWriteFileSync
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-IBBAISSW.js";
|
|
10
11
|
|
|
11
12
|
// src/core/audit-log.ts
|
|
12
13
|
import fs from "fs";
|
|
@@ -58,6 +59,7 @@ import { randomUUID } from "crypto";
|
|
|
58
59
|
import fs3 from "fs";
|
|
59
60
|
|
|
60
61
|
// src/profile/types.ts
|
|
62
|
+
var JOURNAL_VERSION = 2;
|
|
61
63
|
var PARTITIONS = ["interest", "projects", "comm", "psy"];
|
|
62
64
|
var CONFIDENCE_CAP = {
|
|
63
65
|
chat: 0.6,
|
|
@@ -123,6 +125,290 @@ function checkAddAgainstSchema(schema, partition, topic, subTopic, nominated) {
|
|
|
123
125
|
return { ok: true, temporal: nominated };
|
|
124
126
|
}
|
|
125
127
|
|
|
128
|
+
// src/profile/shapes.ts
|
|
129
|
+
import z from "@deepseek-ai/schemastery";
|
|
130
|
+
var ShapeError = class extends Error {
|
|
131
|
+
constructor(message) {
|
|
132
|
+
super(message);
|
|
133
|
+
this.name = "ShapeError";
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
function isPlainObject(v) {
|
|
137
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
138
|
+
}
|
|
139
|
+
function describe(v) {
|
|
140
|
+
if (v === null) return "null";
|
|
141
|
+
if (Array.isArray(v)) return "an array";
|
|
142
|
+
return typeof v;
|
|
143
|
+
}
|
|
144
|
+
function closed(schema, keys, required = keys) {
|
|
145
|
+
return { schema, keys, required };
|
|
146
|
+
}
|
|
147
|
+
function check(shape, value, label) {
|
|
148
|
+
if (!isPlainObject(value)) throw new ShapeError(`${label}: expected an object, got ${describe(value)}`);
|
|
149
|
+
for (const req of shape.required) {
|
|
150
|
+
const key = typeof req === "string" ? req : req.key;
|
|
151
|
+
const nullable = typeof req !== "string" && req.nullable;
|
|
152
|
+
if (value[key] === void 0 || value[key] === null && !nullable) {
|
|
153
|
+
throw new ShapeError(`${label}.${key}: missing required value`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (const k of Object.keys(value)) {
|
|
157
|
+
if (!shape.keys.includes(k)) throw new ShapeError(`${label}.${k}: unknown field`);
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
return shape.schema(value);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
throw new ShapeError(`${label}: ${e instanceof Error ? e.message : String(e)}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
var Str = z.string().required();
|
|
166
|
+
var Num = z.number().required();
|
|
167
|
+
var NullableStr = z.union([z.string(), z.const(null)]);
|
|
168
|
+
var PartitionEnum = z.union(PARTITIONS.map((p) => z.const(p))).required();
|
|
169
|
+
var TemporalEnum = z.union([z.const("stable"), z.const("volatile")]).required();
|
|
170
|
+
var OptionalTemporal = z.union([z.const("stable"), z.const("volatile")]);
|
|
171
|
+
var KindEnum = z.union([
|
|
172
|
+
z.const("chat"),
|
|
173
|
+
z.const("screen"),
|
|
174
|
+
z.const("browse"),
|
|
175
|
+
z.const("hand"),
|
|
176
|
+
z.const("ledger")
|
|
177
|
+
]).required();
|
|
178
|
+
var ConfidenceNum = z.number().min(0).max(1).required();
|
|
179
|
+
var EvidenceShape = closed(
|
|
180
|
+
z.object({ kind: KindEnum, at: Str, ref: Str, quote: z.string() }),
|
|
181
|
+
["kind", "at", "ref", "quote"],
|
|
182
|
+
["kind", "at", "ref"]
|
|
183
|
+
);
|
|
184
|
+
function parseEvidence(value, label = "evidence") {
|
|
185
|
+
const o = check(EvidenceShape, value, label);
|
|
186
|
+
return {
|
|
187
|
+
kind: o.kind,
|
|
188
|
+
at: String(o.at),
|
|
189
|
+
ref: String(o.ref),
|
|
190
|
+
...o.quote === void 0 ? {} : { quote: String(o.quote) }
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function parseEvidenceList(value, label) {
|
|
194
|
+
if (!Array.isArray(value)) throw new ShapeError(`${label}: expected an array`);
|
|
195
|
+
return value.map((v, i) => parseEvidence(v, `${label}[${i}]`));
|
|
196
|
+
}
|
|
197
|
+
var EntryShape = closed(
|
|
198
|
+
z.object({
|
|
199
|
+
id: Str,
|
|
200
|
+
partition: PartitionEnum,
|
|
201
|
+
topic: Str,
|
|
202
|
+
subTopic: Str,
|
|
203
|
+
content: Str,
|
|
204
|
+
confidence: ConfidenceNum,
|
|
205
|
+
temporal: TemporalEnum,
|
|
206
|
+
validFrom: Str,
|
|
207
|
+
validTo: NullableStr,
|
|
208
|
+
supersededBy: NullableStr,
|
|
209
|
+
evidence: z.array(z.any()).required(),
|
|
210
|
+
createdAt: Str,
|
|
211
|
+
updatedAt: Str,
|
|
212
|
+
updateCount: Num,
|
|
213
|
+
lowActivity: z.boolean()
|
|
214
|
+
}),
|
|
215
|
+
[
|
|
216
|
+
"id",
|
|
217
|
+
"partition",
|
|
218
|
+
"topic",
|
|
219
|
+
"subTopic",
|
|
220
|
+
"content",
|
|
221
|
+
"confidence",
|
|
222
|
+
"temporal",
|
|
223
|
+
"validFrom",
|
|
224
|
+
"validTo",
|
|
225
|
+
"supersededBy",
|
|
226
|
+
"evidence",
|
|
227
|
+
"createdAt",
|
|
228
|
+
"updatedAt",
|
|
229
|
+
"updateCount",
|
|
230
|
+
"lowActivity"
|
|
231
|
+
],
|
|
232
|
+
[
|
|
233
|
+
"id",
|
|
234
|
+
"partition",
|
|
235
|
+
"topic",
|
|
236
|
+
"subTopic",
|
|
237
|
+
"content",
|
|
238
|
+
"confidence",
|
|
239
|
+
"temporal",
|
|
240
|
+
"validFrom",
|
|
241
|
+
{ key: "validTo", nullable: true },
|
|
242
|
+
{ key: "supersededBy", nullable: true },
|
|
243
|
+
"evidence",
|
|
244
|
+
"createdAt",
|
|
245
|
+
"updatedAt",
|
|
246
|
+
"updateCount"
|
|
247
|
+
]
|
|
248
|
+
);
|
|
249
|
+
function parseProfileEntry(value, label = "entry") {
|
|
250
|
+
const o = check(EntryShape, value, label);
|
|
251
|
+
return {
|
|
252
|
+
id: String(o.id),
|
|
253
|
+
partition: o.partition,
|
|
254
|
+
topic: String(o.topic),
|
|
255
|
+
subTopic: String(o.subTopic),
|
|
256
|
+
content: String(o.content),
|
|
257
|
+
confidence: Number(o.confidence),
|
|
258
|
+
temporal: o.temporal,
|
|
259
|
+
validFrom: String(o.validFrom),
|
|
260
|
+
validTo: o.validTo === null ? null : String(o.validTo),
|
|
261
|
+
supersededBy: o.supersededBy === null ? null : String(o.supersededBy),
|
|
262
|
+
evidence: parseEvidenceList(o.evidence, `${label}.evidence`),
|
|
263
|
+
createdAt: String(o.createdAt),
|
|
264
|
+
updatedAt: String(o.updatedAt),
|
|
265
|
+
updateCount: Number(o.updateCount),
|
|
266
|
+
...o.lowActivity === void 0 ? {} : { lowActivity: Boolean(o.lowActivity) }
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
var InboxShape = closed(
|
|
270
|
+
z.object({ id: z.string(), kind: KindEnum, at: Str, ref: Str, note: z.string() }),
|
|
271
|
+
["id", "kind", "at", "ref", "note"],
|
|
272
|
+
["kind", "at", "ref"]
|
|
273
|
+
);
|
|
274
|
+
var AddShape = closed(
|
|
275
|
+
z.object({
|
|
276
|
+
op: z.const("ADD").required(),
|
|
277
|
+
partition: PartitionEnum,
|
|
278
|
+
topic: Str,
|
|
279
|
+
subTopic: Str,
|
|
280
|
+
content: Str,
|
|
281
|
+
temporal: OptionalTemporal,
|
|
282
|
+
confidence: z.number().min(0).max(1),
|
|
283
|
+
why: z.string(),
|
|
284
|
+
evidence: z.array(z.any()).required()
|
|
285
|
+
}),
|
|
286
|
+
["op", "partition", "topic", "subTopic", "content", "temporal", "confidence", "why", "evidence"],
|
|
287
|
+
["op", "partition", "topic", "subTopic", "content", "evidence"]
|
|
288
|
+
);
|
|
289
|
+
var UpdateShape = closed(
|
|
290
|
+
z.object({
|
|
291
|
+
op: z.const("UPDATE").required(),
|
|
292
|
+
id: Str,
|
|
293
|
+
changes: z.object({ content: z.string(), confidence: z.number().min(0).max(1) }),
|
|
294
|
+
why: z.string()
|
|
295
|
+
}),
|
|
296
|
+
["op", "id", "changes", "why"],
|
|
297
|
+
["op", "id", "changes"]
|
|
298
|
+
);
|
|
299
|
+
var InvalidateShape = closed(
|
|
300
|
+
z.object({
|
|
301
|
+
op: z.const("INVALIDATE").required(),
|
|
302
|
+
id: Str,
|
|
303
|
+
why: z.string(),
|
|
304
|
+
evidence: z.array(z.any())
|
|
305
|
+
}),
|
|
306
|
+
["op", "id", "why", "evidence"],
|
|
307
|
+
["op", "id"]
|
|
308
|
+
);
|
|
309
|
+
var NoopShape = closed(
|
|
310
|
+
z.object({ op: z.const("NOOP").required(), why: z.string() }),
|
|
311
|
+
["op", "why"],
|
|
312
|
+
["op"]
|
|
313
|
+
);
|
|
314
|
+
function parseObservationRefs(value, label) {
|
|
315
|
+
if (value === void 0) return [];
|
|
316
|
+
if (!Array.isArray(value)) throw new ShapeError(`${label}: expected an array of observation ids`);
|
|
317
|
+
return value.map((v, i) => {
|
|
318
|
+
if (typeof v !== "string" || !v.trim()) {
|
|
319
|
+
throw new ShapeError(`${label}[${i}]: expected an observation id like "o1" (refs, timestamps and kinds are code-generated)`);
|
|
320
|
+
}
|
|
321
|
+
return v.trim();
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
function parseProposedOp(raw, label) {
|
|
325
|
+
if (!isPlainObject(raw)) throw new ShapeError(`${label}: expected an object, got ${describe(raw)}`);
|
|
326
|
+
const tag = raw.op;
|
|
327
|
+
if (tag === "ADD") {
|
|
328
|
+
const o = check(AddShape, raw, label);
|
|
329
|
+
const evidenceRefs = parseObservationRefs(o.evidence, `${label}.evidence`);
|
|
330
|
+
if (evidenceRefs.length === 0) {
|
|
331
|
+
throw new ShapeError(`${label}.evidence: ADD needs at least one observation id (axiom 1: no provenance, no memory)`);
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
op: "ADD",
|
|
335
|
+
partition: o.partition,
|
|
336
|
+
topic: String(o.topic),
|
|
337
|
+
subTopic: String(o.subTopic),
|
|
338
|
+
content: String(o.content),
|
|
339
|
+
...o.temporal === void 0 ? {} : { temporal: o.temporal },
|
|
340
|
+
...o.confidence === void 0 ? {} : { confidence: Number(o.confidence) },
|
|
341
|
+
why: typeof o.why === "string" ? o.why : "",
|
|
342
|
+
evidenceRefs
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
if (tag === "UPDATE") {
|
|
346
|
+
const o = check(UpdateShape, raw, label);
|
|
347
|
+
const changes = o.changes;
|
|
348
|
+
const out = {};
|
|
349
|
+
if (changes.content !== void 0) out.content = String(changes.content);
|
|
350
|
+
if (changes.confidence !== void 0) out.confidence = Number(changes.confidence);
|
|
351
|
+
if (out.content === void 0 && out.confidence === void 0) {
|
|
352
|
+
throw new ShapeError(`${label}.changes: needs content or confidence`);
|
|
353
|
+
}
|
|
354
|
+
return { op: "UPDATE", id: String(o.id), changes: out, why: typeof o.why === "string" ? o.why : "" };
|
|
355
|
+
}
|
|
356
|
+
if (tag === "INVALIDATE") {
|
|
357
|
+
const o = check(InvalidateShape, raw, label);
|
|
358
|
+
return {
|
|
359
|
+
op: "INVALIDATE",
|
|
360
|
+
id: String(o.id),
|
|
361
|
+
why: typeof o.why === "string" ? o.why : "",
|
|
362
|
+
evidenceRefs: parseObservationRefs(o.evidence, `${label}.evidence`)
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
if (tag === "NOOP") {
|
|
366
|
+
const o = check(NoopShape, raw, label);
|
|
367
|
+
return { op: "NOOP", why: typeof o.why === "string" ? o.why : "" };
|
|
368
|
+
}
|
|
369
|
+
throw new ShapeError(`${label}.op: unknown op ${JSON.stringify(tag)}`);
|
|
370
|
+
}
|
|
371
|
+
function parseProposedOps(list) {
|
|
372
|
+
const ops = [];
|
|
373
|
+
const rejected = [];
|
|
374
|
+
list.forEach((raw, i) => {
|
|
375
|
+
try {
|
|
376
|
+
ops.push(parseProposedOp(raw, `op[${i}]`));
|
|
377
|
+
} catch (e) {
|
|
378
|
+
rejected.push({ op: raw, reason: e instanceof Error ? e.message : String(e) });
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
return { ops, rejected };
|
|
382
|
+
}
|
|
383
|
+
function materializeEvidence(refs, observations, label) {
|
|
384
|
+
const evidence = [];
|
|
385
|
+
for (const ref of refs) {
|
|
386
|
+
const item = observations.get(ref);
|
|
387
|
+
if (!item) return { evidence: [], reason: `${label}: ${ref} does not match any observation in this batch` };
|
|
388
|
+
evidence.push({ kind: item.kind, at: item.at, ref: item.ref });
|
|
389
|
+
}
|
|
390
|
+
return { evidence };
|
|
391
|
+
}
|
|
392
|
+
function tryParseJournalRecord(value) {
|
|
393
|
+
try {
|
|
394
|
+
if (!isPlainObject(value)) return null;
|
|
395
|
+
const rec = {
|
|
396
|
+
ts: typeof value.ts === "string" ? value.ts : "",
|
|
397
|
+
runId: typeof value.runId === "string" ? value.runId : "",
|
|
398
|
+
applied: Array.isArray(value.applied) ? value.applied : [],
|
|
399
|
+
rejected: Array.isArray(value.rejected) ? value.rejected : []
|
|
400
|
+
};
|
|
401
|
+
if (value.v === JOURNAL_VERSION) {
|
|
402
|
+
if (!Array.isArray(value.entries)) return null;
|
|
403
|
+
rec.v = JOURNAL_VERSION;
|
|
404
|
+
rec.entries = value.entries.map((e, i) => parseProfileEntry(e, `entries[${i}]`));
|
|
405
|
+
}
|
|
406
|
+
return rec;
|
|
407
|
+
} catch {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
126
412
|
// src/profile/store.ts
|
|
127
413
|
var DAY_MS = 864e5;
|
|
128
414
|
function profileFilePath(dataDir) {
|
|
@@ -153,34 +439,77 @@ function replayText(doc, text, baselineTs, stats) {
|
|
|
153
439
|
for (let i = 0; i < lines.length; i++) {
|
|
154
440
|
const trimmed = lines[i].trim();
|
|
155
441
|
if (!trimmed) continue;
|
|
156
|
-
|
|
157
|
-
const rec = JSON.parse(trimmed);
|
|
158
|
-
if (baselineTs && rec.ts && String(rec.ts) <= baselineTs) continue;
|
|
159
|
-
for (const op of rec.applied ?? []) applyOpPermissive(doc, op, rec.ts);
|
|
160
|
-
stats.records += 1;
|
|
161
|
-
} catch {
|
|
442
|
+
const tornTail = () => {
|
|
162
443
|
const isLast = lines.slice(i + 1).every((l) => !l.trim());
|
|
163
444
|
if (stats.isLive && isLast) {
|
|
164
445
|
stats.truncatedTail = lines.length - i;
|
|
165
|
-
|
|
446
|
+
return true;
|
|
166
447
|
}
|
|
448
|
+
return false;
|
|
449
|
+
};
|
|
450
|
+
let rec = null;
|
|
451
|
+
try {
|
|
452
|
+
rec = tryParseJournalRecord(JSON.parse(trimmed));
|
|
453
|
+
} catch {
|
|
454
|
+
rec = null;
|
|
455
|
+
}
|
|
456
|
+
if (!rec) {
|
|
457
|
+
if (tornTail()) break;
|
|
458
|
+
stats.badRecords += 1;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (baselineTs && rec.ts && String(rec.ts) <= baselineTs) continue;
|
|
462
|
+
try {
|
|
463
|
+
if (rec.v === JOURNAL_VERSION && rec.entries) {
|
|
464
|
+
for (const entry of rec.entries) upsertEntry(doc, entry);
|
|
465
|
+
} else {
|
|
466
|
+
for (const op of rec.applied ?? []) applyOpPermissive(doc, op, rec.ts);
|
|
467
|
+
}
|
|
468
|
+
stats.records += 1;
|
|
469
|
+
} catch {
|
|
470
|
+
stats.badRecords += 1;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function bucketFor(doc, partition) {
|
|
475
|
+
let bucket = doc.partitions[partition];
|
|
476
|
+
if (!bucket) {
|
|
477
|
+
bucket = { entries: [] };
|
|
478
|
+
doc.partitions[partition] = bucket;
|
|
479
|
+
}
|
|
480
|
+
return bucket;
|
|
481
|
+
}
|
|
482
|
+
function upsertEntry(doc, entry) {
|
|
483
|
+
const next = structuredClone(entry);
|
|
484
|
+
for (const p of PARTITIONS) {
|
|
485
|
+
const list = doc.partitions[p]?.entries;
|
|
486
|
+
if (!list) continue;
|
|
487
|
+
const at = list.findIndex((e) => e.id === next.id);
|
|
488
|
+
if (at < 0) continue;
|
|
489
|
+
if (p === next.partition) {
|
|
490
|
+
list[at] = next;
|
|
491
|
+
} else {
|
|
492
|
+
list.splice(at, 1);
|
|
493
|
+
bucketFor(doc, next.partition).entries.push(next);
|
|
167
494
|
}
|
|
495
|
+
return;
|
|
168
496
|
}
|
|
497
|
+
bucketFor(doc, next.partition).entries.push(next);
|
|
169
498
|
}
|
|
170
499
|
function replayJournal(guard, dataDir) {
|
|
171
500
|
const snap = loadSnapshot(guard, dataDir);
|
|
172
501
|
const doc = snap ? snap.doc : emptyProfile();
|
|
173
502
|
const baselineTs = snap?.baselineTs ?? "";
|
|
174
|
-
const stats = { records: snap?.recordsFolded ?? 0, truncatedTail: 0, isLive: false };
|
|
503
|
+
const stats = { records: snap?.recordsFolded ?? 0, truncatedTail: 0, badRecords: 0, isLive: false };
|
|
175
504
|
for (const file of listArchiveFiles(guard, dataDir)) {
|
|
176
505
|
try {
|
|
177
|
-
replayText(doc, fs3.readFileSync(path3.join(dataDir, file), "utf8"), baselineTs, { records: 0, truncatedTail: 0, isLive: false });
|
|
506
|
+
replayText(doc, fs3.readFileSync(path3.join(dataDir, file), "utf8"), baselineTs, { records: 0, truncatedTail: 0, badRecords: 0, isLive: false });
|
|
178
507
|
} catch {
|
|
179
508
|
}
|
|
180
509
|
}
|
|
181
510
|
stats.isLive = true;
|
|
182
511
|
replayText(doc, readText(guard, journalFilePath(dataDir), ""), baselineTs, stats);
|
|
183
|
-
return { doc, truncatedTail: stats.truncatedTail, records: stats.records };
|
|
512
|
+
return { doc, truncatedTail: stats.truncatedTail, records: stats.records, badRecords: stats.badRecords };
|
|
184
513
|
}
|
|
185
514
|
function readJournalTexts(guard, dataDir) {
|
|
186
515
|
const texts = [];
|
|
@@ -229,6 +558,7 @@ function findActive(doc, id) {
|
|
|
229
558
|
function applyOpsToDoc(guard, dataDir, doc, ops, schema, policy, now) {
|
|
230
559
|
const applied = [];
|
|
231
560
|
const rejected = [];
|
|
561
|
+
const entries = [];
|
|
232
562
|
const nowIso = new Date(now).toISOString();
|
|
233
563
|
for (const op of ops) {
|
|
234
564
|
if (op.op === "NOOP") {
|
|
@@ -242,12 +572,12 @@ function applyOpsToDoc(guard, dataDir, doc, ops, schema, policy, now) {
|
|
|
242
572
|
}
|
|
243
573
|
let temporal = "stable";
|
|
244
574
|
if (schema) {
|
|
245
|
-
const
|
|
246
|
-
if (!
|
|
247
|
-
rejected.push({ op, reason:
|
|
575
|
+
const check2 = checkAddAgainstSchema(schema, op.partition, op.topic, op.subTopic, op.temporal);
|
|
576
|
+
if (!check2.ok) {
|
|
577
|
+
rejected.push({ op, reason: check2.reason });
|
|
248
578
|
continue;
|
|
249
579
|
}
|
|
250
|
-
temporal =
|
|
580
|
+
temporal = check2.temporal;
|
|
251
581
|
} else if (!(op.partition in doc.partitions)) {
|
|
252
582
|
rejected.push({ op, reason: `unknown partition: ${op.partition}` });
|
|
253
583
|
continue;
|
|
@@ -288,6 +618,7 @@ function applyOpsToDoc(guard, dataDir, doc, ops, schema, policy, now) {
|
|
|
288
618
|
};
|
|
289
619
|
op.assignedId = entry.id;
|
|
290
620
|
doc.partitions[op.partition].entries.push(entry);
|
|
621
|
+
entries.push(structuredClone(entry));
|
|
291
622
|
applied.push(op);
|
|
292
623
|
continue;
|
|
293
624
|
}
|
|
@@ -297,17 +628,20 @@ function applyOpsToDoc(guard, dataDir, doc, ops, schema, policy, now) {
|
|
|
297
628
|
rejected.push({ op, reason: `unknown or inactive entry: ${op.id}` });
|
|
298
629
|
continue;
|
|
299
630
|
}
|
|
300
|
-
|
|
631
|
+
let nextConfidence = null;
|
|
301
632
|
if (op.changes.confidence !== void 0) {
|
|
302
633
|
const cap = capForKinds(entry.evidence.map((e) => e.kind));
|
|
303
634
|
if (op.changes.confidence > entry.confidence && entry.evidence.length < 2) {
|
|
304
635
|
rejected.push({ op, reason: "confidence upgrade requires a second confirming observation" });
|
|
305
636
|
continue;
|
|
306
637
|
}
|
|
307
|
-
|
|
638
|
+
nextConfidence = Math.min(op.changes.confidence, cap);
|
|
308
639
|
}
|
|
640
|
+
if (op.changes.content !== void 0) entry.content = op.changes.content.trim();
|
|
641
|
+
if (nextConfidence !== null) entry.confidence = nextConfidence;
|
|
309
642
|
entry.updatedAt = nowIso;
|
|
310
643
|
entry.updateCount += 1;
|
|
644
|
+
entries.push(structuredClone(entry));
|
|
311
645
|
applied.push(op);
|
|
312
646
|
continue;
|
|
313
647
|
}
|
|
@@ -331,15 +665,17 @@ function applyOpsToDoc(guard, dataDir, doc, ops, schema, policy, now) {
|
|
|
331
665
|
entry.updatedAt = nowIso;
|
|
332
666
|
entry.updateCount += 1;
|
|
333
667
|
if (op.evidence) entry.evidence.push(...op.evidence);
|
|
668
|
+
entries.push(structuredClone(entry));
|
|
334
669
|
applied.push(op);
|
|
335
670
|
continue;
|
|
336
671
|
}
|
|
337
672
|
}
|
|
338
|
-
return { applied, rejected };
|
|
673
|
+
return { applied, rejected, entries };
|
|
339
674
|
}
|
|
340
675
|
var dbSeq = 0;
|
|
341
676
|
function runDeterministicAging(doc, policy, now) {
|
|
342
677
|
const nowIso = new Date(now).toISOString();
|
|
678
|
+
const entries = [];
|
|
343
679
|
let volatileExpired = 0;
|
|
344
680
|
let lowActivityMarked = 0;
|
|
345
681
|
for (const p of PARTITIONS) {
|
|
@@ -352,18 +688,21 @@ function runDeterministicAging(doc, policy, now) {
|
|
|
352
688
|
e.updatedAt = nowIso;
|
|
353
689
|
e.updateCount += 1;
|
|
354
690
|
volatileExpired += 1;
|
|
691
|
+
entries.push(structuredClone(e));
|
|
355
692
|
}
|
|
356
693
|
} else if (!e.lowActivity && now - lastEvidence > policy.profile.stableLowActivityDays * DAY_MS) {
|
|
357
694
|
e.lowActivity = true;
|
|
358
695
|
lowActivityMarked += 1;
|
|
696
|
+
entries.push(structuredClone(e));
|
|
359
697
|
}
|
|
360
698
|
}
|
|
361
699
|
}
|
|
362
|
-
return { volatileExpired, lowActivityMarked };
|
|
700
|
+
return { volatileExpired, lowActivityMarked, entries };
|
|
363
701
|
}
|
|
364
702
|
function persistWithJournal(guard, dataDir, doc, record) {
|
|
365
703
|
saveJson(guard, profileFilePath(dataDir), doc);
|
|
366
|
-
const
|
|
704
|
+
const v = record.entries === void 0 ? void 0 : JOURNAL_VERSION;
|
|
705
|
+
const line = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...record, v });
|
|
367
706
|
const journal = journalFilePath(dataDir);
|
|
368
707
|
try {
|
|
369
708
|
fs3.appendFileSync(guard.assert(journal), line + "\n", "utf8");
|
|
@@ -458,12 +797,46 @@ function rebuildProfile(guard, dataDir, opts = {}) {
|
|
|
458
797
|
}
|
|
459
798
|
return { ok: true, truncatedTail: replayed.truncatedTail, records: replayed.records, wrote: true };
|
|
460
799
|
}
|
|
800
|
+
var WIPE_FILES = ["profile.json", "profile_inbox.jsonl", "profile_journal.jsonl", SNAPSHOT_FILE];
|
|
801
|
+
var WIPE_PATTERNS = [
|
|
802
|
+
/^profile_journal\.archive-/,
|
|
803
|
+
/\.bak-migrate-\d{14}$/,
|
|
804
|
+
/\.incoming-\d{14}$/
|
|
805
|
+
];
|
|
806
|
+
function wipeProfile(guard, dataDir) {
|
|
807
|
+
const removed = [];
|
|
808
|
+
for (const f of WIPE_FILES) {
|
|
809
|
+
const abs = guard.assert(path3.join(dataDir, f));
|
|
810
|
+
if (fs3.existsSync(abs)) {
|
|
811
|
+
fs3.rmSync(abs, { force: true });
|
|
812
|
+
removed.push(f);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
for (const re of WIPE_PATTERNS) {
|
|
816
|
+
let rels = [];
|
|
817
|
+
try {
|
|
818
|
+
rels = matchFilesRecursive(guard.assert(dataDir), re);
|
|
819
|
+
} catch {
|
|
820
|
+
rels = [];
|
|
821
|
+
}
|
|
822
|
+
for (const rel of rels) {
|
|
823
|
+
try {
|
|
824
|
+
fs3.rmSync(guard.assert(path3.join(dataDir, rel)), { force: true });
|
|
825
|
+
removed.push(rel);
|
|
826
|
+
} catch {
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return removed;
|
|
831
|
+
}
|
|
461
832
|
|
|
462
833
|
export {
|
|
463
834
|
appendAuditLine,
|
|
464
835
|
readAuditLines,
|
|
465
836
|
pruneAuditFile,
|
|
466
837
|
loadProfileSchema,
|
|
838
|
+
parseProposedOps,
|
|
839
|
+
materializeEvidence,
|
|
467
840
|
profileFilePath,
|
|
468
841
|
journalFilePath,
|
|
469
842
|
ARCHIVE_PREFIX,
|
|
@@ -475,6 +848,7 @@ export {
|
|
|
475
848
|
runDeterministicAging,
|
|
476
849
|
persistWithJournal,
|
|
477
850
|
verifyProfile,
|
|
478
|
-
rebuildProfile
|
|
851
|
+
rebuildProfile,
|
|
852
|
+
wipeProfile
|
|
479
853
|
};
|
|
480
|
-
//# sourceMappingURL=chunk-
|
|
854
|
+
//# sourceMappingURL=chunk-544QOJN5.js.map
|