@gamaze/hicortex 0.23.0 → 0.23.2

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.
Files changed (49) hide show
  1. package/assets/dashboard.html +455 -153
  2. package/dist/dashboard.d.ts +18 -7
  3. package/dist/dashboard.js +42 -10
  4. package/dist/dedup.js +2 -2
  5. package/dist/index.js +17 -2
  6. package/dist/learnings-identity.js +20 -1
  7. package/dist/llm.d.ts +12 -1
  8. package/dist/llm.js +14 -3
  9. package/dist/mcp-server.js +6 -2
  10. package/dist/mcp-stdio.d.ts +61 -3
  11. package/dist/mcp-stdio.js +272 -51
  12. package/dist/nightly.js +1 -1
  13. package/hermes-plugin/hicortex/provider.py +23 -0
  14. package/opencode-plugin/hicortex/index.ts +24 -1
  15. package/package.json +2 -1
  16. package/pi-extension/hicortex/index.ts +24 -1
  17. package/server.json +2 -2
  18. package/dist/eval/decay-eval.d.ts +0 -111
  19. package/dist/eval/decay-eval.js +0 -214
  20. package/dist/eval/dups.d.ts +0 -100
  21. package/dist/eval/dups.js +0 -174
  22. package/dist/eval/eval-clock.d.ts +0 -32
  23. package/dist/eval/eval-clock.js +0 -47
  24. package/dist/eval/eval-db.d.ts +0 -25
  25. package/dist/eval/eval-db.js +0 -67
  26. package/dist/eval/graph-eval.d.ts +0 -89
  27. package/dist/eval/graph-eval.js +0 -246
  28. package/dist/eval/importance-eval.d.ts +0 -85
  29. package/dist/eval/importance-eval.js +0 -286
  30. package/dist/eval/planted-eval.d.ts +0 -30
  31. package/dist/eval/planted-eval.js +0 -122
  32. package/dist/eval/planted-fixtures.d.ts +0 -107
  33. package/dist/eval/planted-fixtures.js +0 -283
  34. package/dist/eval/planted-harness.d.ts +0 -183
  35. package/dist/eval/planted-harness.js +0 -651
  36. package/dist/eval/ranking-battery.d.ts +0 -125
  37. package/dist/eval/ranking-battery.js +0 -289
  38. package/dist/eval/ranking-eval.d.ts +0 -61
  39. package/dist/eval/ranking-eval.js +0 -554
  40. package/dist/eval/ranking-fixtures.d.ts +0 -117
  41. package/dist/eval/ranking-fixtures.js +0 -485
  42. package/dist/eval/recall-sweep.d.ts +0 -87
  43. package/dist/eval/recall-sweep.js +0 -1030
  44. package/dist/eval/reflection-census.d.ts +0 -19
  45. package/dist/eval/reflection-census.js +0 -25
  46. package/dist/eval/relevance-eval.d.ts +0 -178
  47. package/dist/eval/relevance-eval.js +0 -2240
  48. package/dist/eval/run-eval.d.ts +0 -20
  49. package/dist/eval/run-eval.js +0 -299
@@ -1,651 +0,0 @@
1
- "use strict";
2
- /**
3
- * The planted-pairs acceptance gate (#393 increment A) — machinery shared by
4
- * the CLI runner (planted-eval.ts, REAL embedder + real-text corpus) and the
5
- * vitest suite (tests/planted-pairs.test.ts, controlled synthetic vectors).
6
- *
7
- * What it measures, per planted pair, before/after running the REAL
8
- * production resolution stage (`stageReconsolidation`, which internally runs
9
- * the deterministic merge zone LAST — guard-C: judgment outranks the sweep):
10
- *
11
- * DETECTED — the pair entered the resolution machinery as a candidate: a
12
- * verdict call was made on it (judged band, logged by the ground-truth
13
- * judge) OR it co-clustered in the pre-stage `planDedup` discovery at the
14
- * zone ceiling. Tagged by source ("judge" = KNN/similarity, "scout" = the
15
- * #393 B reference-extraction source — attribution rule: a verdict call
16
- * on a below-floor pair can only be scout-sourced, "zone" = the
17
- * deterministic ceiling).
18
- * BOUND — a resolution structure reflecting the ground truth now connects
19
- * the pair: `corrected_by` / `superseded_by` / `conflicts` link, or a
20
- * merge (`dedup_log` row). Guard-C added the `conflicts` bind (the judge
21
- * flags a genuine conflict instead of leaving it inexpressible).
22
- * RESOLVED — the class-specific desired END STATE holds in the store:
23
- * corrects = correction effective (older demoted/absorbed/rewritten) and
24
- * the correction information live;
25
- * supersedes= older demoted-or-absorbed and newer live in recall;
26
- * merge = exactly one live row remains, the other absorbed via dedup;
27
- * conflict = both live AND not blended AND the conflicts link exists
28
- * (guard-C's end state — the consumer sees both truths);
29
- * none = both live, not blended, no resolution link between them.
30
- *
31
- * The version_chain CLASS end-state is measured on the RECALL surface, not
32
- * the store (#393 D): a retrieval-only change cannot move a store probe, so
33
- * post-stage the harness runs the production retrieve() (read-only: limit 3,
34
- * below the cold-exposure threshold, noStrengthen) with the OLDEST chain
35
- * member's content as query and requires the belief walk's guarantee — no
36
- * superseded ancestor surfaces as a competing truth, and the linked chain's
37
- * terminal surfaces. Store facts (terminal live, non-terminals gone) remain
38
- * in the class note as corroboration.
39
- *
40
- * The judge is a GROUND-TRUTH-STUBBED LlmClient (the refine addendum's Q3
41
- * recommendation): it answers each planted pair's declared relation and
42
- * defaults unknown/collider pairs to `none`. This isolates the MACHINERY
43
- * (detection sources, binding actions, merge guard, walk) from judge quality —
44
- * the harness answers "would the pipeline resolve a KNOWN correction if the
45
- * judge were perfect?". Live-LLM verdict quality is the real-corpus soak run.
46
- *
47
- * Read/write discipline: the gate NEVER touches ~/.hicortex — the fixture DB
48
- * and the stage's stateDir are caller-supplied (temp) paths. A real snapshot
49
- * is only ever PLANTED INTO A COPY (planted-eval.ts --snapshot).
50
- */
51
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
52
- if (k2 === undefined) k2 = k;
53
- var desc = Object.getOwnPropertyDescriptor(m, k);
54
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
55
- desc = { enumerable: true, get: function() { return m[k]; } };
56
- }
57
- Object.defineProperty(o, k2, desc);
58
- }) : (function(o, m, k, k2) {
59
- if (k2 === undefined) k2 = k;
60
- o[k2] = m[k];
61
- }));
62
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
63
- Object.defineProperty(o, "default", { enumerable: true, value: v });
64
- }) : function(o, v) {
65
- o["default"] = v;
66
- });
67
- var __importStar = (this && this.__importStar) || (function () {
68
- var ownKeys = function(o) {
69
- ownKeys = Object.getOwnPropertyNames || function (o) {
70
- var ar = [];
71
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
72
- return ar;
73
- };
74
- return ownKeys(o);
75
- };
76
- return function (mod) {
77
- if (mod && mod.__esModule) return mod;
78
- var result = {};
79
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
80
- __setModuleDefault(result, mod);
81
- return result;
82
- };
83
- })();
84
- Object.defineProperty(exports, "__esModule", { value: true });
85
- exports.plantCorpus = plantCorpus;
86
- exports.makeGroundTruthJudge = makeGroundTruthJudge;
87
- exports.runPlantedGate = runPlantedGate;
88
- exports.renderPlantedReport = renderPlantedReport;
89
- const node_fs_1 = require("node:fs");
90
- const retrieval_js_1 = require("../retrieval.js");
91
- const db_js_1 = require("../db.js");
92
- const storage = __importStar(require("../storage.js"));
93
- const consolidate_js_1 = require("../consolidate.js");
94
- const reconsolidation_js_1 = require("../reconsolidation.js");
95
- const dedup_js_1 = require("../dedup.js");
96
- const graph_eval_js_1 = require("./graph-eval.js");
97
- const planted_fixtures_js_1 = require("./planted-fixtures.js");
98
- // ---------------------------------------------------------------------------
99
- // Fixture DB builder
100
- // ---------------------------------------------------------------------------
101
- /** Insert the corpus into a writable DB (fresh, or a copy of a snapshot). */
102
- async function plantCorpus(db, corpus, embedFn) {
103
- const idByKey = new Map();
104
- for (const row of corpus.rows) {
105
- const embedding = await embedFn(row.content);
106
- const id = storage.insertMemory(db, row.content, embedding, {
107
- sourceAgent: planted_fixtures_js_1.PLANTED_SOURCE_AGENT,
108
- project: planted_fixtures_js_1.PLANTED_PROJECT,
109
- memoryType: row.memoryType,
110
- createdAt: row.createdAt,
111
- });
112
- idByKey.set(row.key, id);
113
- }
114
- return idByKey;
115
- }
116
- const VERDICT_PROMPT_RE = /OLDER MEMORY:\n([\s\S]*?)\n\nNEWER MEMORY:\n([\s\S]*?)\n\nHow does the NEWER/;
117
- const REWRITE_PROMPT_RE = /OLDER MEMORY \(currently stored[^)]*\):\n([\s\S]*?)\n\nCORRECTING MEMORIES \(newer[^)]*\):\n([\s\S]*?)\n\nCompose/;
118
- const SCOUT_PROMPT_RE = /You are scanning a memory that was just added[\s\S]*?MEMORY:\n([\s\S]*?)\n\nDoes this memory correct/;
119
- /**
120
- * Deterministic, ground-truth-keyed judge. Parses each prompt back into its
121
- * (older, newer) contents — the exact texts `buildCorrectionVerdictPrompt` /
122
- * `buildRewritePrompt` embed — and answers from the corpus's declared
123
- * relations:
124
- * corrects -> {"action":"corrects","confidence":0.95} (above the gate)
125
- * supersedes-> {"action":"supersedes","confidence":0.9}
126
- * merge -> {"action":"merge","confidence":0.95} (above the gate)
127
- * conflict -> {"action":"conflicts","confidence":0.9} (guard-C: flag,
128
- * both live, never blended)
129
- * none -> {"action":"none","confidence":0.9}
130
- * Unknown pairs (colliders, filler cross-pairs) default to none.
131
- *
132
- * #393 B: the judge also answers the scout's per-memory shape calls — a
133
- * memory is correction-shaped iff it is the newer side of a declared
134
- * corrects/supersedes/conflict pair (declared `references` returned verbatim,
135
- * throwing if a resolution-shaped pair never declared them; guard-C added
136
- * `conflict` to the shaped set — two records disagreeing on one quantity
137
- * share their wording); everything else answers correction=false. Shape calls
138
- * are logged in scoutCalls.
139
- */
140
- function makeGroundTruthJudge(corpus, idByKey) {
141
- const keyByContent = new Map();
142
- for (const row of corpus.rows)
143
- keyByContent.set(row.content, row.key);
144
- const pairByKeys = new Map();
145
- for (const p of corpus.pairs)
146
- pairByKeys.set(`${p.olderKey}|${p.newerKey}`, p);
147
- const keyById = new Map([...idByKey.entries()].map(([key, id]) => [id, key]));
148
- const verdictCalls = [];
149
- const rewriteCalls = [];
150
- const scoutCalls = [];
151
- const verdictReply = (p) => {
152
- switch (p.relation) {
153
- case "corrects":
154
- return '{"action": "corrects", "confidence": 0.95}';
155
- case "supersedes":
156
- return '{"action": "supersedes", "confidence": 0.9}';
157
- case "merge":
158
- return '{"action": "merge", "confidence": 0.95}';
159
- case "conflict":
160
- return '{"action": "conflicts", "confidence": 0.9}';
161
- default:
162
- return '{"action": "none", "confidence": 0.9}';
163
- }
164
- };
165
- /**
166
- * Ground truth for the scout's shape question: a memory is correction-shaped
167
- * iff it is the NEWER side of a declared `corrects`/`supersedes`/`conflict`
168
- * pair (guard-C added `conflict` — two records disagreeing on one quantity
169
- * share their wording, so the scout's reference extraction finds the old
170
- * record exactly like a correction does). `merge`/`none` are
171
- * duplicates/additions, not resolutions.
172
- */
173
- const shapeFor = (key) => {
174
- const shaped = corpus.pairs.find((p) => p.newerKey === key &&
175
- (p.relation === "corrects" || p.relation === "supersedes" || p.relation === "conflict"));
176
- if (!shaped)
177
- return { correction: false, references: "" };
178
- if (!shaped.references) {
179
- throw new Error(`planted-harness: shape call on ${key} (relation ${shaped.relation}) has no declared ` +
180
- `references — every corrects/supersedes/conflict pair must declare the old claim's terms`);
181
- }
182
- return { correction: true, references: shaped.references };
183
- };
184
- const obj = {
185
- verdictCalls,
186
- rewriteCalls,
187
- scoutCalls,
188
- complete: async (prompt) => {
189
- const scoutMatch = SCOUT_PROMPT_RE.exec(prompt);
190
- if (scoutMatch) {
191
- const key = keyByContent.get(scoutMatch[1]) ?? "?";
192
- const shape = key === "?" ? { correction: false, references: "" } : shapeFor(key);
193
- scoutCalls.push({ key, correction: shape.correction });
194
- return {
195
- text: JSON.stringify({
196
- correction: shape.correction,
197
- references: shape.references,
198
- confidence: 0.9,
199
- }),
200
- };
201
- }
202
- const verdictMatch = VERDICT_PROMPT_RE.exec(prompt);
203
- if (verdictMatch) {
204
- const olderKey = keyByContent.get(verdictMatch[1]);
205
- const newerKey = keyByContent.get(verdictMatch[2]);
206
- const pair = olderKey !== undefined && newerKey !== undefined
207
- ? pairByKeys.get(`${olderKey}|${newerKey}`)
208
- : undefined;
209
- verdictCalls.push({
210
- olderKey: olderKey ?? "?",
211
- newerKey: newerKey ?? "?",
212
- grounded: pair !== undefined,
213
- });
214
- return { text: pair ? verdictReply(pair) : '{"action": "none", "confidence": 0.9}' };
215
- }
216
- const rewriteMatch = REWRITE_PROMPT_RE.exec(prompt);
217
- if (rewriteMatch) {
218
- const targetKey = keyByContent.get(rewriteMatch[1]);
219
- const triggerIds = [...rewriteMatch[2].matchAll(/\[([0-9a-f-]{36})\]/g)].map((m) => m[1]);
220
- rewriteCalls.push({
221
- targetKey: targetKey ?? "?",
222
- triggerKeys: triggerIds.map((id) => keyById.get(id) ?? "?"),
223
- });
224
- // The scripted rewrite: the declared `rewritten` of the corrects pair
225
- // targeting this row, with every trigger absorbed (the correction
226
- // rider is transition-only — the standard disposition).
227
- const pair = corpus.pairs.find((p) => p.relation === "corrects" && p.olderKey === targetKey && p.rewritten !== undefined);
228
- if (!pair || !pair.rewritten) {
229
- throw new Error(`planted-harness: rewrite prompt for ${targetKey ?? "?"} has no scripted rewritten text — ` +
230
- `every corrects pair must declare one`);
231
- }
232
- return {
233
- text: JSON.stringify({
234
- rewritten: pair.rewritten,
235
- triggers: triggerIds.map((id) => ({ id, disposition: "absorb" })),
236
- }),
237
- };
238
- }
239
- throw new Error(`planted-harness: unrecognized LLM prompt (neither verdict nor rewrite):\n${prompt.slice(0, 200)}`);
240
- },
241
- };
242
- return { llm: obj, verdictCalls, rewriteCalls, scoutCalls };
243
- }
244
- function probe(db, id) {
245
- const row = db
246
- .prepare("SELECT status, content FROM memories WHERE id = ?")
247
- .get(id);
248
- if (!row)
249
- return null;
250
- const hasVector = !!db.prepare("SELECT 1 FROM memory_vectors WHERE id = ?").get(id);
251
- return { status: row.status, content: row.content, hasVector };
252
- }
253
- /** Absorbed rows are structurally invisible (no vector row). */
254
- function absorbed(p) {
255
- return p === null || p.status === "absorbed" || !p.hasVector;
256
- }
257
- /** Live in recall: not absorbed (superseded/retracted rows remain visible, demoted). */
258
- function liveInRecall(p) {
259
- return p !== null && p.status !== "absorbed" && p.hasVector;
260
- }
261
- /** Demoted (ranking-penalty status) or fully out of recall. */
262
- function demotedOrGone(p) {
263
- return p === null || p.status === "superseded" || p.status === "retracted" || absorbed(p);
264
- }
265
- function linkBetween(db, a, b, relationships) {
266
- if (relationships.length === 0)
267
- return null;
268
- const ph = relationships.map(() => "?").join(",");
269
- const row = db
270
- .prepare(`SELECT source_id, target_id, relationship FROM memory_links
271
- WHERE relationship IN (${ph})
272
- AND ((source_id = ? AND target_id = ?) OR (source_id = ? AND target_id = ?))`)
273
- .get(...relationships, a, b, b, a);
274
- if (!row)
275
- return null;
276
- return {
277
- relationship: row.relationship,
278
- direction: row.source_id === a ? "a_to_b" : "b_to_a",
279
- };
280
- }
281
- function dedupLogBetween(db, a, b) {
282
- const row = db
283
- .prepare(`SELECT loser_id, canonical_id FROM dedup_log
284
- WHERE (loser_id = ? AND canonical_id = ?) OR (loser_id = ? AND canonical_id = ?)`)
285
- .get(a, b, b, a);
286
- if (!row)
287
- return null;
288
- return { loserKeyIsA: row.loser_id === a };
289
- }
290
- const alwaysAcquire = async () => () => { };
291
- /** Canonical map key for an unordered row pair. */
292
- function pairKey(a, b) {
293
- return [a, b].sort().join("|");
294
- }
295
- /** All rows' measured pairwise cosines (planted corpus only). */
296
- function measuredCosines(db, corpus, idByKey) {
297
- const out = new Map();
298
- for (let i = 0; i < corpus.rows.length; i++) {
299
- for (let j = i + 1; j < corpus.rows.length; j++) {
300
- const a = corpus.rows[i];
301
- const b = corpus.rows[j];
302
- const va = storage.getStoredEmbedding(db, idByKey.get(a.key) ?? "");
303
- const vb = storage.getStoredEmbedding(db, idByKey.get(b.key) ?? "");
304
- out.set(pairKey(a.key, b.key), va && vb ? (0, graph_eval_js_1.cosineBetween)(va, vb) : Number.NaN);
305
- }
306
- }
307
- return out;
308
- }
309
- /**
310
- * Build (or plant into) the DB, run the production stage, measure the gate.
311
- * This is the ONE orchestration shared by the CLI runner and the vitest suite.
312
- */
313
- async function runPlantedGate(corpus, opts) {
314
- (0, node_fs_1.mkdirSync)(opts.stateDir, { recursive: true });
315
- const db = (0, db_js_1.initDb)(opts.dbPath);
316
- try {
317
- const idByKey = await plantCorpus(db, corpus, opts.embedFn);
318
- const keyById = new Map([...idByKey.entries()].map(([k, v]) => [v, k]));
319
- // --- pre-stage zone discovery snapshot (detection + blend prediction) ---
320
- const threshold = opts.threshold ?? dedup_js_1.DEFAULT_DEDUP_MERGE_THRESHOLD;
321
- const zonePlanBefore = (0, dedup_js_1.planDedup)(db, threshold);
322
- const zoneClusterKeys = zonePlanBefore.mergePlans.map((p) => {
323
- const keys = [...p.membersOldestFirst.map((m) => m.id), ...p.losers.map((l) => l.id)]
324
- .map((id) => keyById.get(id))
325
- .filter((k) => k !== undefined);
326
- return new Set(keys);
327
- });
328
- const mismatchClusterKeys = zonePlanBefore.mismatchSkipped.map((p) => {
329
- const keys = p.memberIds.map((id) => keyById.get(id)).filter((k) => k !== undefined);
330
- return new Set(keys);
331
- });
332
- const blendPredictions = [];
333
- for (const p of zonePlanBefore.mergePlans) {
334
- const canonicalKey = keyById.get(p.canonical.id);
335
- const loserKeys = p.losers.map((l) => keyById.get(l.id)).filter((k) => !!k);
336
- if (!canonicalKey)
337
- continue;
338
- for (const loserKey of loserKeys) {
339
- blendPredictions.push({ pair: `${loserKey}+${canonicalKey}`, canonicalKey, loserKey });
340
- }
341
- }
342
- const cosines = measuredCosines(db, corpus, idByKey);
343
- const declaredPairs = new Set(corpus.pairs.map((p) => pairKey(p.olderKey, p.newerKey)));
344
- // --- run the production stage ---
345
- const judge = makeGroundTruthJudge(corpus, idByKey);
346
- const budget = new consolidate_js_1.BudgetTracker(1_000_000);
347
- const stageReport = await (0, reconsolidation_js_1.stageReconsolidation)(db, judge.llm, budget, opts.embedFn, false, opts.stateDir, { acquireLock: opts.acquireLock ?? alwaysAcquire, autoMergeThreshold: threshold });
348
- // --- measure ---
349
- const inCluster = (sets, a, b) => sets.some((s) => s.has(a) && s.has(b));
350
- const pairResults = corpus.pairs.map((p) => {
351
- const olderId = idByKey.get(p.olderKey) ?? "";
352
- const newerId = idByKey.get(p.newerKey) ?? "";
353
- const cosine = cosines.get(pairKey(p.olderKey, p.newerKey)) ?? Number.NaN;
354
- const inBand = Number.isFinite(cosine) && cosine >= p.band[0] && cosine <= p.band[1];
355
- const judged = judge.verdictCalls.some((c) => (c.olderKey === p.olderKey && c.newerKey === p.newerKey) ||
356
- (c.olderKey === p.newerKey && c.newerKey === p.olderKey));
357
- const zoned = inCluster(zoneClusterKeys, p.olderKey, p.newerKey);
358
- const mismatched = inCluster(mismatchClusterKeys, p.olderKey, p.newerKey);
359
- // #393 B scout attribution: a verdict call on a BELOW-floor pair can
360
- // only come from the scout source (the similarity floor structurally
361
- // excludes sub-floor pairs before any LLM sees them).
362
- const scouted = judged && Number.isFinite(cosine) && cosine < reconsolidation_js_1.DEFAULT_CORRECTION_MIN_SIMILARITY;
363
- const detectionSource = scouted && zoned ? "scout+zone"
364
- : scouted ? "scout"
365
- : judged && zoned ? "judge+zone"
366
- : judged ? "judge"
367
- : zoned || mismatched ? "zone"
368
- : "";
369
- const olderProbe = probe(db, olderId);
370
- const newerProbe = probe(db, newerId);
371
- const merged = dedupLogBetween(db, olderId, newerId);
372
- let bound;
373
- let boundHow;
374
- switch (p.relation) {
375
- case "corrects": {
376
- const link = linkBetween(db, olderId, newerId, ["corrected_by"]);
377
- bound = link !== null;
378
- boundHow = link ? `corrected_by (${link.direction === "a_to_b" ? "older→newer" : "newer→older"})` : "no corrected_by link";
379
- break;
380
- }
381
- case "supersedes": {
382
- const link = linkBetween(db, olderId, newerId, ["superseded_by"]);
383
- bound = link !== null || merged !== null;
384
- boundHow = link
385
- ? `superseded_by (${link.direction === "a_to_b" ? "older→newer" : "newer→older"})`
386
- : merged
387
- ? "merged via dedup_log (blend, not a chain link)"
388
- : "no superseded_by link";
389
- break;
390
- }
391
- case "merge": {
392
- bound = merged !== null;
393
- boundHow = merged ? "merged via dedup_log" : "not merged";
394
- break;
395
- }
396
- case "conflict": {
397
- // Guard-C's bind: the `conflicts` link, either direction.
398
- const link = linkBetween(db, olderId, newerId, ["conflicts"]);
399
- bound = link !== null;
400
- boundHow = link ? "conflicts bind" : "no conflicts link";
401
- break;
402
- }
403
- default: {
404
- bound = null;
405
- boundHow = "n/a (keep-both relation)";
406
- }
407
- }
408
- let resolved;
409
- const notes = [];
410
- switch (p.relation) {
411
- case "corrects": {
412
- const rewrittenInPlace = olderProbe !== null &&
413
- olderProbe.status === "corrected" &&
414
- olderProbe.content !== (corpus.rows.find((r) => r.key === p.olderKey)?.content ?? "");
415
- const correctionLive = liveInRecall(newerProbe) || rewrittenInPlace;
416
- resolved = (demotedOrGone(olderProbe) || rewrittenInPlace) && correctionLive;
417
- if (!judged && !zoned)
418
- notes.push(`cosine ${cosine.toFixed(3)} below the 0.75 floor — never a candidate`);
419
- if (rewrittenInPlace)
420
- notes.push("older rewritten in place (corrected)");
421
- else if (demotedOrGone(olderProbe))
422
- notes.push("older demoted/absorbed");
423
- else
424
- notes.push("older still active — wrong fact remains recallable");
425
- break;
426
- }
427
- case "supersedes": {
428
- const olderGone = demotedOrGone(olderProbe);
429
- const newerLive = liveInRecall(newerProbe);
430
- resolved = olderGone && newerLive;
431
- if (merged) {
432
- notes.push(`BLENDED by the deterministic zone (canonical=${merged.loserKeyIsA ? p.newerKey : p.olderKey}) — no supersession chain`);
433
- }
434
- if (!olderGone)
435
- notes.push("older still recallable");
436
- if (!newerLive) {
437
- // Guard-C note: with the zone running last, an absorbed newer side
438
- // was scanned (even judged) before the zone blended it out of
439
- // recall — the parenthetical states the store fact, not the scan.
440
- notes.push(`newer not live in recall${absorbed(newerProbe) ? " (absorbed)" : ""}`);
441
- }
442
- if (resolved && !merged)
443
- notes.push("older demoted, newer live");
444
- break;
445
- }
446
- case "merge": {
447
- resolved =
448
- merged !== null &&
449
- (liveInRecall(olderProbe) !== liveInRecall(newerProbe)) &&
450
- (absorbed(olderProbe) || absorbed(newerProbe));
451
- notes.push(resolved ? "one live row remains, loser absorbed with audit row" : "pair not merged");
452
- break;
453
- }
454
- case "conflict": {
455
- const bothLive = liveInRecall(olderProbe) && liveInRecall(newerProbe);
456
- const conflictMarked = bound === true;
457
- resolved = bothLive && !merged && conflictMarked;
458
- if (merged) {
459
- notes.push(`HARM: blended by the deterministic zone (canonical=${merged.loserKeyIsA ? p.newerKey : p.olderKey}) — two live true records destroyed into one`);
460
- }
461
- else if (conflictMarked && bothLive) {
462
- notes.push("both live, conflict-flagged, no blend — the consumer sees both truths");
463
- }
464
- else if (bothLive) {
465
- notes.push("both live but unflagged — silent coexistence, consumer cannot see the conflict");
466
- }
467
- break;
468
- }
469
- default: {
470
- // none: keep both, untouched
471
- const bothLive = liveInRecall(olderProbe) && liveInRecall(newerProbe);
472
- const linked = linkBetween(db, olderId, newerId, ["corrected_by", "superseded_by", "conflicts"]);
473
- resolved = bothLive && !merged && !linked;
474
- notes.push(resolved ? "both kept live, no link, not merged" : "unexpectedly bound or missing");
475
- break;
476
- }
477
- }
478
- if (mismatched && !zoned)
479
- notes.push("zone cluster refused on metadata mismatch (detected, not merged)");
480
- if (!inBand)
481
- notes.push(`OUT-OF-BAND cosine ${cosine.toFixed(3)} vs declared [${p.band[0]}, ${p.band[1]}]`);
482
- return {
483
- class: p.class,
484
- olderKey: p.olderKey,
485
- newerKey: p.newerKey,
486
- relation: p.relation,
487
- cosine,
488
- inBand,
489
- detected: detectionSource !== "",
490
- detectionSource,
491
- bound,
492
- boundHow,
493
- resolved,
494
- notes: notes.join("; "),
495
- };
496
- });
497
- // --- class aggregates ---
498
- const byClass = new Map();
499
- for (const r of pairResults) {
500
- const arr = byClass.get(r.class) ?? [];
501
- arr.push(r);
502
- byClass.set(r.class, arr);
503
- }
504
- const classResults = [];
505
- for (const [cls, rows] of byClass) {
506
- let classResolved;
507
- let notes;
508
- if (cls === "version_chain") {
509
- const members = (0, planted_fixtures_js_1.chainMembers)(corpus);
510
- const terminal = members[members.length - 1];
511
- const terminalLive = liveInRecall(probe(db, idByKey.get(terminal) ?? ""));
512
- const nonTerminalsGone = members
513
- .slice(0, -1)
514
- .every((k) => demotedOrGone(probe(db, idByKey.get(k) ?? "")));
515
- // #393 D: the class end-state is measured on the RECALL surface —
516
- // the production retrieve(), read-only. limit 3 is deliberately
517
- // below the cold-exposure threshold (limit >= 4) and noStrengthen
518
- // keeps the probe side-effect-free, so it cannot perturb anything
519
- // downstream in this report.
520
- const queryContent = corpus.rows.find((r) => r.key === members[0])?.content ?? "";
521
- const recall = await (0, retrieval_js_1.retrieve)(db, opts.embedFn, queryContent, {
522
- limit: 3,
523
- noStrengthen: true,
524
- ...(opts.now ? { now: opts.now } : {}), // #458: pinned clock (undefined = live)
525
- });
526
- // Every surfaced row must be its own walk terminal — no superseded
527
- // ancestor competes as a truth — and the linked chain's terminal
528
- // (computed with the production helper) must be surfaced.
529
- const competing = recall.filter((r) => (0, retrieval_js_1.beliefWalkTerminal)(db, r.id) !== r.id);
530
- const walkTerminalId = (0, retrieval_js_1.beliefWalkTerminal)(db, idByKey.get(members[0]) ?? "");
531
- const terminalSurfaced = recall.some((r) => r.id === walkTerminalId);
532
- classResolved = terminalSurfaced && competing.length === 0;
533
- notes =
534
- `terminal ${terminal} live=${terminalLive}; non-terminals demoted/absorbed=${nonTerminalsGone}; ` +
535
- `walk terminal ${keyById.get(walkTerminalId) ?? "?"} surfaced=${terminalSurfaced}; superseded ancestors competing=${competing.length}` +
536
- (terminalLive
537
- ? ""
538
- : `; ground-truth terminal ${terminal} absorbed by the zone blend — the walk terminal is the linked chain's end`);
539
- }
540
- else {
541
- classResolved = rows.every((r) => r.resolved);
542
- notes = rows.length === 1 ? (rows[0].resolved ? "pair resolved" : "pair not resolved") : `${rows.filter((r) => r.resolved).length}/${rows.length} pairs resolved`;
543
- }
544
- classResults.push({
545
- class: cls,
546
- pairs: rows.length,
547
- detected: rows.filter((r) => r.detected).length,
548
- bound: rows.filter((r) => r.bound === true).length,
549
- resolved: rows.filter((r) => r.resolved).length,
550
- classResolved,
551
- notes,
552
- });
553
- }
554
- // --- colliders: non-ground-truth pairs at/above 0.70 ---
555
- const colliders = [];
556
- for (let i = 0; i < corpus.rows.length; i++) {
557
- for (let j = i + 1; j < corpus.rows.length; j++) {
558
- const a = corpus.rows[i];
559
- const b = corpus.rows[j];
560
- const sorted = pairKey(a.key, b.key);
561
- if (declaredPairs.has(sorted))
562
- continue;
563
- const cosine = cosines.get(sorted) ?? Number.NaN;
564
- if (!Number.isFinite(cosine) || cosine < 0.7)
565
- continue;
566
- const idA = idByKey.get(a.key) ?? "";
567
- const idB = idByKey.get(b.key) ?? "";
568
- const [olderKey, newerKey] = a.createdAt <= b.createdAt ? [a.key, b.key] : [b.key, a.key];
569
- colliders.push({
570
- aKey: a.key,
571
- bKey: b.key,
572
- cosine,
573
- verdictCalled: judge.verdictCalls.some((c) => (c.olderKey === olderKey && c.newerKey === newerKey) ||
574
- (c.olderKey === newerKey && c.newerKey === olderKey)),
575
- merged: dedupLogBetween(db, idA, idB) !== null,
576
- bothLive: liveInRecall(probe(db, idA)) && liveInRecall(probe(db, idB)),
577
- });
578
- }
579
- }
580
- return { pairResults, classResults, colliders, stageReport, zonePlanBefore, blendPredictions };
581
- }
582
- finally {
583
- db.close();
584
- }
585
- }
586
- // ---------------------------------------------------------------------------
587
- // Report rendering (markdown — issue-comment shaped)
588
- // ---------------------------------------------------------------------------
589
- function yn(v) {
590
- return v === null ? "n/a" : v ? "YES" : "no";
591
- }
592
- function renderPlantedReport(args) {
593
- const { result } = args;
594
- const L = [];
595
- L.push("# Planted-Pairs Resolution Gate (#393 increment A)\n");
596
- L.push(`Corpus: ${args.corpusLabel} \nGenerated: ${args.generatedAt} \nEmbedder: ${args.embedderLabel} \n` +
597
- `Clock: ${args.clock} (#458) \n` +
598
- `Judge: ground-truth stub (perfect-judge semantics — machinery isolation, per the refine Q3 ruling). \n` +
599
- `Floors: correctionMinSimilarity 0.75 (judged-band floor) · dedupAutoMergeThreshold 0.92 (zone ceiling).\n`);
600
- L.push("## Gate — per planted pair\n");
601
- L.push("| class | pair | truth | cosine | band | detected | source | bound | resolved | notes |");
602
- L.push("|---|---|---|---|---|---|---|---|---|---|");
603
- for (const r of result.pairResults) {
604
- L.push(`| ${r.class} | ${r.olderKey}→${r.newerKey} | ${r.relation} | ${r.cosine.toFixed(4)} | ` +
605
- `${r.inBand ? "in" : "**OUT**"} | ${yn(r.detected)} | ${r.detectionSource || "-"} | ${yn(r.bound)} | ` +
606
- `${yn(r.resolved)} | ${r.notes} |`);
607
- }
608
- L.push("");
609
- L.push("## Gate — per class\n");
610
- L.push("| class | pairs | detected | bound | resolved | class end-state | notes |");
611
- L.push("|---|---|---|---|---|---|---|");
612
- for (const c of result.classResults) {
613
- L.push(`| ${c.class} | ${c.pairs} | ${c.detected} | ${c.bound} | ${c.resolved} | ${yn(c.classResolved)} | ${c.notes} |`);
614
- }
615
- L.push("");
616
- if (result.colliders.length > 0) {
617
- L.push("## Colliders (non-ground-truth pairs >= 0.70 cosine)\n");
618
- L.push("| pair | cosine | verdict called | merged | both live |");
619
- L.push("|---|---|---|---|---|");
620
- for (const c of result.colliders) {
621
- L.push(`| ${c.aKey} + ${c.bKey} | ${c.cosine.toFixed(4)} | ${yn(c.verdictCalled)} | ${yn(c.merged)} | ${yn(c.bothLive)} |`);
622
- }
623
- L.push("");
624
- }
625
- else {
626
- L.push("## Colliders\n\n_(no non-ground-truth pair >= 0.70 cosine)_\n");
627
- }
628
- if (result.blendPredictions.length > 0) {
629
- L.push("## Pre-stage zone-blend predictions (>= 0.92 discovery)\n");
630
- for (const b of result.blendPredictions) {
631
- L.push(`- ${b.pair} — canonical survives: **${b.canonicalKey}**, loser absorbed: ${b.loserKey}`);
632
- }
633
- L.push("");
634
- }
635
- const s = result.stageReport;
636
- L.push("## Stage report (production `stageReconsolidation` output)\n");
637
- L.push(`- scanned ${s.scanned}, pairs_discovered ${s.pairs_discovered} (unlinked ${s.pairs_discovered_unlinked}), ` +
638
- `pairs_evaluated ${s.pairs_evaluated}\n` +
639
- `- scout (#393 B): scanned ${s.scout_scanned}, correction_shaped ${s.scout_correction_shaped}, ` +
640
- `candidates_found ${s.scout_candidates_found}\n` +
641
- `- verdicts applied: rewritten ${s.rewritten} (absorbed ${s.absorbed}, kept ${s.kept_linked}), ` +
642
- `marked_superseded ${s.marked_superseded}, marked_retracted ${s.marked_retracted}, ` +
643
- `merge_pairs_applied ${s.merge_pairs_applied}\n` +
644
- `- conflicts (guard-C): flagged ${s.conflict_flagged}, skipped ${s.conflict_skipped} ` +
645
- `(zone clusters + judged merges refused on a conflicts link)\n` +
646
- `- zone: clusters_found ${s.merges.clusters_found}, merged ${s.merges.merged_clusters}, ` +
647
- `losers_merged ${s.merges.losers_merged}, project_skipped ${s.merges.skipped_project_mismatch}, ` +
648
- `conflict_skipped ${s.merges.skipped_conflict}\n` +
649
- `- skipped: infra ${s.skipped_infra}, idempotent ${s.skipped_idempotent}, above_ceiling ${s.skipped_above_ceiling}\n`);
650
- return L.join("\n");
651
- }