@gamaze/hicortex 0.21.0 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/dashboard.html +282 -27
- package/dist/calibration.d.ts +77 -12
- package/dist/calibration.js +82 -15
- package/dist/capture-health.d.ts +42 -5
- package/dist/capture-health.js +57 -7
- package/dist/capture-pause.d.ts +7 -4
- package/dist/capture-pause.js +7 -4
- package/dist/consolidate.d.ts +19 -0
- package/dist/consolidate.js +125 -51
- package/dist/dashboard.d.ts +41 -16
- package/dist/dashboard.js +110 -41
- package/dist/db.js +16 -0
- package/dist/dedup.js +4 -1
- package/dist/eval/decay-eval.d.ts +6 -5
- package/dist/eval/decay-eval.js +10 -48
- package/dist/eval/eval-clock.d.ts +32 -0
- package/dist/eval/eval-clock.js +47 -0
- package/dist/eval/graph-eval.d.ts +15 -2
- package/dist/eval/graph-eval.js +51 -5
- package/dist/eval/planted-eval.d.ts +4 -0
- package/dist/eval/planted-eval.js +27 -2
- package/dist/eval/planted-harness.d.ts +7 -0
- package/dist/eval/planted-harness.js +2 -0
- package/dist/eval/ranking-battery.d.ts +49 -2
- package/dist/eval/ranking-battery.js +110 -2
- package/dist/eval/ranking-eval.d.ts +26 -6
- package/dist/eval/ranking-eval.js +197 -34
- package/dist/eval/ranking-fixtures.d.ts +46 -6
- package/dist/eval/ranking-fixtures.js +268 -9
- package/dist/eval/recall-sweep.d.ts +7 -2
- package/dist/eval/recall-sweep.js +42 -13
- package/dist/eval/relevance-eval.d.ts +115 -1
- package/dist/eval/relevance-eval.js +318 -32
- package/dist/eval/run-eval.d.ts +7 -4
- package/dist/eval/run-eval.js +37 -10
- package/dist/graph.d.ts +1 -2
- package/dist/graph.js +3 -4
- package/dist/mcp-server.js +3 -2
- package/dist/nofit.d.ts +2 -1
- package/dist/nofit.js +2 -1
- package/dist/recall-index.d.ts +3 -2
- package/dist/recall-index.js +3 -2
- package/dist/retrieval.d.ts +43 -18
- package/dist/retrieval.js +148 -86
- package/dist/status.js +18 -0
- package/dist/storage.d.ts +2 -1
- package/dist/storage.js +6 -1
- package/dist/types.d.ts +25 -4
- package/package.json +1 -1
- package/server.json +4 -4
package/dist/dashboard.d.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import type express from "express";
|
|
24
24
|
import type Database from "better-sqlite3";
|
|
25
|
+
import { type CaptureHealthRow } from "./capture-health.js";
|
|
25
26
|
import { type Stage } from "./stages.js";
|
|
26
27
|
/** Corpus-shape snapshot. `adoption` is null in backfilled rows (point-in-time,
|
|
27
28
|
* can't be reconstructed from created_at). */
|
|
@@ -202,7 +203,7 @@ export interface DashboardData {
|
|
|
202
203
|
period_start: string | null;
|
|
203
204
|
};
|
|
204
205
|
};
|
|
205
|
-
range: "7d" | "30d" | "90d" | "all";
|
|
206
|
+
range: "7d" | "30d" | "90d" | "180d" | "all";
|
|
206
207
|
series: DashboardSnapshot[];
|
|
207
208
|
composition: {
|
|
208
209
|
by_type: Record<string, number>;
|
|
@@ -213,22 +214,28 @@ export interface DashboardData {
|
|
|
213
214
|
};
|
|
214
215
|
/**
|
|
215
216
|
* #422 Phase 2 — capture health: per machine × agent /distill outcome
|
|
216
|
-
* accounting
|
|
217
|
-
* held / retried). ALWAYS present; {day: null, rows: []} when nothing is
|
|
217
|
+
* accounting. ALWAYS present; {day: null, rows: []} when nothing is
|
|
218
218
|
* recorded (fresh install / all rows pruned — the page degrades to the
|
|
219
219
|
* phase-1 counts-only rows).
|
|
220
|
+
*
|
|
221
|
+
* #409 fix round 7 (owner ruling 2026-09-14: "the normal 30 day as the
|
|
222
|
+
* other cards"): `day`/`rows` keep the NEWEST night with rows (secondary
|
|
223
|
+
* info — the card's "tonight" suffix), while `window_days`/`window_rows`
|
|
224
|
+
* carry the ROLLING capture window's per machine × agent sums — the card's
|
|
225
|
+
* face. Both derive from the same distill_activity rows and the same
|
|
226
|
+
* CAPTURE_HEALTH_WINDOW_DAYS constant (retention == window). window_rows
|
|
227
|
+
* is [] exactly when rows is (same table) — the counts fallback then still
|
|
228
|
+
* applies. A pre-round-7 server sends neither window key; the page guards.
|
|
220
229
|
*/
|
|
221
230
|
capture_health: {
|
|
222
231
|
day: string | null;
|
|
223
|
-
rows:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
retried: number;
|
|
231
|
-
}>;
|
|
232
|
+
rows: CaptureHealthRow[];
|
|
233
|
+
/** Echoed window length (the page renders "last N days" from it — never
|
|
234
|
+
* hardcoded client-side). */
|
|
235
|
+
window_days: number;
|
|
236
|
+
/** Per machine × agent sums over the rolling window (posts / sessions /
|
|
237
|
+
* bytes / held / retried), bytes DESC. */
|
|
238
|
+
window_rows: CaptureHealthRow[];
|
|
232
239
|
};
|
|
233
240
|
/**
|
|
234
241
|
* #423 phase 3 — fleet presence: the operator's capture pauses + per-bundle
|
|
@@ -565,7 +572,10 @@ export declare function handleDashboardField(db: Database.Database): {
|
|
|
565
572
|
export declare function dashboardFieldHandler(getDb: () => Database.Database): express.RequestHandler;
|
|
566
573
|
/** One night of the replay ledger. `by_agent` keys are source_agent strings
|
|
567
574
|
* ("(unknown)" when the memory row is gone); learned counts added, enriched
|
|
568
|
-
* counts distinct enriched memories, merged counts dedup losers
|
|
575
|
+
* counts distinct enriched memories, merged counts dedup losers, and the
|
|
576
|
+
* #452 truth-management keys count superseded/retracted sources and
|
|
577
|
+
* reconsolidation rewrites (agent = the OLD memory's source — the event
|
|
578
|
+
* belongs to the memory that was demoted). */
|
|
569
579
|
export interface DashboardEventsNight {
|
|
570
580
|
date: string;
|
|
571
581
|
added: string[];
|
|
@@ -578,10 +588,25 @@ export interface DashboardEventsNight {
|
|
|
578
588
|
a: string;
|
|
579
589
|
b: string;
|
|
580
590
|
}>;
|
|
591
|
+
/** #452 — superseded_by links born that night ({old, by} ids). */
|
|
592
|
+
superseded: Array<{
|
|
593
|
+
old: string;
|
|
594
|
+
by: string;
|
|
595
|
+
}>;
|
|
596
|
+
/** #452 — corrected_by links whose source is currently status='retracted'. */
|
|
597
|
+
retracted: Array<{
|
|
598
|
+
old: string;
|
|
599
|
+
by: string;
|
|
600
|
+
}>;
|
|
601
|
+
/** #452 — distinct memories reconsolidation rewrote that night. */
|
|
602
|
+
rewritten: string[];
|
|
581
603
|
by_agent: Record<string, {
|
|
582
604
|
learned: number;
|
|
583
605
|
enriched: number;
|
|
584
606
|
merged: number;
|
|
607
|
+
superseded: number;
|
|
608
|
+
retracted: number;
|
|
609
|
+
rewritten: number;
|
|
585
610
|
}>;
|
|
586
611
|
}
|
|
587
612
|
/** The /dashboard/events response. */
|
|
@@ -589,9 +614,9 @@ export interface DashboardEvents {
|
|
|
589
614
|
nights: DashboardEventsNight[];
|
|
590
615
|
}
|
|
591
616
|
/**
|
|
592
|
-
* The pure data handler for GET /dashboard/events?days=N. Buckets the
|
|
593
|
-
*
|
|
594
|
-
*
|
|
617
|
+
* The pure data handler for GET /dashboard/events?days=N. Buckets the event
|
|
618
|
+
* sources into UTC nights (only nights with events, ascending) within the
|
|
619
|
+
* last `days` calendar days (today inclusive).
|
|
595
620
|
*/
|
|
596
621
|
export declare function handleDashboardEvents(db: Database.Database, query: {
|
|
597
622
|
days?: unknown;
|
package/dist/dashboard.js
CHANGED
|
@@ -113,38 +113,17 @@ function computeDashboardMetrics(db) {
|
|
|
113
113
|
},
|
|
114
114
|
};
|
|
115
115
|
}
|
|
116
|
-
/**
|
|
117
|
-
* One grouped pass over memory_links for per-memory link counts (batch —
|
|
118
|
-
* never per-row queries); shared by the field payload and stage_counts.
|
|
119
|
-
* Counts BOTH directions (a memory's connectivity is symmetric).
|
|
120
|
-
*/
|
|
121
|
-
function linkCountsByMemory(db) {
|
|
122
|
-
const rows = db
|
|
123
|
-
.prepare(`SELECT id, SUM(c) AS c FROM (
|
|
124
|
-
SELECT source_id AS id, COUNT(*) AS c FROM memory_links GROUP BY source_id
|
|
125
|
-
UNION ALL
|
|
126
|
-
SELECT target_id AS id, COUNT(*) AS c FROM memory_links GROUP BY target_id
|
|
127
|
-
) GROUP BY id`)
|
|
128
|
-
.all();
|
|
129
|
-
const linkCounts = new Map();
|
|
130
|
-
for (const r of rows)
|
|
131
|
-
linkCounts.set(r.id, r.c);
|
|
132
|
-
return linkCounts;
|
|
133
|
-
}
|
|
134
116
|
/**
|
|
135
117
|
* Derive one memory's {strength, stage} — the exact math of the field
|
|
136
|
-
* payload: effectiveStrength (importance = base
|
|
137
|
-
*
|
|
118
|
+
* payload: effectiveStrength (importance = base) rounded to the 1e-6 wire
|
|
119
|
+
* format, then the recency gate (days since
|
|
138
120
|
* last_accessed, falling back to created_at; unparseable → null, the gate is
|
|
139
121
|
* skipped rather than guessing "very old") and the calibrated strength bands.
|
|
140
122
|
*/
|
|
141
|
-
function deriveStageForRow(row,
|
|
123
|
+
function deriveStageForRow(row, now, nowDay) {
|
|
142
124
|
const base = row.base_strength ?? 0.5;
|
|
143
|
-
const accessCount = row.access_count ?? 0;
|
|
144
125
|
const effStr = (0, retrieval_js_1.effectiveStrength)(base, row.last_accessed, now, {
|
|
145
126
|
importance: base,
|
|
146
|
-
accessCount,
|
|
147
|
-
linkCount,
|
|
148
127
|
});
|
|
149
128
|
const strength = Math.round(effStr * 1e6) / 1e6;
|
|
150
129
|
const refDay = utcDayNumber(row.last_accessed) ?? utcDayNumber(row.created_at);
|
|
@@ -160,15 +139,14 @@ function deriveStageForRow(row, linkCount, now, nowDay) {
|
|
|
160
139
|
function computeStageCounts(db) {
|
|
161
140
|
const now = new Date();
|
|
162
141
|
const nowDay = Math.floor(now.getTime() / 86_400_000);
|
|
163
|
-
const linkCounts = linkCountsByMemory(db);
|
|
164
142
|
const rows = db
|
|
165
|
-
.prepare(`SELECT id, base_strength, last_accessed,
|
|
143
|
+
.prepare(`SELECT id, base_strength, last_accessed, created_at
|
|
166
144
|
FROM memories
|
|
167
145
|
WHERE COALESCE(status, '') != 'absorbed'`)
|
|
168
146
|
.all();
|
|
169
147
|
const counts = { forming: 0, belief: 0, truth: 0, fading: 0 };
|
|
170
148
|
for (const r of rows) {
|
|
171
|
-
counts[deriveStageForRow(r,
|
|
149
|
+
counts[deriveStageForRow(r, now, nowDay).stage]++;
|
|
172
150
|
}
|
|
173
151
|
return counts;
|
|
174
152
|
}
|
|
@@ -397,7 +375,7 @@ function backfillSnapshots(db) {
|
|
|
397
375
|
// LIVE composition (so day-one, before any snapshot is written, still shows
|
|
398
376
|
// the current corpus shape), and builds the digest for the selected day.
|
|
399
377
|
// ---------------------------------------------------------------------------
|
|
400
|
-
const VALID_RANGES = new Set(["7d", "30d", "90d", "all"]);
|
|
378
|
+
const VALID_RANGES = new Set(["7d", "30d", "90d", "180d", "all"]);
|
|
401
379
|
const DEFAULT_RANGE = "30d";
|
|
402
380
|
const DEFAULT_DIGEST_LIMIT = 10;
|
|
403
381
|
function rangeToCutoff(range) {
|
|
@@ -597,7 +575,7 @@ function handleDashboardData(db, query, config) {
|
|
|
597
575
|
by_source_agent: live.by_source_agent,
|
|
598
576
|
by_source_machine: live.by_source_machine,
|
|
599
577
|
},
|
|
600
|
-
capture_health: (0, capture_health_js_1.readCaptureHealth)(db),
|
|
578
|
+
capture_health: { ...(0, capture_health_js_1.readCaptureHealth)(db), ...(0, capture_health_js_1.readCaptureHealthWindow)(db) },
|
|
601
579
|
// #423 phase 3: pauses + presence in one block — one source of truth
|
|
602
580
|
// for the rail's dots, toggles and the capture card's PAUSED badges.
|
|
603
581
|
fleet: {
|
|
@@ -690,8 +668,6 @@ function utcDayNumber(iso) {
|
|
|
690
668
|
function handleDashboardField(db) {
|
|
691
669
|
const now = new Date();
|
|
692
670
|
const nowDay = Math.floor(now.getTime() / 86_400_000);
|
|
693
|
-
// Shared derivation inputs (one batched pass — never per-row queries).
|
|
694
|
-
const linkCounts = linkCountsByMemory(db);
|
|
695
671
|
const rows = db
|
|
696
672
|
.prepare(`SELECT id, content, base_strength, last_accessed, access_count,
|
|
697
673
|
created_at, domain, source_agent, source_machine
|
|
@@ -702,7 +678,7 @@ function handleDashboardField(db) {
|
|
|
702
678
|
const memories = rows.map((r) => {
|
|
703
679
|
// Shared per-row derivation — the SAME math the snapshot stage_counts
|
|
704
680
|
// uses (deriveStageForRow); the field only adds its wire fields.
|
|
705
|
-
const { strength, stage } = deriveStageForRow(r,
|
|
681
|
+
const { strength, stage } = deriveStageForRow(r, now, nowDay);
|
|
706
682
|
return {
|
|
707
683
|
id: r.id,
|
|
708
684
|
title: (0, recall_index_js_1.memoryTitle)(r.content, calibration_js_1.RECALL_TITLE_CHARS),
|
|
@@ -787,9 +763,12 @@ function dashboardFieldHandler(getDb) {
|
|
|
787
763
|
}
|
|
788
764
|
};
|
|
789
765
|
}
|
|
790
|
-
|
|
766
|
+
// #452 (owner directive 2026-09-16): the default window is 30 days for ALL
|
|
767
|
+
// views — longer ranges are opt-in via the page's selector. MAX stays 365:
|
|
768
|
+
// it is the generic API cap and an explicit ?days= is honored up to it.
|
|
769
|
+
const EVENTS_DEFAULT_DAYS = 30;
|
|
791
770
|
const EVENTS_MAX_DAYS = 365;
|
|
792
|
-
/** Parse the ?days= param: integer 1..365, default
|
|
771
|
+
/** Parse the ?days= param: integer 1..365, default 30. Returns null when the
|
|
793
772
|
* value is present but invalid (→ 400); undefined when absent (→ default). */
|
|
794
773
|
function parseEventsDays(raw) {
|
|
795
774
|
if (raw === undefined)
|
|
@@ -801,9 +780,9 @@ function parseEventsDays(raw) {
|
|
|
801
780
|
return n;
|
|
802
781
|
}
|
|
803
782
|
/**
|
|
804
|
-
* The pure data handler for GET /dashboard/events?days=N. Buckets the
|
|
805
|
-
*
|
|
806
|
-
*
|
|
783
|
+
* The pure data handler for GET /dashboard/events?days=N. Buckets the event
|
|
784
|
+
* sources into UTC nights (only nights with events, ascending) within the
|
|
785
|
+
* last `days` calendar days (today inclusive).
|
|
807
786
|
*/
|
|
808
787
|
function handleDashboardEvents(db, query) {
|
|
809
788
|
const parsed = parseEventsDays(query.days);
|
|
@@ -828,14 +807,31 @@ function handleDashboardEvents(db, query) {
|
|
|
828
807
|
const nightFor = (date) => {
|
|
829
808
|
let n = nights.get(date);
|
|
830
809
|
if (!n) {
|
|
831
|
-
n = {
|
|
810
|
+
n = {
|
|
811
|
+
date,
|
|
812
|
+
added: [],
|
|
813
|
+
enriched: [],
|
|
814
|
+
merged: [],
|
|
815
|
+
linked: [],
|
|
816
|
+
superseded: [],
|
|
817
|
+
retracted: [],
|
|
818
|
+
rewritten: [],
|
|
819
|
+
by_agent: {},
|
|
820
|
+
};
|
|
832
821
|
nights.set(date, n);
|
|
833
822
|
}
|
|
834
823
|
return n;
|
|
835
824
|
};
|
|
836
825
|
const bumpAgent = (n, agent, key) => {
|
|
837
826
|
const k = agent ?? "(unknown)";
|
|
838
|
-
const rec = n.by_agent[k] ?? {
|
|
827
|
+
const rec = n.by_agent[k] ?? {
|
|
828
|
+
learned: 0,
|
|
829
|
+
enriched: 0,
|
|
830
|
+
merged: 0,
|
|
831
|
+
superseded: 0,
|
|
832
|
+
retracted: 0,
|
|
833
|
+
rewritten: 0,
|
|
834
|
+
};
|
|
839
835
|
rec[key] += 1;
|
|
840
836
|
n.by_agent[k] = rec;
|
|
841
837
|
};
|
|
@@ -855,10 +851,12 @@ function handleDashboardEvents(db, query) {
|
|
|
855
851
|
// enriched — memory_history rows, DISTINCT memory per night (one rewrite of
|
|
856
852
|
// the same memory on a night is one enrichment). LEFT JOIN: the memory row
|
|
857
853
|
// can be gone (hard-deleted legacy losers); those count under "(unknown)".
|
|
854
|
+
// #452: reconsolidation rewrites are EXCLUDED — they render as `rewritten`
|
|
855
|
+
// below, so one rewrite is one caption, never "Enriched"+"Rewritten".
|
|
858
856
|
const enrichRows = db
|
|
859
857
|
.prepare(`SELECT h.memory_id, h.created_at, m.source_agent AS agent
|
|
860
858
|
FROM memory_history h LEFT JOIN memories m ON m.id = h.memory_id
|
|
861
|
-
WHERE h.created_at >=
|
|
859
|
+
WHERE h.created_at >= ? AND h.cause <> 'reconsolidation'`)
|
|
862
860
|
.all(cutoffIso);
|
|
863
861
|
const enrichedSeen = new Set(); // `${date}|${memory_id}` dedup
|
|
864
862
|
for (const r of enrichRows) {
|
|
@@ -898,8 +896,79 @@ function handleDashboardEvents(db, query) {
|
|
|
898
896
|
continue;
|
|
899
897
|
nightFor(date).linked.push({ a: r.source_id, b: r.target_id });
|
|
900
898
|
}
|
|
899
|
+
// superseded (#452) — superseded_by links born in the window; agent = the
|
|
900
|
+
// OLD memory's source (the event belongs to the demoted claim). The link
|
|
901
|
+
// ALSO stays in `linked` above — supersession is still a connection, the
|
|
902
|
+
// ring is unchanged.
|
|
903
|
+
const supersededRows = db
|
|
904
|
+
.prepare(`SELECT l.source_id, l.target_id, l.created_at, m.source_agent AS agent
|
|
905
|
+
FROM memory_links l LEFT JOIN memories m ON m.id = l.source_id
|
|
906
|
+
WHERE l.relationship = 'superseded_by' AND l.created_at >= ?`)
|
|
907
|
+
.all(cutoffIso);
|
|
908
|
+
for (const r of supersededRows) {
|
|
909
|
+
const date = inWindow(r.created_at);
|
|
910
|
+
if (date === null)
|
|
911
|
+
continue;
|
|
912
|
+
const n = nightFor(date);
|
|
913
|
+
n.superseded.push({ old: r.source_id, by: r.target_id });
|
|
914
|
+
bumpAgent(n, r.agent, "superseded");
|
|
915
|
+
}
|
|
916
|
+
// retracted (#452) — corrected_by links whose SOURCE's CURRENT status is
|
|
917
|
+
// 'retracted'. INNER JOIN so the status filter applies: rewrite-path
|
|
918
|
+
// corrected_by links leave status 'corrected' (or NULL on legacy rows) and
|
|
919
|
+
// are NOT retractions. Marks write no memory_history row (grounding: the
|
|
920
|
+
// only two INSERT sites are the reconsolidation rewrite + rollback), so
|
|
921
|
+
// the link + current status IS the retraction record. Dedup per
|
|
922
|
+
// (date, source_id) — the enriched pattern — in case a pair's link row was
|
|
923
|
+
// re-created on the same night.
|
|
924
|
+
const retractedRows = db
|
|
925
|
+
.prepare(`SELECT l.source_id, l.target_id, l.created_at, m.source_agent AS agent
|
|
926
|
+
FROM memory_links l JOIN memories m ON m.id = l.source_id
|
|
927
|
+
WHERE l.relationship = 'corrected_by' AND m.status = 'retracted' AND l.created_at >= ?`)
|
|
928
|
+
.all(cutoffIso);
|
|
929
|
+
const retractedSeen = new Set(); // `${date}|${source_id}` dedup
|
|
930
|
+
for (const r of retractedRows) {
|
|
931
|
+
const date = inWindow(r.created_at);
|
|
932
|
+
if (date === null)
|
|
933
|
+
continue;
|
|
934
|
+
const key = `${date}|${r.source_id}`;
|
|
935
|
+
if (retractedSeen.has(key))
|
|
936
|
+
continue;
|
|
937
|
+
retractedSeen.add(key);
|
|
938
|
+
const n = nightFor(date);
|
|
939
|
+
n.retracted.push({ old: r.source_id, by: r.target_id });
|
|
940
|
+
bumpAgent(n, r.agent, "retracted");
|
|
941
|
+
}
|
|
942
|
+
// rewritten (#452) — memory_history rows from the reconsolidation rewrite
|
|
943
|
+
// (cause='reconsolidation'; rollback rows keep their own cause and stay
|
|
944
|
+
// enriched-shaped). DISTINCT memory per night, the enriched pattern.
|
|
945
|
+
const rewrittenRows = db
|
|
946
|
+
.prepare(`SELECT h.memory_id, h.created_at, m.source_agent AS agent
|
|
947
|
+
FROM memory_history h LEFT JOIN memories m ON m.id = h.memory_id
|
|
948
|
+
WHERE h.cause = 'reconsolidation' AND h.created_at >= ?`)
|
|
949
|
+
.all(cutoffIso);
|
|
950
|
+
const rewrittenSeen = new Set(); // `${date}|${memory_id}` dedup
|
|
951
|
+
for (const r of rewrittenRows) {
|
|
952
|
+
const date = inWindow(r.created_at);
|
|
953
|
+
if (date === null)
|
|
954
|
+
continue;
|
|
955
|
+
const key = `${date}|${r.memory_id}`;
|
|
956
|
+
if (rewrittenSeen.has(key))
|
|
957
|
+
continue;
|
|
958
|
+
rewrittenSeen.add(key);
|
|
959
|
+
const n = nightFor(date);
|
|
960
|
+
n.rewritten.push(r.memory_id);
|
|
961
|
+
bumpAgent(n, r.agent, "rewritten");
|
|
962
|
+
}
|
|
901
963
|
const sorted = Array.from(nights.values())
|
|
902
|
-
.filter((n) => n.added.length +
|
|
964
|
+
.filter((n) => n.added.length +
|
|
965
|
+
n.enriched.length +
|
|
966
|
+
n.merged.length +
|
|
967
|
+
n.linked.length +
|
|
968
|
+
n.superseded.length +
|
|
969
|
+
n.retracted.length +
|
|
970
|
+
n.rewritten.length >
|
|
971
|
+
0)
|
|
903
972
|
.sort((x, y) => (x.date < y.date ? -1 : x.date > y.date ? 1 : 0));
|
|
904
973
|
return { status: 200, body: { nights: sorted } };
|
|
905
974
|
}
|
package/dist/db.js
CHANGED
|
@@ -652,6 +652,22 @@ const MIGRATIONS = [
|
|
|
652
652
|
WHERE importance_scored_at IS NULL AND base_strength != 0.5`);
|
|
653
653
|
},
|
|
654
654
|
},
|
|
655
|
+
{
|
|
656
|
+
version: 20,
|
|
657
|
+
name: "add_promotion_last_count",
|
|
658
|
+
up: (db) => {
|
|
659
|
+
// #448 — promotion baseline: the access_count the nightly promotion
|
|
660
|
+
// stage (stagePromotion) last consumed. Backfilled to access_count so
|
|
661
|
+
// the first post-upgrade nightly does not replay lifetime counts as
|
|
662
|
+
// promotions (v19 backfill precedent). Written by the stage, this
|
|
663
|
+
// backfill, and dedup merges (the baseline moves with the summed
|
|
664
|
+
// counter). Idempotent via hasColumn (the v8 pattern).
|
|
665
|
+
if (!hasColumn(db, "memories", "promotion_last_count")) {
|
|
666
|
+
db.exec("ALTER TABLE memories ADD COLUMN promotion_last_count INTEGER");
|
|
667
|
+
}
|
|
668
|
+
db.exec("UPDATE memories SET promotion_last_count = access_count WHERE promotion_last_count IS NULL");
|
|
669
|
+
},
|
|
670
|
+
},
|
|
655
671
|
];
|
|
656
672
|
/**
|
|
657
673
|
* Run all pending migrations against the database.
|
package/dist/dedup.js
CHANGED
|
@@ -347,7 +347,9 @@ function mergeCluster(db, canonical, losers, injectFailure) {
|
|
|
347
347
|
clearLoserTags.run(loser.id);
|
|
348
348
|
storage.updateMemory(db, loser.id, { domain: null });
|
|
349
349
|
}
|
|
350
|
-
// 5. Merge counters onto the canonical.
|
|
350
|
+
// 5. Merge counters onto the canonical. promotion_last_count moves WITH the
|
|
351
|
+
// summed access_count (#448): a held baseline would replay the losers'
|
|
352
|
+
// lifetime accesses as fresh promotions on the next nightly run.
|
|
351
353
|
const accessCount = canonical.access_count + losers.reduce((s, l) => s + l.access_count, 0);
|
|
352
354
|
const shownCount = (canonical.shown_count ?? 0) + losers.reduce((s, l) => s + (l.shown_count ?? 0), 0);
|
|
353
355
|
const lastAccessed = [canonical, ...losers]
|
|
@@ -361,6 +363,7 @@ function mergeCluster(db, canonical, losers, injectFailure) {
|
|
|
361
363
|
shown_count: shownCount,
|
|
362
364
|
...(lastAccessed ? { last_accessed: lastAccessed } : {}),
|
|
363
365
|
base_strength: baseStrength,
|
|
366
|
+
promotion_last_count: accessCount,
|
|
364
367
|
});
|
|
365
368
|
injectFailure?.(canonical.id);
|
|
366
369
|
// 6. Audit trail (dedup_log is the merge record — and the only surviving
|
|
@@ -50,8 +50,7 @@ export declare function runPruneDryRun(db: Database.Database, decayHalfLifeDays?
|
|
|
50
50
|
* with `importance` defaulting to `base_strength` (retrieval.ts). A memory
|
|
51
51
|
* can EVER cross the prune floor (0.01, stageDecayPrune) only if that
|
|
52
52
|
* asymptote is itself below it: base_strength² * 0.1 < 0.01, i.e.
|
|
53
|
-
* base_strength < sqrt(0.1).
|
|
54
|
-
* only slow the approach, they never raise the floor.
|
|
53
|
+
* base_strength < sqrt(0.1).
|
|
55
54
|
*/
|
|
56
55
|
export declare const EVER_PRUNABLE_BASE_STRENGTH_CEILING: number;
|
|
57
56
|
export declare function histogram(values: number[], edges?: number[]): Record<string, number>;
|
|
@@ -67,12 +66,14 @@ export interface StructuralStrengthStats {
|
|
|
67
66
|
}
|
|
68
67
|
/**
|
|
69
68
|
* Compute effective-strength distributions now, and simulated further out
|
|
70
|
-
* (
|
|
69
|
+
* (decay continuing with no further access or promotion — the
|
|
71
70
|
* "if nothing changes" projection), plus the asymptotic floor per memory.
|
|
72
71
|
* Requires `configureDecay` to already reflect the desired half-life (call
|
|
73
72
|
* `runPruneDryRun` first, or `configureDecay` directly, in the same process).
|
|
73
|
+
* `now` (#458): the instant "now" means — pinned by run-eval's --now for
|
|
74
|
+
* wall-clock-independent before/after audits; default the live clock.
|
|
74
75
|
*/
|
|
75
|
-
export declare function runStructuralStrengthStats(db: Database.Database): StructuralStrengthStats;
|
|
76
|
+
export declare function runStructuralStrengthStats(db: Database.Database, now?: Date): StructuralStrengthStats;
|
|
76
77
|
export interface AdoptionStats {
|
|
77
78
|
totalMemories: number;
|
|
78
79
|
shownCountHistogram: Record<string, number>;
|
|
@@ -107,4 +108,4 @@ export interface AdoptionStats {
|
|
|
107
108
|
* feature's youth as much as its effectiveness. Report callers should state
|
|
108
109
|
* that caveat alongside the numbers, not just the numbers.
|
|
109
110
|
*/
|
|
110
|
-
export declare function runAdoptionStats(db: Database.Database): AdoptionStats;
|
|
111
|
+
export declare function runAdoptionStats(db: Database.Database, now?: Date): AdoptionStats;
|
package/dist/eval/decay-eval.js
CHANGED
|
@@ -11,39 +11,6 @@
|
|
|
11
11
|
* floor, now vs simulated further out
|
|
12
12
|
* (d) adoption — #192 shown_count/access_count signal quality
|
|
13
13
|
*/
|
|
14
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
-
if (k2 === undefined) k2 = k;
|
|
16
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
-
}
|
|
20
|
-
Object.defineProperty(o, k2, desc);
|
|
21
|
-
}) : (function(o, m, k, k2) {
|
|
22
|
-
if (k2 === undefined) k2 = k;
|
|
23
|
-
o[k2] = m[k];
|
|
24
|
-
}));
|
|
25
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
-
}) : function(o, v) {
|
|
28
|
-
o["default"] = v;
|
|
29
|
-
});
|
|
30
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
-
var ownKeys = function(o) {
|
|
32
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
-
var ar = [];
|
|
34
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
-
return ar;
|
|
36
|
-
};
|
|
37
|
-
return ownKeys(o);
|
|
38
|
-
};
|
|
39
|
-
return function (mod) {
|
|
40
|
-
if (mod && mod.__esModule) return mod;
|
|
41
|
-
var result = {};
|
|
42
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
-
__setModuleDefault(result, mod);
|
|
44
|
-
return result;
|
|
45
|
-
};
|
|
46
|
-
})();
|
|
47
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
15
|
exports.EVER_PRUNABLE_BASE_STRENGTH_CEILING = void 0;
|
|
49
16
|
exports.runDomainBacklogAudit = runDomainBacklogAudit;
|
|
@@ -53,7 +20,6 @@ exports.runStructuralStrengthStats = runStructuralStrengthStats;
|
|
|
53
20
|
exports.runAdoptionStats = runAdoptionStats;
|
|
54
21
|
const retrieval_js_1 = require("../retrieval.js");
|
|
55
22
|
const consolidate_js_1 = require("../consolidate.js");
|
|
56
|
-
const storage = __importStar(require("../storage.js"));
|
|
57
23
|
const state_js_1 = require("../state.js");
|
|
58
24
|
/**
|
|
59
25
|
* Partition NULL-domain memories by the `domainCursor` rowid watermark.
|
|
@@ -106,8 +72,7 @@ function runPruneDryRun(db, decayHalfLifeDays = retrieval_js_1.DEFAULT_DECAY_HAL
|
|
|
106
72
|
* with `importance` defaulting to `base_strength` (retrieval.ts). A memory
|
|
107
73
|
* can EVER cross the prune floor (0.01, stageDecayPrune) only if that
|
|
108
74
|
* asymptote is itself below it: base_strength² * 0.1 < 0.01, i.e.
|
|
109
|
-
* base_strength < sqrt(0.1).
|
|
110
|
-
* only slow the approach, they never raise the floor.
|
|
75
|
+
* base_strength < sqrt(0.1).
|
|
111
76
|
*/
|
|
112
77
|
exports.EVER_PRUNABLE_BASE_STRENGTH_CEILING = Math.sqrt(0.1);
|
|
113
78
|
const STRENGTH_BUCKET_EDGES = [0, 0.01, 0.05, 0.1, 0.2, 0.3, 0.5, 0.7, 1.0];
|
|
@@ -128,17 +93,17 @@ function histogram(values, edges = STRENGTH_BUCKET_EDGES) {
|
|
|
128
93
|
}
|
|
129
94
|
/**
|
|
130
95
|
* Compute effective-strength distributions now, and simulated further out
|
|
131
|
-
* (
|
|
96
|
+
* (decay continuing with no further access or promotion — the
|
|
132
97
|
* "if nothing changes" projection), plus the asymptotic floor per memory.
|
|
133
98
|
* Requires `configureDecay` to already reflect the desired half-life (call
|
|
134
99
|
* `runPruneDryRun` first, or `configureDecay` directly, in the same process).
|
|
100
|
+
* `now` (#458): the instant "now" means — pinned by run-eval's --now for
|
|
101
|
+
* wall-clock-independent before/after audits; default the live clock.
|
|
135
102
|
*/
|
|
136
|
-
function runStructuralStrengthStats(db) {
|
|
103
|
+
function runStructuralStrengthStats(db, now = new Date()) {
|
|
137
104
|
const rows = db
|
|
138
|
-
.prepare("SELECT id, base_strength, last_accessed
|
|
105
|
+
.prepare("SELECT id, base_strength, last_accessed FROM memories")
|
|
139
106
|
.all();
|
|
140
|
-
const linkCounts = storage.getAllLinkCounts(db);
|
|
141
|
-
const now = new Date();
|
|
142
107
|
const nowVals = [];
|
|
143
108
|
const at180 = [];
|
|
144
109
|
const at365 = [];
|
|
@@ -148,11 +113,9 @@ function runStructuralStrengthStats(db) {
|
|
|
148
113
|
const base = r.base_strength ?? 0.5;
|
|
149
114
|
if (base < exports.EVER_PRUNABLE_BASE_STRENGTH_CEILING)
|
|
150
115
|
everPrunable++;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
at180.push((0, retrieval_js_1.effectiveStrength)(base, r.last_accessed, new Date(now.getTime() + 180 * 86_400_000), opts));
|
|
155
|
-
at365.push((0, retrieval_js_1.effectiveStrength)(base, r.last_accessed, new Date(now.getTime() + 365 * 86_400_000), opts));
|
|
116
|
+
nowVals.push((0, retrieval_js_1.effectiveStrength)(base, r.last_accessed, now));
|
|
117
|
+
at180.push((0, retrieval_js_1.effectiveStrength)(base, r.last_accessed, new Date(now.getTime() + 180 * 86_400_000)));
|
|
118
|
+
at365.push((0, retrieval_js_1.effectiveStrength)(base, r.last_accessed, new Date(now.getTime() + 365 * 86_400_000)));
|
|
156
119
|
neverVals.push(base * base * 0.1);
|
|
157
120
|
}
|
|
158
121
|
return {
|
|
@@ -185,11 +148,10 @@ function ageBucketLabel(ageDays) {
|
|
|
185
148
|
* feature's youth as much as its effectiveness. Report callers should state
|
|
186
149
|
* that caveat alongside the numbers, not just the numbers.
|
|
187
150
|
*/
|
|
188
|
-
function runAdoptionStats(db) {
|
|
151
|
+
function runAdoptionStats(db, now = new Date()) {
|
|
189
152
|
const rows = db
|
|
190
153
|
.prepare("SELECT id, shown_count, access_count, source_agent, created_at FROM memories")
|
|
191
154
|
.all();
|
|
192
|
-
const now = new Date();
|
|
193
155
|
const shownVals = [];
|
|
194
156
|
const accessVals = [];
|
|
195
157
|
let totalShown = 0;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #458 — the shared pinned-clock helper for eval harnesses.
|
|
3
|
+
*
|
|
4
|
+
* The recall evals score against wall-clock time (recency + decay terms in
|
|
5
|
+
* computeScore/effectiveStrength), so a pre-photo and a post-run hours apart
|
|
6
|
+
* drift even on identical code — PR D evidenced mode-OFF units moving with no
|
|
7
|
+
* changed code in the path. The fix is a harness-visible pin: every eval that
|
|
8
|
+
* calls retrieve() (or the decay/adoption stats) accepts a `--now <ISO>` flag
|
|
9
|
+
* and threads the SAME instant into the retrieve() `now` seam (retrieval.ts —
|
|
10
|
+
* the injectable-clock idiom of run-deadline.ts), making before/after runs
|
|
11
|
+
* wall-clock-independent. Default stays the live clock — pinning is opt-in,
|
|
12
|
+
* for comparability.
|
|
13
|
+
*
|
|
14
|
+
* Fail-explicit by design (the repo convention): an invalid pin THROWS with a
|
|
15
|
+
* clear message rather than silently falling back to the live clock — a run
|
|
16
|
+
* that believed it was pinned but wasn't would silently reintroduce the drift
|
|
17
|
+
* class this helper exists to remove.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Parse a `--now` flag value into the pinned instant.
|
|
21
|
+
*
|
|
22
|
+
* @param raw the raw flag value. undefined or empty/whitespace → null (the
|
|
23
|
+
* live clock — the flag was not passed). Anything else must parse as a
|
|
24
|
+
* Date; garbage or an invalid instant (e.g. month 13) throws.
|
|
25
|
+
* @returns the pinned Date, or null for the live clock.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parsePinnedNow(raw: string | undefined): Date | null;
|
|
28
|
+
/**
|
|
29
|
+
* Human-readable clock mode for report headers / photo JSON: "pinned <ISO>"
|
|
30
|
+
* or "live" — so artifacts are self-describing about which clock produced them.
|
|
31
|
+
*/
|
|
32
|
+
export declare function clockLabel(now: Date | null): string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* #458 — the shared pinned-clock helper for eval harnesses.
|
|
4
|
+
*
|
|
5
|
+
* The recall evals score against wall-clock time (recency + decay terms in
|
|
6
|
+
* computeScore/effectiveStrength), so a pre-photo and a post-run hours apart
|
|
7
|
+
* drift even on identical code — PR D evidenced mode-OFF units moving with no
|
|
8
|
+
* changed code in the path. The fix is a harness-visible pin: every eval that
|
|
9
|
+
* calls retrieve() (or the decay/adoption stats) accepts a `--now <ISO>` flag
|
|
10
|
+
* and threads the SAME instant into the retrieve() `now` seam (retrieval.ts —
|
|
11
|
+
* the injectable-clock idiom of run-deadline.ts), making before/after runs
|
|
12
|
+
* wall-clock-independent. Default stays the live clock — pinning is opt-in,
|
|
13
|
+
* for comparability.
|
|
14
|
+
*
|
|
15
|
+
* Fail-explicit by design (the repo convention): an invalid pin THROWS with a
|
|
16
|
+
* clear message rather than silently falling back to the live clock — a run
|
|
17
|
+
* that believed it was pinned but wasn't would silently reintroduce the drift
|
|
18
|
+
* class this helper exists to remove.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.parsePinnedNow = parsePinnedNow;
|
|
22
|
+
exports.clockLabel = clockLabel;
|
|
23
|
+
/**
|
|
24
|
+
* Parse a `--now` flag value into the pinned instant.
|
|
25
|
+
*
|
|
26
|
+
* @param raw the raw flag value. undefined or empty/whitespace → null (the
|
|
27
|
+
* live clock — the flag was not passed). Anything else must parse as a
|
|
28
|
+
* Date; garbage or an invalid instant (e.g. month 13) throws.
|
|
29
|
+
* @returns the pinned Date, or null for the live clock.
|
|
30
|
+
*/
|
|
31
|
+
function parsePinnedNow(raw) {
|
|
32
|
+
if (raw === undefined || raw.trim() === "")
|
|
33
|
+
return null;
|
|
34
|
+
const parsed = new Date(raw);
|
|
35
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
36
|
+
throw new Error(`eval clock: invalid --now value "${raw}" — pass a full ISO 8601 instant ` +
|
|
37
|
+
`(e.g. 2026-09-17T09:00:00.000Z) or omit the flag to run on the live clock`);
|
|
38
|
+
}
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Human-readable clock mode for report headers / photo JSON: "pinned <ISO>"
|
|
43
|
+
* or "live" — so artifacts are self-describing about which clock produced them.
|
|
44
|
+
*/
|
|
45
|
+
function clockLabel(now) {
|
|
46
|
+
return now === null ? "live" : `pinned ${now.toISOString()}`;
|
|
47
|
+
}
|
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
* corrected cosine formula, #145).
|
|
9
9
|
*/
|
|
10
10
|
import type Database from "better-sqlite3";
|
|
11
|
+
/**
|
|
12
|
+
* Deterministic sample of `rows` (#460): a seeded partial Fisher–Yates
|
|
13
|
+
* shuffles the LAST `size` slots from the back, and that shuffled tail is
|
|
14
|
+
* the sample. Rows must already be in a stable order (the caller sorts by
|
|
15
|
+
* PK) so the draw does not depend on SQLite's scan order.
|
|
16
|
+
*/
|
|
17
|
+
export declare function seededSample<T>(rows: T[], size: number, seed: number): T[];
|
|
11
18
|
export interface LinkRow {
|
|
12
19
|
source_id: string;
|
|
13
20
|
target_id: string;
|
|
@@ -50,8 +57,14 @@ export interface DriftReport {
|
|
|
50
57
|
maxAbsDrift: number;
|
|
51
58
|
skippedMissingEmbedding: number;
|
|
52
59
|
}
|
|
53
|
-
/**
|
|
54
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Recompute cosine for a link sample and compare to stored `strength` (post
|
|
62
|
+
* migration-5 rescale, should track closely). The sample is deterministic
|
|
63
|
+
* (#460): all link rows in stable PK order, drawn by a seeded Fisher–Yates —
|
|
64
|
+
* two runs on the same build + snapshot produce an identical report, so
|
|
65
|
+
* before/after comparisons can require full-output identity.
|
|
66
|
+
*/
|
|
67
|
+
export declare function runDriftSample(db: Database.Database, embeddings: Map<string, Float32Array>, sampleSize?: number, seed?: number): DriftReport;
|
|
55
68
|
export interface PartitionStats {
|
|
56
69
|
linkCount: number;
|
|
57
70
|
byRelationship: Record<string, number>;
|