@remnic/belief-ledger 9.3.596

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/dist/index.js ADDED
@@ -0,0 +1,1679 @@
1
+ // openclaw-engram: Local-first memory plugin
2
+
3
+ // src/schema.ts
4
+ var LEDGER_TAG = "belief-ledger";
5
+ var LEDGER_SCHEMA_VERSION = "1";
6
+ var KIND_VALUES = /* @__PURE__ */ new Set(["claim", "prediction", "opinion"]);
7
+ var STANCE_VALUES = /* @__PURE__ */ new Set(["for", "against", "uncertain", "neutral"]);
8
+ var STATUS_VALUES = /* @__PURE__ */ new Set(["active", "superseded", "resolved", "snoozed", "ignored"]);
9
+ var JUDGE_VALUES = /* @__PURE__ */ new Set(["contradiction", "evolution", "refinement", "unrelated"]);
10
+ var VERDICT_VALUES = /* @__PURE__ */ new Set(["true", "false", "mixed", "unknown"]);
11
+ var HIDDEN_REMNIC_STATUSES = /* @__PURE__ */ new Set([
12
+ "forgotten",
13
+ "pending_review",
14
+ "quarantined",
15
+ "rejected"
16
+ ]);
17
+ function normalizeClaimDraft(draft, options) {
18
+ const now = normalizeIsoTimestamp("now", options.now);
19
+ const statement = cleanRequiredString("statement", draft.statement, 5e3);
20
+ const deadline = draft.deadline === void 0 ? void 0 : normalizeIsoTimestamp("deadline", draft.deadline);
21
+ const kind = normalizeKind(draft.kind ?? (deadline ? "prediction" : "claim"));
22
+ const stance = normalizeStance(draft.stance);
23
+ const confidence = normalizeUnitInterval("confidence", draft.confidence);
24
+ const scope = normalizeScope(draft.scope);
25
+ return {
26
+ statement,
27
+ kind,
28
+ stance,
29
+ confidence,
30
+ scope,
31
+ deadline,
32
+ evidenceLinks: normalizeStringList("evidenceLinks", draft.evidenceLinks ?? [], 100, 2048),
33
+ status: "active",
34
+ createdAt: now,
35
+ updatedAt: now,
36
+ parentIds: [],
37
+ ...options.sourceText ? { sourceText: options.sourceText } : {}
38
+ };
39
+ }
40
+ function normalizeJudgeResult(raw, priorClaimId) {
41
+ if (!isRecord(raw)) {
42
+ throw new Error("judge result must be an object");
43
+ }
44
+ const classification = normalizeJudgeClassification(raw.classification);
45
+ return {
46
+ priorClaimId,
47
+ classification,
48
+ confidence: normalizeUnitInterval("judge confidence", raw.confidence ?? 0.5),
49
+ rationale: cleanRequiredString("judge rationale", raw.rationale ?? "", 2e3)
50
+ };
51
+ }
52
+ function normalizeChallenge(raw, priorClaimIds) {
53
+ if (!isRecord(raw)) {
54
+ throw new Error("challenge must be an object");
55
+ }
56
+ const question = cleanRequiredString("challenge question", raw.question, 2e3);
57
+ const parsedPriorIds = normalizeStringList(
58
+ "challenge priorClaimIds",
59
+ Array.isArray(raw.priorClaimIds) ? raw.priorClaimIds : priorClaimIds,
60
+ 50,
61
+ 256
62
+ );
63
+ const allowedPriorIds = new Set(priorClaimIds);
64
+ const safePriorIds = parsedPriorIds.filter((id) => allowedPriorIds.has(id));
65
+ const suggestedActions = Array.isArray(raw.suggestedActions) ? raw.suggestedActions.filter(
66
+ (value) => value === "supersede" || value === "split" || value === "resolve" || value === "ignore"
67
+ ) : [];
68
+ return {
69
+ question,
70
+ priorClaimIds: safePriorIds.length > 0 ? safePriorIds : priorClaimIds,
71
+ suggestedActions: suggestedActions.length > 0 ? [...new Set(suggestedActions)] : ["supersede", "split", "ignore"]
72
+ };
73
+ }
74
+ function normalizePredictionGrade(raw) {
75
+ if (!isRecord(raw)) {
76
+ throw new Error("prediction grade must be an object");
77
+ }
78
+ return {
79
+ verdict: normalizePredictionVerdict(raw.verdict),
80
+ actualConfidence: normalizeUnitInterval("actualConfidence", raw.actualConfidence),
81
+ rationale: cleanRequiredString("grade rationale", raw.rationale ?? "", 2e3),
82
+ ...typeof raw.source === "string" && raw.source.trim() ? { source: raw.source.trim().slice(0, 2048) } : {}
83
+ };
84
+ }
85
+ function createResolution(input) {
86
+ const actualConfidence = normalizeUnitInterval("actualConfidence", input.actualConfidence);
87
+ const predictedConfidence = normalizeUnitInterval("predictedConfidence", input.predictedConfidence);
88
+ const resolvedAt = normalizeIsoTimestamp("resolvedAt", input.resolvedAt);
89
+ return {
90
+ verdict: normalizePredictionVerdict(input.verdict),
91
+ actualConfidence,
92
+ resolvedAt,
93
+ ...input.source ? { source: cleanRequiredString("source", input.source, 2048) } : {},
94
+ ...input.notes ? { notes: cleanRequiredString("notes", input.notes, 5e3) } : {},
95
+ brierScore: computeBrierScore(predictedConfidence, actualConfidence)
96
+ };
97
+ }
98
+ function computeBrierScore(predictedConfidence, actualConfidence) {
99
+ const predicted = normalizeUnitInterval("predictedConfidence", predictedConfidence);
100
+ const actual = normalizeUnitInterval("actualConfidence", actualConfidence);
101
+ return roundMetric((predicted - actual) ** 2);
102
+ }
103
+ function roundMetric(value) {
104
+ if (!Number.isFinite(value)) {
105
+ throw new Error(`metric must be finite, got ${String(value)}`);
106
+ }
107
+ return Math.round(value * 1e6) / 1e6;
108
+ }
109
+ function claimToStructuredAttributes(claim) {
110
+ const attrs = {
111
+ "ledger.schemaVersion": LEDGER_SCHEMA_VERSION,
112
+ "ledger.kind": claim.kind,
113
+ "ledger.stance": claim.stance,
114
+ "ledger.confidence": String(claim.confidence),
115
+ "ledger.entities": JSON.stringify(claim.scope.entities),
116
+ "ledger.status": claim.status,
117
+ "ledger.createdAt": claim.createdAt,
118
+ "ledger.updatedAt": claim.updatedAt,
119
+ "ledger.parentIds": JSON.stringify(claim.parentIds)
120
+ };
121
+ if (claim.scope.domain) attrs["ledger.domain"] = claim.scope.domain;
122
+ if (claim.scope.timeWindow?.start) attrs["ledger.timeWindowStart"] = claim.scope.timeWindow.start;
123
+ if (claim.scope.timeWindow?.end) attrs["ledger.timeWindowEnd"] = claim.scope.timeWindow.end;
124
+ if (claim.deadline) attrs["ledger.deadline"] = claim.deadline;
125
+ if (claim.evidenceLinks.length > 0) attrs["ledger.evidenceLinks"] = JSON.stringify(claim.evidenceLinks);
126
+ if (claim.supersedes) attrs["ledger.supersedes"] = claim.supersedes;
127
+ if (claim.supersededBy) attrs["ledger.supersededBy"] = claim.supersededBy;
128
+ if (claim.snoozedUntil) attrs["ledger.snoozedUntil"] = claim.snoozedUntil;
129
+ if (claim.ignoredAt) attrs["ledger.ignoredAt"] = claim.ignoredAt;
130
+ if (claim.ignoredReason) attrs["ledger.ignoredReason"] = claim.ignoredReason;
131
+ if (claim.sourceText) attrs["ledger.sourceText"] = claim.sourceText.slice(0, 5e3);
132
+ if (claim.resolution) {
133
+ attrs["ledger.verdict"] = claim.resolution.verdict;
134
+ attrs["ledger.actualConfidence"] = String(claim.resolution.actualConfidence);
135
+ attrs["ledger.resolvedAt"] = claim.resolution.resolvedAt;
136
+ if (claim.resolution.source) attrs["ledger.resolutionSource"] = claim.resolution.source;
137
+ if (claim.resolution.notes) attrs["ledger.resolutionNotes"] = claim.resolution.notes;
138
+ if (claim.resolution.brierScore !== void 0) attrs["ledger.brierScore"] = String(claim.resolution.brierScore);
139
+ }
140
+ return attrs;
141
+ }
142
+ function claimTags(claim) {
143
+ const tags = [LEDGER_TAG, `${LEDGER_TAG}:${claim.kind}`, `${LEDGER_TAG}:status:${claim.status}`];
144
+ if (claim.scope.domain) {
145
+ tags.push(`${LEDGER_TAG}:domain:${slugTagValue(claim.scope.domain)}`);
146
+ }
147
+ if (claim.deadline) tags.push(`${LEDGER_TAG}:deadline`);
148
+ return [...new Set(tags)];
149
+ }
150
+ function serializeClaimBody(claim) {
151
+ const lines = [
152
+ "# Belief Ledger Claim",
153
+ "",
154
+ claim.statement,
155
+ "",
156
+ `Kind: ${claim.kind}`,
157
+ `Stance: ${claim.stance}`,
158
+ `Confidence: ${claim.confidence}`,
159
+ `Status: ${claim.status}`
160
+ ];
161
+ if (claim.scope.domain) lines.push(`Domain: ${claim.scope.domain}`);
162
+ if (claim.scope.entities.length > 0) lines.push(`Entities: ${claim.scope.entities.join(", ")}`);
163
+ if (claim.deadline) lines.push(`Deadline: ${claim.deadline}`);
164
+ if (claim.resolution) {
165
+ lines.push(`Verdict: ${claim.resolution.verdict}`);
166
+ lines.push(`Actual confidence: ${claim.resolution.actualConfidence}`);
167
+ if (claim.resolution.brierScore !== void 0) {
168
+ lines.push(`Brier score: ${claim.resolution.brierScore}`);
169
+ }
170
+ }
171
+ if (claim.evidenceLinks.length > 0) {
172
+ lines.push("", "Evidence:");
173
+ for (const link of claim.evidenceLinks) {
174
+ lines.push(`- ${link}`);
175
+ }
176
+ }
177
+ return `${lines.join("\n").trimEnd()}
178
+ `;
179
+ }
180
+ function claimFromMemory(memory) {
181
+ const attrs = normalizeStructuredAttributes(memory.frontmatter.structuredAttributes);
182
+ const tags = memory.frontmatter.tags ?? [];
183
+ if (!tags.includes(LEDGER_TAG) && attrs["ledger.schemaVersion"] !== LEDGER_SCHEMA_VERSION) {
184
+ return null;
185
+ }
186
+ if (isHiddenRemnicStatus(memory.frontmatter.status)) {
187
+ return null;
188
+ }
189
+ const storedStatus = normalizeStatus(attrs["ledger.status"] ?? "active");
190
+ if (memory.frontmatter.status === "archived" && storedStatus === "active") {
191
+ return null;
192
+ }
193
+ const statement = extractStatement(memory.content);
194
+ if (!statement) return null;
195
+ const entities = parseStringArrayAttribute("ledger.entities", attrs["ledger.entities"]);
196
+ const entityRef = memory.frontmatter.entityRef?.trim();
197
+ if (entityRef && !entities.includes(entityRef)) {
198
+ entities.unshift(entityRef);
199
+ }
200
+ const resolution = parseResolution(attrs);
201
+ const status = memory.frontmatter.status === "superseded" ? "superseded" : storedStatus;
202
+ const claim = {
203
+ id: memory.frontmatter.id,
204
+ memoryId: memory.frontmatter.id,
205
+ statement,
206
+ kind: normalizeKind(attrs["ledger.kind"] ?? "claim"),
207
+ stance: normalizeStance(attrs["ledger.stance"] ?? "uncertain"),
208
+ confidence: normalizeUnitInterval("confidence", attrs["ledger.confidence"] ?? memory.frontmatter.confidence),
209
+ scope: {
210
+ entities,
211
+ ...attrs["ledger.domain"] ? { domain: attrs["ledger.domain"] } : {},
212
+ ...parseTimeWindow(attrs)
213
+ },
214
+ ...attrs["ledger.deadline"] ? { deadline: normalizeIsoTimestamp("deadline", attrs["ledger.deadline"]) } : {},
215
+ evidenceLinks: parseStringArrayAttribute("ledger.evidenceLinks", attrs["ledger.evidenceLinks"]),
216
+ status,
217
+ createdAt: normalizeIsoTimestamp(
218
+ "createdAt",
219
+ attrs["ledger.createdAt"] ?? memory.frontmatter.valid_at ?? memory.frontmatter.created
220
+ ),
221
+ updatedAt: normalizeIsoTimestamp("updatedAt", attrs["ledger.updatedAt"] ?? memory.frontmatter.updated),
222
+ ...memory.frontmatter.supersedes || attrs["ledger.supersedes"] ? { supersedes: attrs["ledger.supersedes"] ?? memory.frontmatter.supersedes } : {},
223
+ ...memory.frontmatter.supersededBy || attrs["ledger.supersededBy"] ? { supersededBy: attrs["ledger.supersededBy"] ?? memory.frontmatter.supersededBy } : {},
224
+ parentIds: parseStringArrayAttribute("ledger.parentIds", attrs["ledger.parentIds"]).concat(
225
+ memory.frontmatter.lineage ?? []
226
+ ),
227
+ ...attrs["ledger.snoozedUntil"] ? { snoozedUntil: normalizeIsoTimestamp("snoozedUntil", attrs["ledger.snoozedUntil"]) } : {},
228
+ ...attrs["ledger.ignoredAt"] ? { ignoredAt: normalizeIsoTimestamp("ignoredAt", attrs["ledger.ignoredAt"]) } : {},
229
+ ...attrs["ledger.ignoredReason"] ? { ignoredReason: attrs["ledger.ignoredReason"] } : {},
230
+ ...resolution ? { resolution } : {},
231
+ ...attrs["ledger.sourceText"] ? { sourceText: attrs["ledger.sourceText"] } : {},
232
+ sourceMemory: memory
233
+ };
234
+ claim.parentIds = [...new Set(claim.parentIds.filter((value) => value !== claim.id))];
235
+ return claim;
236
+ }
237
+ function isHiddenRemnicStatus(status) {
238
+ return status !== void 0 && HIDDEN_REMNIC_STATUSES.has(status);
239
+ }
240
+ function mergeClaimPatch(claim, patch, now) {
241
+ const updated = {
242
+ ...claim,
243
+ ...patch,
244
+ scope: patch.scope ? normalizeScope(patch.scope) : claim.scope,
245
+ evidenceLinks: patch.evidenceLinks ? normalizeStringList("evidenceLinks", patch.evidenceLinks, 100, 2048) : claim.evidenceLinks,
246
+ parentIds: patch.parentIds ? normalizeStringList("parentIds", patch.parentIds, 100, 256) : claim.parentIds,
247
+ createdAt: normalizeIsoTimestamp("createdAt", patch.createdAt ?? claim.createdAt),
248
+ updatedAt: normalizeIsoTimestamp("updatedAt", patch.updatedAt ?? now)
249
+ };
250
+ updated.statement = cleanRequiredString("statement", updated.statement, 5e3);
251
+ updated.kind = normalizeKind(updated.kind);
252
+ updated.stance = normalizeStance(updated.stance);
253
+ updated.confidence = normalizeUnitInterval("confidence", updated.confidence);
254
+ updated.status = normalizeStatus(updated.status);
255
+ if (updated.resolution) updated.resolution = normalizeResolutionPatch(updated.resolution, updated.confidence);
256
+ if (updated.deadline) updated.deadline = normalizeIsoTimestamp("deadline", updated.deadline);
257
+ if (updated.snoozedUntil) updated.snoozedUntil = normalizeIsoTimestamp("snoozedUntil", updated.snoozedUntil);
258
+ if (updated.ignoredAt) updated.ignoredAt = normalizeIsoTimestamp("ignoredAt", updated.ignoredAt);
259
+ return updated;
260
+ }
261
+ function normalizeResolutionPatch(value, predictedConfidence) {
262
+ if (!isRecord(value)) {
263
+ throw new Error("resolution must be an object");
264
+ }
265
+ if (value.source !== void 0 && typeof value.source !== "string") {
266
+ throw new Error("resolution source must be a string");
267
+ }
268
+ if (value.notes !== void 0 && typeof value.notes !== "string") {
269
+ throw new Error("resolution notes must be a string");
270
+ }
271
+ return createResolution({
272
+ verdict: value.verdict,
273
+ actualConfidence: value.actualConfidence,
274
+ resolvedAt: value.resolvedAt,
275
+ source: value.source,
276
+ notes: value.notes,
277
+ predictedConfidence
278
+ });
279
+ }
280
+ function memoryFrontmatterPatchForClaim(claim) {
281
+ return {
282
+ updated: claim.updatedAt,
283
+ confidence: claim.confidence,
284
+ tags: claimTags(claim),
285
+ entityRef: claim.scope.entities[0],
286
+ supersedes: claim.supersedes,
287
+ lineage: claim.parentIds.length > 0 ? claim.parentIds : void 0,
288
+ status: remnicMemoryStatusForClaim(claim),
289
+ supersededBy: claim.supersededBy,
290
+ supersededAt: claim.status === "superseded" ? claim.updatedAt : void 0,
291
+ structuredAttributes: claimToStructuredAttributes(claim)
292
+ };
293
+ }
294
+ function remnicMemoryStatusForClaim(claim) {
295
+ switch (claim.status) {
296
+ case "active":
297
+ return "active";
298
+ case "superseded":
299
+ return "superseded";
300
+ case "ignored":
301
+ case "resolved":
302
+ case "snoozed":
303
+ return "archived";
304
+ }
305
+ }
306
+ function normalizeIsoTimestamp(field, value) {
307
+ if (typeof value !== "string" || value.trim().length === 0) {
308
+ throw new Error(`${field} must be a non-empty ISO timestamp string`);
309
+ }
310
+ const trimmed = value.trim();
311
+ validateIsoTimestampComponents(field, trimmed);
312
+ const time = Date.parse(trimmed);
313
+ if (!Number.isFinite(time)) {
314
+ throw new Error(`${field} must be a valid ISO timestamp, got ${JSON.stringify(value)}`);
315
+ }
316
+ const date = new Date(time);
317
+ if (!Number.isFinite(date.getTime())) {
318
+ throw new Error(`${field} must be within JavaScript Date bounds`);
319
+ }
320
+ return date.toISOString();
321
+ }
322
+ function validateIsoTimestampComponents(field, value) {
323
+ if (value.length < 10 || value[4] !== "-" || value[7] !== "-") {
324
+ throw new Error(`${field} must be a valid ISO timestamp, got ${JSON.stringify(value)}`);
325
+ }
326
+ const year = readFixedDigits(value, 0, 4);
327
+ const month = readFixedDigits(value, 5, 2);
328
+ const day = readFixedDigits(value, 8, 2);
329
+ if (year === void 0 || month === void 0 || day === void 0) {
330
+ throw new Error(`${field} must be a valid ISO timestamp, got ${JSON.stringify(value)}`);
331
+ }
332
+ if (value.length === 10) {
333
+ assertValidCalendarParts(field, value, year, month, day, 0, 0, 0, 0);
334
+ return;
335
+ }
336
+ const separator = value[10];
337
+ if (separator !== "T" && separator !== "t" && separator !== " ") {
338
+ throw new Error(`${field} must be a valid ISO timestamp, got ${JSON.stringify(value)}`);
339
+ }
340
+ const timeText = stripAndValidateTimeZone(field, value, value.slice(11));
341
+ const timeParts = parseIsoTimeParts(field, value, timeText);
342
+ assertValidCalendarParts(
343
+ field,
344
+ value,
345
+ year,
346
+ month,
347
+ day,
348
+ timeParts.hour,
349
+ timeParts.minute,
350
+ timeParts.second,
351
+ timeParts.millisecond
352
+ );
353
+ }
354
+ function stripAndValidateTimeZone(field, original, rawTime) {
355
+ if (!rawTime) {
356
+ throw new Error(`${field} must be a valid ISO timestamp, got ${JSON.stringify(original)}`);
357
+ }
358
+ const lastChar = rawTime[rawTime.length - 1];
359
+ if (lastChar === "Z" || lastChar === "z") {
360
+ return rawTime.slice(0, -1);
361
+ }
362
+ const offsetStart = rawTime.length - 6;
363
+ const sign = offsetStart > 0 ? rawTime[offsetStart] : void 0;
364
+ if ((sign === "+" || sign === "-") && rawTime[offsetStart + 3] === ":") {
365
+ const offsetHour = readFixedDigits(rawTime, offsetStart + 1, 2);
366
+ const offsetMinute = readFixedDigits(rawTime, offsetStart + 4, 2);
367
+ if (offsetHour === void 0 || offsetMinute === void 0 || offsetHour > 23 || offsetMinute > 59) {
368
+ throw new Error(`${field} must be a valid ISO timestamp, got ${JSON.stringify(original)}`);
369
+ }
370
+ return rawTime.slice(0, offsetStart);
371
+ }
372
+ throw new Error(`${field} must be a valid ISO timestamp, got ${JSON.stringify(original)}`);
373
+ }
374
+ function parseIsoTimeParts(field, original, value) {
375
+ if (value.length < 5 || value[2] !== ":") {
376
+ throw new Error(`${field} must be a valid ISO timestamp, got ${JSON.stringify(original)}`);
377
+ }
378
+ const hour = readFixedDigits(value, 0, 2);
379
+ const minute = readFixedDigits(value, 3, 2);
380
+ if (hour === void 0 || minute === void 0 || hour > 23 || minute > 59) {
381
+ throw new Error(`${field} must be a valid ISO timestamp, got ${JSON.stringify(original)}`);
382
+ }
383
+ if (value.length === 5) {
384
+ return { hour, minute, second: 0, millisecond: 0 };
385
+ }
386
+ if (value[5] !== ":") {
387
+ throw new Error(`${field} must be a valid ISO timestamp, got ${JSON.stringify(original)}`);
388
+ }
389
+ const second = readFixedDigits(value, 6, 2);
390
+ if (second === void 0 || second > 59) {
391
+ throw new Error(`${field} must be a valid ISO timestamp, got ${JSON.stringify(original)}`);
392
+ }
393
+ if (value.length === 8) {
394
+ return { hour, minute, second, millisecond: 0 };
395
+ }
396
+ if (value[8] !== ".") {
397
+ throw new Error(`${field} must be a valid ISO timestamp, got ${JSON.stringify(original)}`);
398
+ }
399
+ const fraction = value.slice(9);
400
+ if (fraction.length === 0 || fraction.length > 3 || !isAllDigits(fraction)) {
401
+ throw new Error(`${field} must be a valid ISO timestamp, got ${JSON.stringify(original)}`);
402
+ }
403
+ return {
404
+ hour,
405
+ minute,
406
+ second,
407
+ millisecond: Number(fraction.padEnd(3, "0"))
408
+ };
409
+ }
410
+ function assertValidCalendarParts(field, original, year, month, day, hour, minute, second, millisecond) {
411
+ const time = Date.UTC(year, month - 1, day, hour, minute, second, millisecond);
412
+ const date = new Date(time);
413
+ if (!Number.isFinite(time) || date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day || date.getUTCHours() !== hour || date.getUTCMinutes() !== minute || date.getUTCSeconds() !== second || date.getUTCMilliseconds() !== millisecond) {
414
+ throw new Error(`${field} must be a valid ISO timestamp, got ${JSON.stringify(original)}`);
415
+ }
416
+ }
417
+ function readFixedDigits(value, start, length) {
418
+ const end = start + length;
419
+ if (end > value.length) return void 0;
420
+ const slice = value.slice(start, end);
421
+ return isAllDigits(slice) ? Number(slice) : void 0;
422
+ }
423
+ function isAllDigits(value) {
424
+ if (!value) return false;
425
+ for (const char of value) {
426
+ const code = char.charCodeAt(0);
427
+ if (code < 48 || code > 57) return false;
428
+ }
429
+ return true;
430
+ }
431
+ function normalizeUnitInterval(field, value) {
432
+ const num = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
433
+ if (!Number.isFinite(num)) {
434
+ throw new Error(`${field} must be a finite number in [0, 1], got ${String(value)}`);
435
+ }
436
+ if (num < 0 || num > 1) {
437
+ throw new Error(`${field} must be in [0, 1], got ${num}`);
438
+ }
439
+ return num;
440
+ }
441
+ function normalizeScope(value) {
442
+ const scope = value ?? {};
443
+ const entities = normalizeStringList("entities", scope.entities ?? [], 50, 256);
444
+ const domain = typeof scope.domain === "string" && scope.domain.trim().length > 0 ? scope.domain.trim().slice(0, 256) : void 0;
445
+ const timeWindow = scope.timeWindow ? normalizeTimeWindow(scope.timeWindow) : void 0;
446
+ return {
447
+ entities,
448
+ ...domain ? { domain } : {},
449
+ ...timeWindow ? { timeWindow } : {}
450
+ };
451
+ }
452
+ function normalizeTimeWindow(value) {
453
+ const start = value.start === void 0 ? void 0 : normalizeIsoTimestamp("timeWindow.start", value.start);
454
+ const end = value.end === void 0 ? void 0 : normalizeIsoTimestamp("timeWindow.end", value.end);
455
+ if (start && end && Date.parse(start) >= Date.parse(end)) {
456
+ throw new Error("timeWindow.start must be before timeWindow.end");
457
+ }
458
+ return start || end ? { ...start ? { start } : {}, ...end ? { end } : {} } : void 0;
459
+ }
460
+ function parseTimeWindow(attrs) {
461
+ const start = attrs["ledger.timeWindowStart"];
462
+ const end = attrs["ledger.timeWindowEnd"];
463
+ const timeWindow = normalizeTimeWindow({ start, end });
464
+ return timeWindow ? { timeWindow } : {};
465
+ }
466
+ function normalizeKind(value) {
467
+ if (typeof value !== "string" || !KIND_VALUES.has(value)) {
468
+ throw new Error(`kind must be one of ${[...KIND_VALUES].join(", ")}`);
469
+ }
470
+ return value;
471
+ }
472
+ function normalizeStance(value) {
473
+ if (typeof value !== "string" || !STANCE_VALUES.has(value)) {
474
+ throw new Error(`stance must be one of ${[...STANCE_VALUES].join(", ")}`);
475
+ }
476
+ return value;
477
+ }
478
+ function normalizeStatus(value) {
479
+ if (typeof value !== "string" || !STATUS_VALUES.has(value)) {
480
+ throw new Error(`status must be one of ${[...STATUS_VALUES].join(", ")}`);
481
+ }
482
+ return value;
483
+ }
484
+ function normalizeJudgeClassification(value) {
485
+ if (typeof value !== "string" || !JUDGE_VALUES.has(value)) {
486
+ throw new Error(`classification must be one of ${[...JUDGE_VALUES].join(", ")}`);
487
+ }
488
+ return value;
489
+ }
490
+ function normalizePredictionVerdict(value) {
491
+ if (typeof value !== "string" || !VERDICT_VALUES.has(value)) {
492
+ throw new Error(`verdict must be one of ${[...VERDICT_VALUES].join(", ")}`);
493
+ }
494
+ return value;
495
+ }
496
+ function parseResolution(attrs) {
497
+ if (!attrs["ledger.verdict"]) return void 0;
498
+ const actualConfidence = normalizeUnitInterval("actualConfidence", attrs["ledger.actualConfidence"]);
499
+ const verdict = normalizePredictionVerdict(attrs["ledger.verdict"]);
500
+ const resolvedAt = normalizeIsoTimestamp("resolvedAt", attrs["ledger.resolvedAt"]);
501
+ return {
502
+ verdict,
503
+ actualConfidence,
504
+ resolvedAt,
505
+ ...attrs["ledger.resolutionSource"] ? { source: attrs["ledger.resolutionSource"] } : {},
506
+ ...attrs["ledger.resolutionNotes"] ? { notes: attrs["ledger.resolutionNotes"] } : {},
507
+ ...attrs["ledger.brierScore"] !== void 0 ? { brierScore: roundMetric(Number(attrs["ledger.brierScore"])) } : {}
508
+ };
509
+ }
510
+ function normalizeStructuredAttributes(value) {
511
+ if (!isRecord(value)) return {};
512
+ const result = {};
513
+ for (const [key, raw] of Object.entries(value)) {
514
+ if (typeof raw === "string") {
515
+ result[key] = raw;
516
+ } else if (typeof raw === "number" || typeof raw === "boolean") {
517
+ result[key] = String(raw);
518
+ }
519
+ }
520
+ return result;
521
+ }
522
+ function parseStringArrayAttribute(field, value) {
523
+ if (value === void 0 || value.trim() === "") return [];
524
+ let parsed;
525
+ try {
526
+ parsed = JSON.parse(value);
527
+ } catch {
528
+ throw new Error(`${field} must be a JSON array`);
529
+ }
530
+ return normalizeStringList(field, parsed, 100, 2048);
531
+ }
532
+ function normalizeStringList(field, value, maxItems, maxLength) {
533
+ if (!Array.isArray(value)) {
534
+ throw new Error(`${field} must be an array`);
535
+ }
536
+ const result = [];
537
+ const seen = /* @__PURE__ */ new Set();
538
+ for (const item of value) {
539
+ if (typeof item !== "string") {
540
+ throw new Error(`${field} entries must be strings`);
541
+ }
542
+ const trimmed = item.trim();
543
+ if (!trimmed) continue;
544
+ const clipped = trimmed.slice(0, maxLength);
545
+ if (seen.has(clipped)) continue;
546
+ seen.add(clipped);
547
+ result.push(clipped);
548
+ if (result.length > maxItems) {
549
+ throw new Error(`${field} must contain at most ${maxItems} entries`);
550
+ }
551
+ }
552
+ return result;
553
+ }
554
+ function cleanRequiredString(field, value, maxLength) {
555
+ if (typeof value !== "string") {
556
+ throw new Error(`${field} must be a string`);
557
+ }
558
+ const trimmed = value.trim();
559
+ if (!trimmed) {
560
+ throw new Error(`${field} must not be empty`);
561
+ }
562
+ if (trimmed.length > maxLength) {
563
+ throw new Error(`${field} must be at most ${maxLength} characters`);
564
+ }
565
+ return trimmed;
566
+ }
567
+ function extractStatement(content) {
568
+ const normalized = content.replace(/\r\n/g, "\n").trim();
569
+ const lines = normalized.split("\n");
570
+ const titleIndex = lines.findIndex((line) => line.trim() === "# Belief Ledger Claim");
571
+ if (titleIndex >= 0) {
572
+ const statementLines = [];
573
+ let started = false;
574
+ for (let i = titleIndex + 1; i < lines.length; i += 1) {
575
+ const line = lines[i] ?? "";
576
+ const trimmed = line.trim();
577
+ if (!started && !trimmed) continue;
578
+ if (isClaimMetadataLine(trimmed)) {
579
+ break;
580
+ }
581
+ started = true;
582
+ statementLines.push(line.trimEnd());
583
+ }
584
+ const statement = statementLines.join("\n").trim();
585
+ if (statement) {
586
+ return statement;
587
+ }
588
+ }
589
+ const first = lines.find((line) => line.trim() && !line.trim().startsWith("#"));
590
+ return first?.trim() ?? "";
591
+ }
592
+ function isClaimMetadataLine(line) {
593
+ return CLAIM_METADATA_PREFIXES.some((prefix) => line.startsWith(prefix));
594
+ }
595
+ var CLAIM_METADATA_PREFIXES = [
596
+ "Kind:",
597
+ "Stance:",
598
+ "Confidence:",
599
+ "Status:",
600
+ "Domain:",
601
+ "Entities:",
602
+ "Deadline:",
603
+ "Verdict:",
604
+ "Actual confidence:",
605
+ "Brier score:"
606
+ ];
607
+ function slugTagValue(value) {
608
+ let slug = "";
609
+ let pendingDash = false;
610
+ for (const char of value.toLowerCase()) {
611
+ const code = char.charCodeAt(0);
612
+ const isAsciiLetter = code >= 97 && code <= 122;
613
+ const isDigit = code >= 48 && code <= 57;
614
+ if (isAsciiLetter || isDigit) {
615
+ if (pendingDash && slug) slug += "-";
616
+ slug += char;
617
+ pendingDash = false;
618
+ } else if (slug) {
619
+ pendingDash = true;
620
+ }
621
+ }
622
+ return slug || "unknown";
623
+ }
624
+ function isRecord(value) {
625
+ return typeof value === "object" && value !== null && !Array.isArray(value);
626
+ }
627
+
628
+ // src/reflection.ts
629
+ var DEFAULT_DORMANT_AFTER_DAYS = 60;
630
+ function buildReflectionReport(claims, options = {}) {
631
+ const nowIso = normalizeIsoTimestamp("reflection.now", options.now ?? (/* @__PURE__ */ new Date()).toISOString());
632
+ const now = Date.parse(nowIso);
633
+ if (!Number.isFinite(now)) {
634
+ throw new Error(`reflection now must be a valid ISO timestamp, got ${String(options.now)}`);
635
+ }
636
+ const resolvedPredictions = claims.filter(
637
+ (claim) => claim.kind === "prediction" && claim.resolution?.brierScore !== void 0
638
+ );
639
+ const brierScore = resolvedPredictions.length > 0 ? roundMetric(mean(resolvedPredictions.map((claim) => claim.resolution?.brierScore ?? 0))) : void 0;
640
+ return {
641
+ generatedAt: new Date(now).toISOString(),
642
+ totalClaims: claims.length,
643
+ activeClaims: claims.filter((claim) => claim.status === "active").length,
644
+ resolvedPredictions: resolvedPredictions.length,
645
+ ...brierScore !== void 0 ? { brierScore } : {},
646
+ calibrationBins: buildCalibrationBins(resolvedPredictions),
647
+ domains: buildDomainCalibration(resolvedPredictions),
648
+ flippedClaims: buildFlippedClaims(claims),
649
+ dormantTopics: buildDormantTopics(claims, now, options.dormantAfterDays ?? DEFAULT_DORMANT_AFTER_DAYS)
650
+ };
651
+ }
652
+ function buildCalibrationBins(claims) {
653
+ const bins = [
654
+ { minConfidence: 0, maxConfidence: 0.2 },
655
+ { minConfidence: 0.2, maxConfidence: 0.4 },
656
+ { minConfidence: 0.4, maxConfidence: 0.6 },
657
+ { minConfidence: 0.6, maxConfidence: 0.8 },
658
+ { minConfidence: 0.8, maxConfidence: 1.000001 }
659
+ ];
660
+ return bins.map((bin) => {
661
+ const members = claims.filter(
662
+ (claim) => claim.confidence >= bin.minConfidence && claim.confidence < bin.maxConfidence
663
+ );
664
+ if (members.length === 0) return null;
665
+ const predicted = mean(members.map((claim) => claim.confidence));
666
+ const actual = mean(members.map((claim) => claim.resolution?.actualConfidence ?? 0.5));
667
+ return {
668
+ minConfidence: bin.minConfidence,
669
+ maxConfidence: bin.maxConfidence > 1 ? 1 : bin.maxConfidence,
670
+ count: members.length,
671
+ meanPredictedConfidence: roundMetric(predicted),
672
+ meanActualConfidence: roundMetric(actual),
673
+ calibrationError: roundMetric(predicted - actual)
674
+ };
675
+ }).filter((bin) => bin !== null);
676
+ }
677
+ function buildDomainCalibration(claims) {
678
+ const grouped = /* @__PURE__ */ new Map();
679
+ for (const claim of claims) {
680
+ const domain = claim.scope.domain?.trim() || "uncategorized";
681
+ grouped.set(domain, [...grouped.get(domain) ?? [], claim]);
682
+ }
683
+ return [...grouped.entries()].map(([domain, members]) => {
684
+ const predicted = mean(members.map((claim) => claim.confidence));
685
+ const actual = mean(members.map((claim) => claim.resolution?.actualConfidence ?? 0.5));
686
+ const error = predicted - actual;
687
+ const tendency = Math.abs(error) <= 0.1 ? "well_calibrated" : error > 0 ? "overconfident" : "underconfident";
688
+ return {
689
+ domain,
690
+ count: members.length,
691
+ brierScore: roundMetric(mean(members.map((claim) => claim.resolution?.brierScore ?? 0))),
692
+ meanPredictedConfidence: roundMetric(predicted),
693
+ meanActualConfidence: roundMetric(actual),
694
+ tendency
695
+ };
696
+ }).sort((a, b) => {
697
+ const countOrder = b.count - a.count;
698
+ if (countOrder !== 0) return countOrder;
699
+ return a.domain.localeCompare(b.domain);
700
+ });
701
+ }
702
+ function buildFlippedClaims(claims) {
703
+ const byId = new Map(claims.map((claim) => [claim.id, claim]));
704
+ const groups = /* @__PURE__ */ new Map();
705
+ for (const claim of claims) {
706
+ const root = findRootClaimId(claim, byId);
707
+ groups.set(root, [...groups.get(root) ?? [], claim]);
708
+ }
709
+ return [...groups.entries()].map(([rootClaimId, members]) => {
710
+ const sorted = [...members].sort((a, b) => {
711
+ const createdOrder = Date.parse(a.createdAt) - Date.parse(b.createdAt);
712
+ if (createdOrder !== 0) return createdOrder;
713
+ return a.id.localeCompare(b.id);
714
+ });
715
+ let flipCount = 0;
716
+ for (let i = 1; i < sorted.length; i += 1) {
717
+ if (sorted[i - 1]?.stance !== sorted[i]?.stance) flipCount += 1;
718
+ }
719
+ if (flipCount === 0) return null;
720
+ return {
721
+ rootClaimId,
722
+ claimIds: sorted.map((claim) => claim.id),
723
+ statements: sorted.map((claim) => claim.statement),
724
+ flipCount
725
+ };
726
+ }).filter((item) => item !== null).sort((a, b) => {
727
+ const flipOrder = b.flipCount - a.flipCount;
728
+ if (flipOrder !== 0) return flipOrder;
729
+ return a.rootClaimId.localeCompare(b.rootClaimId);
730
+ });
731
+ }
732
+ function findRootClaimId(claim, byId) {
733
+ const seen = /* @__PURE__ */ new Set();
734
+ let cursor = claim;
735
+ while (cursor) {
736
+ if (seen.has(cursor.id)) return cursor.id;
737
+ seen.add(cursor.id);
738
+ const parentId = cursor.supersedes ?? cursor.parentIds[0];
739
+ if (!parentId) return cursor.id;
740
+ const parent = byId.get(parentId);
741
+ if (!parent) return parentId;
742
+ cursor = parent;
743
+ }
744
+ return claim.id;
745
+ }
746
+ function buildDormantTopics(claims, nowMs, dormantAfterDays) {
747
+ if (!Number.isFinite(dormantAfterDays) || dormantAfterDays < 0) {
748
+ throw new Error(`dormantAfterDays must be a non-negative number, got ${String(dormantAfterDays)}`);
749
+ }
750
+ const topics = /* @__PURE__ */ new Map();
751
+ for (const claim of claims) {
752
+ const activityAt = claimActivityAt(claim);
753
+ for (const topic of claimTopics(claim)) {
754
+ const existing = topics.get(topic);
755
+ if (!existing || Date.parse(activityAt) > Date.parse(existing.lastClaimAt)) {
756
+ topics.set(topic, {
757
+ lastClaimAt: activityAt,
758
+ claimCount: (existing?.claimCount ?? 0) + 1
759
+ });
760
+ } else {
761
+ existing.claimCount += 1;
762
+ }
763
+ }
764
+ }
765
+ return [...topics.entries()].map(([topic, entry]) => {
766
+ const ageDays = (nowMs - Date.parse(entry.lastClaimAt)) / (24 * 60 * 60 * 1e3);
767
+ return {
768
+ topic,
769
+ lastClaimAt: entry.lastClaimAt,
770
+ daysSilent: Math.max(0, Math.floor(ageDays)),
771
+ claimCount: entry.claimCount
772
+ };
773
+ }).filter((topic) => topic.daysSilent >= dormantAfterDays).sort((a, b) => {
774
+ const daysOrder = b.daysSilent - a.daysSilent;
775
+ if (daysOrder !== 0) return daysOrder;
776
+ return a.topic.localeCompare(b.topic);
777
+ });
778
+ }
779
+ function claimActivityAt(claim) {
780
+ return Date.parse(claim.updatedAt) >= Date.parse(claim.createdAt) ? claim.updatedAt : claim.createdAt;
781
+ }
782
+ function claimTopics(claim) {
783
+ const seen = /* @__PURE__ */ new Set();
784
+ const topics = [];
785
+ for (const topic of [...claim.scope.domain ? [claim.scope.domain] : [], ...claim.scope.entities]) {
786
+ const trimmed = topic.trim();
787
+ if (!trimmed) continue;
788
+ const key = trimmed.toLowerCase();
789
+ if (seen.has(key)) continue;
790
+ seen.add(key);
791
+ topics.push(trimmed);
792
+ }
793
+ return topics;
794
+ }
795
+ function mean(values) {
796
+ if (values.length === 0) return 0;
797
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
798
+ }
799
+
800
+ // src/remnic-store.ts
801
+ import { sanitizeMemoryContent } from "@remnic/core/sanitize";
802
+ import { ContentHashIndex, normalizeAttributePairs } from "@remnic/core/storage";
803
+ function serializeClaimMemoryContent(claim) {
804
+ const attributes = normalizeAttributePairs(claimToStructuredAttributes(claim));
805
+ return `${serializeClaimBody(claim)}
806
+ [Attributes: ${attributes}]`;
807
+ }
808
+ var RemnicLedgerStore = class {
809
+ storage;
810
+ now;
811
+ source;
812
+ shouldWriteEntityLinks;
813
+ constructor(storage, options = {}) {
814
+ this.storage = storage;
815
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
816
+ this.source = options.source ?? "belief-ledger";
817
+ this.shouldWriteEntityLinks = options.writeEntityLinks ?? true;
818
+ }
819
+ async createClaim(input) {
820
+ const pending = {
821
+ ...input,
822
+ id: "pending",
823
+ memoryId: "pending"
824
+ };
825
+ const memoryId = await this.storage.writeMemory("fact", serializeClaimBody(pending), {
826
+ actor: this.source,
827
+ confidence: pending.confidence,
828
+ tags: claimTags(pending),
829
+ entityRef: pending.scope.entities[0],
830
+ source: this.source,
831
+ supersedes: pending.supersedes,
832
+ lineage: pending.parentIds.length > 0 ? pending.parentIds : void 0,
833
+ memoryKind: "note",
834
+ validAt: pending.createdAt,
835
+ structuredAttributes: claimToStructuredAttributes(pending),
836
+ status: remnicMemoryStatusForClaim(pending)
837
+ });
838
+ const claim = await this.getClaim(memoryId);
839
+ if (!claim) {
840
+ throw new Error(`created claim ${memoryId} could not be read back`);
841
+ }
842
+ await this.writeEntityLinksBestEffort(claim);
843
+ return claim;
844
+ }
845
+ async getClaim(id) {
846
+ const memory = await this.getLedgerMemoryById(id);
847
+ return memory ? this.claimFromMemorySafely(memory) : null;
848
+ }
849
+ async listClaims(filter = {}) {
850
+ const memories = await this.readAllLedgerCandidateMemories();
851
+ const statuses = filter.statuses ? new Set(filter.statuses) : null;
852
+ const kinds = filter.kinds ? new Set(filter.kinds) : null;
853
+ return memories.map((memory) => this.claimFromMemorySafely(memory)).filter((claim) => claim !== null).filter((claim) => !statuses || statuses.has(claim.status)).filter((claim) => !kinds || kinds.has(claim.kind)).sort((a, b) => {
854
+ const updatedOrder = Date.parse(b.updatedAt) - Date.parse(a.updatedAt);
855
+ if (updatedOrder !== 0) return updatedOrder;
856
+ return a.id.localeCompare(b.id);
857
+ });
858
+ }
859
+ async updateClaim(id, patch) {
860
+ const memory = await this.getLedgerMemoryById(id);
861
+ const existing = this.claimFromMemorySafely(memory);
862
+ if (!existing) {
863
+ throw new Error(`claim ${id} not found`);
864
+ }
865
+ const updated = mergeClaimPatch(existing, patch, this.now().toISOString());
866
+ const contentUpdated = await this.writeClaimMemory(memory, updated, { actor: this.source });
867
+ if (!contentUpdated) {
868
+ throw new Error(`claim ${id} content update failed`);
869
+ }
870
+ const reread = await this.getClaim(id);
871
+ if (!reread) {
872
+ throw new Error(`claim ${id} could not be read after update`);
873
+ }
874
+ await this.writeEntityLinksBestEffort(reread);
875
+ return reread;
876
+ }
877
+ async supersedeClaim(priorId, newId, reason) {
878
+ const cleanReason = reason.trim();
879
+ if (!cleanReason) {
880
+ throw new Error("supersede reason must not be empty");
881
+ }
882
+ const prior = await this.getClaim(priorId);
883
+ const next = await this.getClaim(newId);
884
+ if (!prior || !next) return false;
885
+ const parentIds = [.../* @__PURE__ */ new Set([...next.parentIds, priorId])];
886
+ await this.updateClaim(newId, {
887
+ supersedes: priorId,
888
+ parentIds,
889
+ updatedAt: this.now().toISOString()
890
+ });
891
+ try {
892
+ await this.updateClaim(priorId, {
893
+ status: "superseded",
894
+ supersededBy: newId,
895
+ updatedAt: this.now().toISOString()
896
+ });
897
+ } catch (error) {
898
+ const restored = await this.restoreClaimBestEffort(next);
899
+ if (!restored) {
900
+ const message = error instanceof Error ? error.message : String(error);
901
+ throw new Error(`claim ${newId} rollback failed after prior supersession update failed: ${message}`);
902
+ }
903
+ throw error;
904
+ }
905
+ await this.writeSupersessionCorrectionBestEffort(prior, next, cleanReason);
906
+ return true;
907
+ }
908
+ async getLedgerMemoryById(id) {
909
+ const memories = await this.readAllLedgerCandidateMemories();
910
+ return memories.find((memory) => memory.frontmatter.id === id) ?? null;
911
+ }
912
+ async readAllLedgerCandidateMemories() {
913
+ const [hot, cold, archived] = await Promise.all([
914
+ this.storage.readAllMemories(),
915
+ this.storage.readAllColdMemories(),
916
+ this.storage.readArchivedMemories()
917
+ ]);
918
+ const byId = /* @__PURE__ */ new Map();
919
+ for (const memory of [...hot, ...cold, ...archived]) {
920
+ if (!byId.has(memory.frontmatter.id)) {
921
+ byId.set(memory.frontmatter.id, memory);
922
+ }
923
+ }
924
+ return [...byId.values()];
925
+ }
926
+ async writeClaimMemory(memory, claim, options) {
927
+ if (!memory) return false;
928
+ const sanitized = sanitizeMemoryContent(serializeClaimMemoryContent(claim));
929
+ const relatedMemoryIds = [
930
+ ...claim.supersedes ? [claim.supersedes] : [],
931
+ ...claim.supersededBy ? [claim.supersededBy] : [],
932
+ ...claim.parentIds
933
+ ];
934
+ return this.storage.writeMemoryFrontmatter(
935
+ { ...memory, content: sanitized.text },
936
+ {
937
+ ...memoryFrontmatterPatchForClaim(claim),
938
+ contentHash: ContentHashIndex.computeHash(sanitized.text)
939
+ },
940
+ {
941
+ actor: options.actor,
942
+ reasonCode: "belief-ledger-update",
943
+ relatedMemoryIds
944
+ }
945
+ );
946
+ }
947
+ async restoreClaimBestEffort(claim) {
948
+ try {
949
+ return await this.writeClaimMemory(claim.sourceMemory ?? await this.getLedgerMemoryById(claim.id), claim, {
950
+ actor: this.source
951
+ });
952
+ } catch {
953
+ return false;
954
+ }
955
+ }
956
+ async writeSupersessionCorrectionBestEffort(prior, next, reason) {
957
+ try {
958
+ await this.storage.writeMemory(
959
+ "correction",
960
+ `Superseded: ${prior.statement}
961
+
962
+ Replacement: ${next.statement}
963
+
964
+ Reason: ${reason}`,
965
+ {
966
+ actor: this.source,
967
+ confidence: 1,
968
+ tags: ["belief-ledger:audit", "supersession", "auto-resolved"],
969
+ source: this.source,
970
+ lineage: [prior.id, next.id]
971
+ }
972
+ );
973
+ } catch {
974
+ }
975
+ }
976
+ claimFromMemorySafely(memory) {
977
+ if (!memory) return null;
978
+ try {
979
+ return claimFromMemory(memory);
980
+ } catch {
981
+ return null;
982
+ }
983
+ }
984
+ async writeEntityLinksBestEffort(claim) {
985
+ if (!this.shouldWriteEntityLinks) return;
986
+ try {
987
+ await this.writeEntityLinks(claim);
988
+ } catch {
989
+ }
990
+ }
991
+ async writeEntityLinks(claim) {
992
+ const timestamp = normalizeIsoTimestamp("createdAt", claim.createdAt);
993
+ for (const entity of claim.scope.entities) {
994
+ await this.storage.writeEntity(entity, "topic", [], {
995
+ timestamp,
996
+ source: this.source,
997
+ structuredSections: [
998
+ {
999
+ key: "belief-ledger",
1000
+ title: "Belief Ledger",
1001
+ facts: beliefLedgerEntityFacts(claim)
1002
+ }
1003
+ ]
1004
+ });
1005
+ }
1006
+ }
1007
+ };
1008
+ function beliefLedgerEntityFacts(claim) {
1009
+ const prefix = `claim=${claim.id}; status=${claim.status}; updatedAt=${claim.updatedAt}`;
1010
+ return [
1011
+ `${prefix}; ${claim.kind}: ${claim.statement}`,
1012
+ `${prefix}; stance=${claim.stance}; confidence=${claim.confidence}`
1013
+ ];
1014
+ }
1015
+
1016
+ // src/retrieval.ts
1017
+ var DEFAULT_LIMIT = 8;
1018
+ var STOPWORDS = /* @__PURE__ */ new Set([
1019
+ "a",
1020
+ "an",
1021
+ "and",
1022
+ "are",
1023
+ "as",
1024
+ "at",
1025
+ "be",
1026
+ "by",
1027
+ "for",
1028
+ "from",
1029
+ "has",
1030
+ "have",
1031
+ "i",
1032
+ "in",
1033
+ "is",
1034
+ "it",
1035
+ "of",
1036
+ "on",
1037
+ "or",
1038
+ "that",
1039
+ "the",
1040
+ "this",
1041
+ "to",
1042
+ "was",
1043
+ "were",
1044
+ "will",
1045
+ "with"
1046
+ ]);
1047
+ async function retrievePriorClaims(query, store, options = {}) {
1048
+ const limit = normalizeLimit(options.limit);
1049
+ const referenceTimeMs = Date.parse(normalizeIsoTimestamp("retrieval.now", options.now ?? query.createdAt));
1050
+ const includeStatuses = options.includeStatuses ?? ["active", "snoozed"];
1051
+ const claims = (await store.listClaims({ statuses: includeStatuses })).filter((claim) => claim.id !== query.id).filter((claim) => isRetrievableAt(claim, referenceTimeMs));
1052
+ const semanticScores = options.semantic ? await collectSemanticScores(query, claims, limit, options) : /* @__PURE__ */ new Map();
1053
+ const candidates = claims.map((claim) => scoreCandidate(query, claim, semanticScores.get(claim.id), referenceTimeMs)).filter((candidate) => candidate.score > 0).sort(compareCandidates);
1054
+ const sliced = candidates.slice(0, limit);
1055
+ if (!options.reranker) return sliced;
1056
+ const reranked = await options.reranker.rerank({ query, candidates: sliced, limit });
1057
+ return reranked.slice(0, limit);
1058
+ }
1059
+ function isRetrievableAt(claim, referenceTimeMs) {
1060
+ if (claim.status !== "snoozed" || !claim.snoozedUntil) return true;
1061
+ return Date.parse(claim.snoozedUntil) <= referenceTimeMs;
1062
+ }
1063
+ function scoreCandidate(query, claim, semanticScore, referenceTimeMs) {
1064
+ let score = 0;
1065
+ let hasTopicalMatch = false;
1066
+ const reasons = [];
1067
+ const lexical = jaccard(tokenize(query.statement), tokenize(claim.statement));
1068
+ if (lexical > 0) {
1069
+ hasTopicalMatch = true;
1070
+ const lexicalScore = lexical * 4;
1071
+ score += lexicalScore;
1072
+ reasons.push(`lexical:${round(lexicalScore)}`);
1073
+ }
1074
+ const entityOverlap = overlap(query.scope.entities.map(normalizeText), claim.scope.entities.map(normalizeText));
1075
+ if (entityOverlap > 0) {
1076
+ hasTopicalMatch = true;
1077
+ const entityScore = entityOverlap * 3;
1078
+ score += entityScore;
1079
+ reasons.push(`entity:${entityOverlap}`);
1080
+ }
1081
+ if (query.scope.domain && claim.scope.domain && normalizeText(query.scope.domain) === normalizeText(claim.scope.domain)) {
1082
+ hasTopicalMatch = true;
1083
+ score += 2;
1084
+ reasons.push("domain");
1085
+ }
1086
+ if (query.stance !== claim.stance && query.stance !== "uncertain" && claim.stance !== "uncertain") {
1087
+ score += 1;
1088
+ reasons.push("stance-diff");
1089
+ }
1090
+ const recency = recencyScore(claim.updatedAt, referenceTimeMs);
1091
+ if (recency > 0) {
1092
+ score += recency;
1093
+ reasons.push(`recency:${round(recency)}`);
1094
+ }
1095
+ if (semanticScore !== void 0) {
1096
+ const semantic = Math.max(0, Math.min(1, semanticScore)) * 5;
1097
+ if (semantic > 0) hasTopicalMatch = true;
1098
+ score += semantic;
1099
+ reasons.push(`semantic:${round(semantic)}`);
1100
+ }
1101
+ return { claim, score: hasTopicalMatch ? round(score) : 0, reasons };
1102
+ }
1103
+ function compareCandidates(a, b) {
1104
+ const scoreOrder = b.score - a.score;
1105
+ if (scoreOrder !== 0) return scoreOrder;
1106
+ const updatedOrder = Date.parse(b.claim.updatedAt) - Date.parse(a.claim.updatedAt);
1107
+ if (updatedOrder !== 0) return updatedOrder;
1108
+ return a.claim.id.localeCompare(b.claim.id);
1109
+ }
1110
+ async function collectSemanticScores(query, candidates, limit, options) {
1111
+ const result = /* @__PURE__ */ new Map();
1112
+ if (!options.semantic || candidates.length === 0) return result;
1113
+ const scored = await options.semantic.scoreClaims({ query, candidates, limit });
1114
+ for (const item of scored) {
1115
+ if (!item.claimId.trim()) continue;
1116
+ if (!Number.isFinite(item.score)) continue;
1117
+ result.set(item.claimId, Math.max(0, Math.min(1, item.score)));
1118
+ }
1119
+ return result;
1120
+ }
1121
+ function normalizeLimit(value) {
1122
+ if (value === void 0) return DEFAULT_LIMIT;
1123
+ if (!Number.isInteger(value) || value < 1 || value > 100) {
1124
+ throw new Error(`retrieval limit must be an integer in [1, 100], got ${String(value)}`);
1125
+ }
1126
+ return value;
1127
+ }
1128
+ function tokenize(value) {
1129
+ return normalizeText(value).split(/\s+/).filter((token) => token.length >= 2).filter((token) => !STOPWORDS.has(token));
1130
+ }
1131
+ function normalizeText(value) {
1132
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
1133
+ }
1134
+ function jaccard(left, right) {
1135
+ if (left.length === 0 || right.length === 0) return 0;
1136
+ const leftSet = new Set(left);
1137
+ const rightSet = new Set(right);
1138
+ let intersection = 0;
1139
+ for (const token of leftSet) {
1140
+ if (rightSet.has(token)) intersection += 1;
1141
+ }
1142
+ const union = (/* @__PURE__ */ new Set([...leftSet, ...rightSet])).size;
1143
+ return union === 0 ? 0 : intersection / union;
1144
+ }
1145
+ function overlap(left, right) {
1146
+ const rightSet = new Set(right.filter(Boolean));
1147
+ let count = 0;
1148
+ for (const item of new Set(left.filter(Boolean))) {
1149
+ if (rightSet.has(item)) count += 1;
1150
+ }
1151
+ return count;
1152
+ }
1153
+ function recencyScore(updatedAt, referenceTimeMs) {
1154
+ const ageMs = referenceTimeMs - Date.parse(updatedAt);
1155
+ if (!Number.isFinite(ageMs) || ageMs < 0) return 0;
1156
+ const ageDays = ageMs / (24 * 60 * 60 * 1e3);
1157
+ if (ageDays > 365) return 0;
1158
+ return (365 - ageDays) / 365;
1159
+ }
1160
+ function round(value) {
1161
+ return Math.round(value * 1e3) / 1e3;
1162
+ }
1163
+
1164
+ // src/ledger.ts
1165
+ var BeliefLedger = class _BeliefLedger {
1166
+ store;
1167
+ llm;
1168
+ retrieval;
1169
+ now;
1170
+ constructor(options) {
1171
+ this.store = options.store;
1172
+ this.llm = options.llm;
1173
+ this.retrieval = options.retrieval ?? {};
1174
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
1175
+ }
1176
+ static fromStorage(storage, options) {
1177
+ return new _BeliefLedger({
1178
+ store: new RemnicLedgerStore(storage, options),
1179
+ llm: options.llm,
1180
+ retrieval: options.retrieval,
1181
+ now: options.now
1182
+ });
1183
+ }
1184
+ async capture(input) {
1185
+ const now = normalizeIsoTimestamp("now", input.now ?? this.now().toISOString());
1186
+ const draft = await this.llm.extractClaim({ ...input, now });
1187
+ const normalized = normalizeClaimDraft(draft, { now, sourceText: input.text });
1188
+ const claim = await this.store.createClaim(normalized);
1189
+ const examination = await this.crossExamine(claim, { now });
1190
+ return { ...examination, claim };
1191
+ }
1192
+ async crossExamine(claimOrId, options = {}) {
1193
+ const claim = typeof claimOrId === "string" ? await this.requireClaim(claimOrId) : claimOrId;
1194
+ const retrievalOptions = {
1195
+ ...this.retrieval,
1196
+ ...options,
1197
+ now: options.now ?? this.retrieval.now ?? this.now().toISOString()
1198
+ };
1199
+ const candidates = await retrievePriorClaims(claim, this.store, retrievalOptions);
1200
+ const judgments = [];
1201
+ for (const candidate of candidates) {
1202
+ const judgment = await this.llm.judgeClaimPair({
1203
+ current: claim,
1204
+ prior: candidate.claim
1205
+ });
1206
+ judgments.push({
1207
+ ...judgment,
1208
+ priorClaimId: candidate.claim.id
1209
+ });
1210
+ }
1211
+ const contradictions = judgments.map((judgment) => ({
1212
+ judgment,
1213
+ claim: candidates.find((candidate) => candidate.claim.id === judgment.priorClaimId)?.claim
1214
+ })).filter(
1215
+ (item) => item.claim !== void 0 && item.judgment.classification === "contradiction"
1216
+ );
1217
+ const challenge = contradictions.length > 0 ? await this.llm.draftSocraticChallenge({
1218
+ current: claim,
1219
+ contradictions
1220
+ }) : void 0;
1221
+ return {
1222
+ claim,
1223
+ candidates,
1224
+ judgments,
1225
+ ...challenge ? { challenge } : {},
1226
+ stats: buildCrossExaminationStats(judgments)
1227
+ };
1228
+ }
1229
+ async supersede(priorId, newId, reason) {
1230
+ return this.store.supersedeClaim(priorId, newId, reason);
1231
+ }
1232
+ async split(priorId, parts, reason = "split into narrower claims") {
1233
+ if (parts.length === 0) {
1234
+ throw new Error("split requires at least one part");
1235
+ }
1236
+ const prior = await this.requireClaim(priorId);
1237
+ const now = this.now().toISOString();
1238
+ const normalizedParts = parts.map((part) => ({
1239
+ ...normalizeClaimDraft(part, {
1240
+ now,
1241
+ sourceText: prior.sourceText ?? prior.statement
1242
+ }),
1243
+ parentIds: [priorId]
1244
+ }));
1245
+ const created = [];
1246
+ try {
1247
+ for (const part of normalizedParts) {
1248
+ created.push(await this.store.createClaim(part));
1249
+ }
1250
+ } catch (error) {
1251
+ await rollbackCreatedSplitClaims(this.store, created, now, `split creation failed for ${priorId}`);
1252
+ throw error;
1253
+ }
1254
+ const firstReplacement = created[0];
1255
+ let didSupersede = false;
1256
+ try {
1257
+ didSupersede = await this.store.supersedeClaim(priorId, firstReplacement.id, reason);
1258
+ } catch (error) {
1259
+ await rollbackCreatedSplitClaims(this.store, created, now, `split supersession failed for ${priorId}`);
1260
+ throw error;
1261
+ }
1262
+ if (!didSupersede) {
1263
+ await rollbackCreatedSplitClaims(this.store, created, now, `split supersession failed for ${priorId}`);
1264
+ throw new Error(`split could not supersede prior claim ${priorId} with ${firstReplacement.id}`);
1265
+ }
1266
+ const linkedFirst = await this.store.getClaim(firstReplacement.id);
1267
+ if (!linkedFirst) {
1268
+ throw new Error(`split replacement claim ${firstReplacement.id} disappeared after supersession`);
1269
+ }
1270
+ created[0] = linkedFirst;
1271
+ return created;
1272
+ }
1273
+ async resolve(claimId, input) {
1274
+ const claim = await this.requireClaim(claimId);
1275
+ const resolvedAt = normalizeIsoTimestamp("resolvedAt", input.resolvedAt ?? this.now().toISOString());
1276
+ const resolution = createResolution({
1277
+ verdict: input.verdict,
1278
+ actualConfidence: input.actualConfidence,
1279
+ resolvedAt,
1280
+ source: input.source,
1281
+ notes: input.notes,
1282
+ predictedConfidence: claim.confidence
1283
+ });
1284
+ return this.store.updateClaim(claimId, {
1285
+ status: "resolved",
1286
+ resolution,
1287
+ updatedAt: resolvedAt
1288
+ });
1289
+ }
1290
+ async snooze(claimId, until) {
1291
+ const snoozedUntil = normalizeIsoTimestamp("snoozedUntil", until);
1292
+ return this.store.updateClaim(claimId, {
1293
+ status: "snoozed",
1294
+ snoozedUntil,
1295
+ updatedAt: this.now().toISOString()
1296
+ });
1297
+ }
1298
+ async ignore(claimId, reason) {
1299
+ const ignoredAt = this.now().toISOString();
1300
+ return this.store.updateClaim(claimId, {
1301
+ status: "ignored",
1302
+ ignoredAt,
1303
+ ...reason?.trim() ? { ignoredReason: reason.trim() } : {},
1304
+ updatedAt: ignoredAt
1305
+ });
1306
+ }
1307
+ async scoreDuePredictions(options = {}) {
1308
+ const now = normalizeIsoTimestamp("now", options.now ?? this.now().toISOString());
1309
+ const limit = normalizeLimit2(options.limit);
1310
+ const verdictSources = options.verdictSources ?? {};
1311
+ const due = (await this.store.listClaims({ kinds: ["prediction"], statuses: ["active", "snoozed"] })).filter((claim) => isDueForScoring(claim, now)).sort(compareDuePredictions).slice(0, limit);
1312
+ const results = [];
1313
+ for (const claim of due) {
1314
+ const verdictSource = verdictSources[claim.id];
1315
+ if (!verdictSource?.trim()) {
1316
+ results.push({
1317
+ claim,
1318
+ status: "needs_user_verdict",
1319
+ prompt: `Prediction deadline passed: "${claim.statement}". What actually happened?`
1320
+ });
1321
+ continue;
1322
+ }
1323
+ if (!this.llm.gradePrediction) {
1324
+ results.push({
1325
+ claim,
1326
+ status: "needs_user_verdict",
1327
+ prompt: `Prediction deadline passed: "${claim.statement}". Provide a verdict because no grader is configured.`
1328
+ });
1329
+ continue;
1330
+ }
1331
+ try {
1332
+ const grade = await this.llm.gradePrediction({ claim, verdictSource, now });
1333
+ if (!grade) {
1334
+ results.push({
1335
+ claim,
1336
+ status: "needs_user_verdict",
1337
+ prompt: `Prediction deadline passed: "${claim.statement}". The verdict source was not enough to grade it.`
1338
+ });
1339
+ continue;
1340
+ }
1341
+ const resolution = createResolution({
1342
+ verdict: grade.verdict,
1343
+ actualConfidence: grade.actualConfidence,
1344
+ resolvedAt: now,
1345
+ source: grade.source ?? verdictSource,
1346
+ notes: grade.rationale,
1347
+ predictedConfidence: claim.confidence
1348
+ });
1349
+ const updated = await this.store.updateClaim(claim.id, {
1350
+ status: "resolved",
1351
+ resolution,
1352
+ updatedAt: now
1353
+ });
1354
+ results.push({
1355
+ claim: updated,
1356
+ status: "resolved",
1357
+ resolution
1358
+ });
1359
+ } catch (error) {
1360
+ results.push({
1361
+ claim,
1362
+ status: "skipped",
1363
+ reason: `prediction scoring failed: ${errorMessage(error)}`
1364
+ });
1365
+ }
1366
+ }
1367
+ return results;
1368
+ }
1369
+ async reflect(options = {}) {
1370
+ const claims = await this.store.listClaims();
1371
+ return buildReflectionReport(claims, {
1372
+ ...options,
1373
+ now: options.now ?? this.now().toISOString()
1374
+ });
1375
+ }
1376
+ async requireClaim(id) {
1377
+ const claim = await this.store.getClaim(id);
1378
+ if (!claim) {
1379
+ throw new Error(`claim ${id} not found`);
1380
+ }
1381
+ return claim;
1382
+ }
1383
+ };
1384
+ function buildCrossExaminationStats(judgments) {
1385
+ const judged = judgments.length;
1386
+ const contradictions = judgments.filter((judgment) => judgment.classification === "contradiction").length;
1387
+ const unrelated = judgments.filter((judgment) => judgment.classification === "unrelated").length;
1388
+ return {
1389
+ candidatesConsidered: judged,
1390
+ judged,
1391
+ contradictions,
1392
+ unrelated,
1393
+ observableFalsePositiveRate: judged === 0 ? 0 : unrelated / judged
1394
+ };
1395
+ }
1396
+ async function rollbackCreatedSplitClaims(store, claims, now, ignoredReason) {
1397
+ for (const claim of claims) {
1398
+ try {
1399
+ await store.updateClaim(claim.id, {
1400
+ status: "ignored",
1401
+ ignoredAt: now,
1402
+ ignoredReason,
1403
+ updatedAt: now
1404
+ });
1405
+ } catch {
1406
+ }
1407
+ }
1408
+ }
1409
+ function isDueForScoring(claim, nowIso) {
1410
+ if (!claim.deadline) return false;
1411
+ if (claim.resolution) return false;
1412
+ if (claim.status === "snoozed" && claim.snoozedUntil && Date.parse(claim.snoozedUntil) > Date.parse(nowIso)) {
1413
+ return false;
1414
+ }
1415
+ return Date.parse(claim.deadline) <= Date.parse(nowIso);
1416
+ }
1417
+ function compareDuePredictions(a, b) {
1418
+ const aDeadline = a.deadline ? Date.parse(a.deadline) : Number.POSITIVE_INFINITY;
1419
+ const bDeadline = b.deadline ? Date.parse(b.deadline) : Number.POSITIVE_INFINITY;
1420
+ const deadlineOrder = aDeadline - bDeadline;
1421
+ if (deadlineOrder !== 0) return deadlineOrder;
1422
+ const updatedOrder = Date.parse(a.updatedAt) - Date.parse(b.updatedAt);
1423
+ if (updatedOrder !== 0) return updatedOrder;
1424
+ return a.id.localeCompare(b.id);
1425
+ }
1426
+ function normalizeLimit2(value) {
1427
+ if (value === void 0) return 100;
1428
+ if (!Number.isInteger(value) || value < 1 || value > 1e3) {
1429
+ throw new Error(`limit must be an integer in [1, 1000], got ${String(value)}`);
1430
+ }
1431
+ return value;
1432
+ }
1433
+ function errorMessage(error) {
1434
+ return error instanceof Error ? error.message : String(error);
1435
+ }
1436
+
1437
+ // src/llm.ts
1438
+ function createFallbackLlmLedgerAdapter(client, options = {}) {
1439
+ const baseOptions = { ...options };
1440
+ return {
1441
+ async extractClaim(request) {
1442
+ const parsed = await client.parseWithSchema(
1443
+ [
1444
+ { role: "system", content: EXTRACT_PROMPT },
1445
+ {
1446
+ role: "user",
1447
+ content: [
1448
+ `Current time: ${request.now}`,
1449
+ request.sessionKey ? `Session: ${request.sessionKey}` : "",
1450
+ "User text:",
1451
+ request.text
1452
+ ].filter(Boolean).join("\n")
1453
+ }
1454
+ ],
1455
+ {
1456
+ parse: (data) => normalizeExtractedDraft(data, request.now, request.text)
1457
+ },
1458
+ { ...baseOptions, temperature: baseOptions.temperature ?? 0.1, maxTokens: baseOptions.maxTokens ?? 1200 }
1459
+ );
1460
+ if (!parsed) {
1461
+ throw new Error("belief-ledger extraction LLM did not return a valid claim");
1462
+ }
1463
+ return parsed;
1464
+ },
1465
+ async judgeClaimPair(request) {
1466
+ const parsed = await client.parseWithSchema(
1467
+ [
1468
+ { role: "system", content: JUDGE_PROMPT },
1469
+ { role: "user", content: formatJudgeInput(request.current, request.prior) }
1470
+ ],
1471
+ {
1472
+ parse: (data) => normalizeJudgeResult(data, request.prior.id)
1473
+ },
1474
+ { ...baseOptions, temperature: baseOptions.temperature ?? 0.1, maxTokens: baseOptions.maxTokens ?? 800 }
1475
+ );
1476
+ if (!parsed) {
1477
+ throw new Error("belief-ledger judge LLM did not return a valid verdict");
1478
+ }
1479
+ return parsed;
1480
+ },
1481
+ async draftSocraticChallenge(request) {
1482
+ const priorClaimIds = request.contradictions.map((item) => item.claim.id);
1483
+ const parsed = await client.parseWithSchema(
1484
+ [
1485
+ { role: "system", content: CHALLENGE_PROMPT },
1486
+ {
1487
+ role: "user",
1488
+ content: [
1489
+ "Current claim:",
1490
+ formatClaim(request.current),
1491
+ "",
1492
+ "Contradicting prior claims:",
1493
+ ...request.contradictions.map(
1494
+ (item) => `${formatClaim(item.claim)}
1495
+ Judge rationale: ${item.judgment.rationale}`
1496
+ )
1497
+ ].join("\n")
1498
+ }
1499
+ ],
1500
+ {
1501
+ parse: (data) => normalizeChallenge(data, priorClaimIds)
1502
+ },
1503
+ { ...baseOptions, temperature: baseOptions.temperature ?? 0.2, maxTokens: baseOptions.maxTokens ?? 800 }
1504
+ );
1505
+ if (!parsed) {
1506
+ throw new Error("belief-ledger challenge LLM did not return a valid prompt");
1507
+ }
1508
+ return parsed;
1509
+ },
1510
+ async gradePrediction(request) {
1511
+ if (!request.verdictSource?.trim()) return null;
1512
+ const parsed = await client.parseWithSchema(
1513
+ [
1514
+ { role: "system", content: GRADE_PROMPT },
1515
+ {
1516
+ role: "user",
1517
+ content: [
1518
+ `Current time: ${request.now}`,
1519
+ "Prediction:",
1520
+ formatClaim(request.claim),
1521
+ "",
1522
+ "Verdict source:",
1523
+ request.verdictSource
1524
+ ].join("\n")
1525
+ }
1526
+ ],
1527
+ {
1528
+ parse: (data) => normalizePredictionGrade(data)
1529
+ },
1530
+ { ...baseOptions, temperature: baseOptions.temperature ?? 0.1, maxTokens: baseOptions.maxTokens ?? 800 }
1531
+ );
1532
+ return parsed;
1533
+ }
1534
+ };
1535
+ }
1536
+ var EXTRACT_PROMPT = `Extract one belief-ledger claim from the user's text.
1537
+
1538
+ Return JSON only:
1539
+ {
1540
+ "statement": "single clear claim or prediction",
1541
+ "kind": "claim" | "prediction" | "opinion",
1542
+ "stance": "for" | "against" | "uncertain" | "neutral",
1543
+ "confidence": 0.0,
1544
+ "scope": {
1545
+ "entities": ["entity names"],
1546
+ "domain": "optional topic",
1547
+ "timeWindow": { "start": "optional ISO", "end": "optional ISO" }
1548
+ },
1549
+ "deadline": "optional ISO timestamp for predictions",
1550
+ "evidenceLinks": ["optional source URLs or identifiers"]
1551
+ }
1552
+
1553
+ Use the user's stated confidence when present. If no confidence is stated, estimate conservatively.`;
1554
+ var JUDGE_PROMPT = `Classify how the current claim relates to the prior claim.
1555
+
1556
+ Return JSON only:
1557
+ {
1558
+ "classification": "contradiction" | "evolution" | "refinement" | "unrelated",
1559
+ "confidence": 0.0,
1560
+ "rationale": "brief reason"
1561
+ }
1562
+
1563
+ Use "contradiction" only when both claims cannot comfortably be true at the same time under their stated scope.`;
1564
+ var CHALLENGE_PROMPT = `Draft one concise Socratic challenge for the user.
1565
+
1566
+ Return JSON only:
1567
+ {
1568
+ "question": "one question asking the user to reconcile the conflict",
1569
+ "priorClaimIds": ["ids involved"],
1570
+ "suggestedActions": ["supersede", "split", "resolve", "ignore"]
1571
+ }
1572
+
1573
+ Do not agree with the user by default. Ask for a reconciliation decision.`;
1574
+ var GRADE_PROMPT = `Grade whether the prediction came true from the supplied verdict source.
1575
+
1576
+ Return JSON only:
1577
+ {
1578
+ "verdict": "true" | "false" | "mixed" | "unknown",
1579
+ "actualConfidence": 0.0,
1580
+ "rationale": "brief reason",
1581
+ "source": "short source label"
1582
+ }
1583
+
1584
+ Use actualConfidence 1 for true, 0 for false, a fractional value for mixed, and 0.5 for unknown.`;
1585
+ function normalizeExtractedDraft(data, now, sourceText) {
1586
+ if (!isRecord2(data)) {
1587
+ throw new Error("claim extraction result must be an object");
1588
+ }
1589
+ const scope = isRecord2(data.scope) ? data.scope : {};
1590
+ const rawTimeWindow = isRecord2(scope.timeWindow) ? scope.timeWindow : isRecord2(scope.time_window) ? scope.time_window : void 0;
1591
+ const draft = {
1592
+ statement: asString(data.statement),
1593
+ kind: optionalString(data.kind),
1594
+ stance: asString(data.stance),
1595
+ confidence: asNumber(data.confidence),
1596
+ scope: {
1597
+ entities: Array.isArray(scope.entities) ? scope.entities.filter((value) => typeof value === "string") : [],
1598
+ ...typeof scope.domain === "string" ? { domain: scope.domain } : {},
1599
+ ...rawTimeWindow ? {
1600
+ timeWindow: {
1601
+ ...typeof rawTimeWindow.start === "string" ? { start: rawTimeWindow.start } : {},
1602
+ ...typeof rawTimeWindow.end === "string" ? { end: rawTimeWindow.end } : {}
1603
+ }
1604
+ } : {}
1605
+ },
1606
+ ...typeof data.deadline === "string" && data.deadline.trim() ? { deadline: data.deadline } : {},
1607
+ evidenceLinks: Array.isArray(data.evidenceLinks) ? data.evidenceLinks.filter((value) => typeof value === "string") : Array.isArray(data.evidence_links) ? data.evidence_links.filter((value) => typeof value === "string") : []
1608
+ };
1609
+ const normalized = normalizeClaimDraft(draft, { now, sourceText });
1610
+ return {
1611
+ statement: normalized.statement,
1612
+ kind: normalized.kind,
1613
+ stance: normalized.stance,
1614
+ confidence: normalized.confidence,
1615
+ scope: normalized.scope,
1616
+ ...normalized.deadline ? { deadline: normalized.deadline } : {},
1617
+ evidenceLinks: normalized.evidenceLinks
1618
+ };
1619
+ }
1620
+ function formatJudgeInput(current, prior) {
1621
+ return ["Current claim:", formatClaim(current), "", "Prior claim:", formatClaim(prior)].join("\n");
1622
+ }
1623
+ function formatClaim(claim) {
1624
+ return [
1625
+ `id: ${claim.id}`,
1626
+ `created: ${claim.createdAt}`,
1627
+ `statement: ${claim.statement}`,
1628
+ `kind: ${claim.kind}`,
1629
+ `stance: ${claim.stance}`,
1630
+ `confidence: ${claim.confidence}`,
1631
+ `domain: ${claim.scope.domain ?? ""}`,
1632
+ `entities: ${claim.scope.entities.join(", ")}`,
1633
+ `timeWindow.start: ${claim.scope.timeWindow?.start ?? ""}`,
1634
+ `timeWindow.end: ${claim.scope.timeWindow?.end ?? ""}`,
1635
+ `deadline: ${claim.deadline ?? ""}`,
1636
+ `status: ${claim.status}`
1637
+ ].join("\n");
1638
+ }
1639
+ function asString(value) {
1640
+ if (typeof value !== "string") {
1641
+ throw new Error("expected string");
1642
+ }
1643
+ return value;
1644
+ }
1645
+ function optionalString(value) {
1646
+ return typeof value === "string" && value.trim() ? value : void 0;
1647
+ }
1648
+ function asNumber(value) {
1649
+ if (typeof value === "number") return value;
1650
+ if (typeof value === "string" && value.trim()) return Number(value);
1651
+ throw new Error("expected number");
1652
+ }
1653
+ function isRecord2(value) {
1654
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1655
+ }
1656
+ export {
1657
+ BeliefLedger,
1658
+ LEDGER_SCHEMA_VERSION,
1659
+ LEDGER_TAG,
1660
+ RemnicLedgerStore,
1661
+ buildReflectionReport,
1662
+ claimFromMemory,
1663
+ claimTags,
1664
+ claimToStructuredAttributes,
1665
+ computeBrierScore,
1666
+ createFallbackLlmLedgerAdapter,
1667
+ createResolution,
1668
+ memoryFrontmatterPatchForClaim,
1669
+ normalizeChallenge,
1670
+ normalizeClaimDraft,
1671
+ normalizeIsoTimestamp,
1672
+ normalizeJudgeResult,
1673
+ normalizePredictionGrade,
1674
+ remnicMemoryStatusForClaim,
1675
+ retrievePriorClaims,
1676
+ roundMetric,
1677
+ serializeClaimBody
1678
+ };
1679
+ //# sourceMappingURL=index.js.map