pi-mega-compact 0.20.6 → 0.20.8

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 (117) hide show
  1. package/dist/config/vector-cortex.js +12 -0
  2. package/dist/config.js +1 -1
  3. package/dist/extensions/dashboard-server/api-contracts/vector-cortex-heal.js +11 -0
  4. package/dist/extensions/dashboard-server/route-dispatch.js +102 -0
  5. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +3 -32
  6. package/dist/extensions/dashboard-server/routes-rag-settings-types.js +11 -0
  7. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +44 -0
  8. package/dist/extensions/dashboard-server/routes-vector-cortex-heal.js +47 -0
  9. package/dist/extensions/dashboard-server/routes-vector-cortex.js +1 -0
  10. package/dist/extensions/dashboard-server/routes.js +1 -1
  11. package/dist/extensions/dashboard-server/server.js +3 -82
  12. package/dist/src/config/dedup.js +1 -0
  13. package/dist/src/config/vector-cortex.js +12 -0
  14. package/dist/src/config.js +1 -1
  15. package/dist/src/dedup-audit.test-helpers.js +43 -0
  16. package/dist/src/monitoring.js +9 -0
  17. package/dist/src/vector-cortex/heal/_acceptance-fixture.js +56 -0
  18. package/dist/src/vector-cortex/heal/closure-metrics.js +66 -0
  19. package/dist/src/vector-cortex/heal/closure-opt.js +242 -0
  20. package/dist/src/vector-cortex/heal/emit.js +69 -0
  21. package/dist/src/vector-cortex/heal/proof.js +210 -0
  22. package/dist/src/vector-cortex/heal/types.js +42 -0
  23. package/dist/src/vectorStore/add-l0.js +108 -0
  24. package/dist/src/vectorStore/add-l1.js +35 -0
  25. package/dist/src/vectorStore/add.js +229 -0
  26. package/dist/src/vectorStore/class.js +15 -291
  27. package/dist/src/vectorStore/dedup-audit.js +104 -0
  28. package/dist/src/vectorStore.js +1 -0
  29. package/dist/vector-cortex/heal/_acceptance-fixture.js +56 -0
  30. package/dist/vector-cortex/heal/closure-metrics.js +66 -0
  31. package/dist/vector-cortex/heal/closure-opt.js +242 -0
  32. package/dist/vector-cortex/heal/emit.js +69 -0
  33. package/dist/vector-cortex/heal/proof.js +210 -0
  34. package/dist/vector-cortex/heal/types.js +42 -0
  35. package/extensions/dashboard-client/dist/assets/{AreaChart-DqTbaSRA.js → AreaChart-5yxaI8IO.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{AreaChart-DqTbaSRA.js.map → AreaChart-5yxaI8IO.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/{BarChart-BfNBeOvF.js → BarChart-BoHhCqH-.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{BarChart-BfNBeOvF.js.map → BarChart-BoHhCqH-.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{CacheTab-C9SOMYB5.js → CacheTab-Di6L3CsF.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{CacheTab-C9SOMYB5.js.map → CacheTab-Di6L3CsF.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{EventsTab-GsMKP10_.js → EventsTab-Cdff3h6s.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{EventsTab-GsMKP10_.js.map → EventsTab-Cdff3h6s.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{HealthTab-DAOCXwRP.js → HealthTab-MthYYVLw.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{HealthTab-DAOCXwRP.js.map → HealthTab-MthYYVLw.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BtBeMkB5.js → MaintenanceTab-D9G4ntsG.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BtBeMkB5.js.map → MaintenanceTab-D9G4ntsG.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DzndtgkZ.js → MemoryMapTab-CN8Lr5Qf.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DzndtgkZ.js.map → MemoryMapTab-CN8Lr5Qf.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/{MetricsTab-DyxJ4eb8.js → MetricsTab-CgO32kSo.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{MetricsTab-DyxJ4eb8.js.map → MetricsTab-CgO32kSo.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/assets/{OverviewTab-DCiEdVWR.js → OverviewTab-Cb4kR0h1.js} +2 -2
  52. package/extensions/dashboard-client/dist/assets/{OverviewTab-DCiEdVWR.js.map → OverviewTab-Cb4kR0h1.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{ReposTab-B5SIqVHA.js → ReposTab-Dt1FGnMR.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{ReposTab-B5SIqVHA.js.map → ReposTab-Dt1FGnMR.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/assets/{SessionsTab-DLWFJ9xI.js → SessionsTab-D7D2RW6U.js} +2 -2
  56. package/extensions/dashboard-client/dist/assets/{SessionsTab-DLWFJ9xI.js.map → SessionsTab-D7D2RW6U.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/{SetupTab-CvGXZyej.js → SetupTab-CgV5xCzz.js} +2 -2
  58. package/extensions/dashboard-client/dist/assets/{SetupTab-CvGXZyej.js.map → SetupTab-CgV5xCzz.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-wZuQGxLS.js → TimeSavedCard-CCNuJjyO.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-wZuQGxLS.js.map → TimeSavedCard-CCNuJjyO.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{TurnsTab-B7aOejch.js → TurnsTab-Cv1OCttJ.js} +2 -2
  62. package/extensions/dashboard-client/dist/assets/{TurnsTab-B7aOejch.js.map → TurnsTab-Cv1OCttJ.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DTabQo6h.js +2 -0
  64. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DTabQo6h.js.map +1 -0
  65. package/extensions/dashboard-client/dist/assets/{WikiTab-DhWHjAtt.js → WikiTab-B48QnQrK.js} +2 -2
  66. package/extensions/dashboard-client/dist/assets/{WikiTab-DhWHjAtt.js.map → WikiTab-B48QnQrK.js.map} +1 -1
  67. package/extensions/dashboard-client/dist/assets/{button-BrC-W87h.js → button-BBQl2MiF.js} +2 -2
  68. package/extensions/dashboard-client/dist/assets/{button-BrC-W87h.js.map → button-BBQl2MiF.js.map} +1 -1
  69. package/extensions/dashboard-client/dist/assets/{card-0rgfRgr3.js → card-BYbefUur.js} +2 -2
  70. package/extensions/dashboard-client/dist/assets/{card-0rgfRgr3.js.map → card-BYbefUur.js.map} +1 -1
  71. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DRresOQF.js → generateCategoricalChart-swm0YZe0.js} +2 -2
  72. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DRresOQF.js.map → generateCategoricalChart-swm0YZe0.js.map} +1 -1
  73. package/extensions/dashboard-client/dist/assets/{index-DohmtZWL.js → index-DeIky7xm.js} +3 -3
  74. package/extensions/dashboard-client/dist/assets/{index-DohmtZWL.js.map → index-DeIky7xm.js.map} +1 -1
  75. package/extensions/dashboard-client/dist/assets/{switch-Bs8g_zB5.js → switch-CctU78ww.js} +2 -2
  76. package/extensions/dashboard-client/dist/assets/{switch-Bs8g_zB5.js.map → switch-CctU78ww.js.map} +1 -1
  77. package/extensions/dashboard-client/dist/assets/{toggle-DVVauIo8.js → toggle-BRCNCoiI.js} +2 -2
  78. package/extensions/dashboard-client/dist/assets/{toggle-DVVauIo8.js.map → toggle-BRCNCoiI.js.map} +1 -1
  79. package/extensions/dashboard-client/dist/assets/{useSSE-DxH1PTAR.js → useSSE-WfOiWDH_.js} +2 -2
  80. package/extensions/dashboard-client/dist/assets/{useSSE-DxH1PTAR.js.map → useSSE-WfOiWDH_.js.map} +1 -1
  81. package/extensions/dashboard-client/dist/index.html +1 -1
  82. package/extensions/dashboard-client/src/api/vector-cortex.ts +9 -0
  83. package/extensions/dashboard-client/src/tabs/VectorCortexClosureCard.tsx +46 -0
  84. package/extensions/dashboard-client/src/tabs/VectorCortexLedgerCard.tsx +80 -0
  85. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +12 -164
  86. package/extensions/dashboard-client/src/tabs/VectorCortexTopologyCard.tsx +131 -0
  87. package/extensions/dashboard-client/src/types/vector-cortex.ts +14 -0
  88. package/extensions/dashboard-server/api-contracts/vector-cortex-heal.ts +43 -0
  89. package/extensions/dashboard-server/api-contracts/vector-cortex.ts +2 -0
  90. package/extensions/dashboard-server/route-dispatch.ts +112 -0
  91. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +11 -135
  92. package/extensions/dashboard-server/routes-rag-settings-types.ts +34 -0
  93. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +148 -0
  94. package/extensions/dashboard-server/routes-vector-cortex-heal.ts +56 -0
  95. package/extensions/dashboard-server/routes-vector-cortex.ts +1 -0
  96. package/extensions/dashboard-server/routes.ts +1 -0
  97. package/extensions/dashboard-server/server.ts +3 -86
  98. package/package.json +1 -1
  99. package/src/config/dedup.ts +9 -0
  100. package/src/config/vector-cortex.ts +13 -0
  101. package/src/config.ts +1 -0
  102. package/src/dedup-audit.test-helpers.ts +49 -0
  103. package/src/monitoring.ts +10 -0
  104. package/src/vector-cortex/heal/_acceptance-fixture.ts +108 -0
  105. package/src/vector-cortex/heal/closure-metrics.ts +73 -0
  106. package/src/vector-cortex/heal/closure-opt.ts +266 -0
  107. package/src/vector-cortex/heal/emit.ts +93 -0
  108. package/src/vector-cortex/heal/proof.ts +234 -0
  109. package/src/vector-cortex/heal/types.ts +226 -0
  110. package/src/vectorStore/add-l0.ts +152 -0
  111. package/src/vectorStore/add-l1.ts +57 -0
  112. package/src/vectorStore/add.ts +269 -0
  113. package/src/vectorStore/class.ts +17 -327
  114. package/src/vectorStore/dedup-audit.ts +199 -0
  115. package/src/vectorStore.ts +1 -0
  116. package/extensions/dashboard-client/dist/assets/VectorCortexTab-eHVt9yDx.js +0 -2
  117. package/extensions/dashboard-client/dist/assets/VectorCortexTab-eHVt9yDx.js.map +0 -1
@@ -0,0 +1,234 @@
1
+ /**
2
+ * vector-cortex/heal/proof.ts — VC6A proof verifier (task 4).
3
+ *
4
+ * The optimizer (`closure-opt.ts`) is PURE and cheap, but a proof is cheap to
5
+ * LIE about: a buggy or tampered producer could ship a proof that removes an
6
+ * edge the conservative closure actually needs. The verifier does not trust the
7
+ * proof — it REPLAYS it against the conservative oracle from scratch.
8
+ *
9
+ * REPLAY. From the conservative `ClosureResult` we rebuild the requirement
10
+ * adjacency (`to ⇒ from` for `depends`, both directions for `tool-pair`) over
11
+ * the in-selection edges — exactly the relation `closure-opt` reduced over.
12
+ * For every row the proof claims to REMOVE, the verifier independently confirms
13
+ * the same alternate path exists. For every row it claims to RETAIN, it confirms
14
+ * the protection reason holds (or that no alternate path exists). Any row that
15
+ * fails its own predicate is a witness violation.
16
+ *
17
+ * SELECTED-SET DIVERGENCE (the cardinal sin). The proof must contain the SAME
18
+ * selected set as the conservative oracle, byte for byte. Closure optimization
19
+ * is allowed to make the plan cheaper, never smaller — so if the proof's
20
+ * `selected` differs from the oracle's, that is `HEAL_PROOF_SET_MISMATCH` and the
21
+ * repaired triad must fall back to mode B (the conservative closure) and
22
+ * ultimately C (VC5C's legacy prompt) if even that fails.
23
+ *
24
+ * PURE / DETERMINISTIC / LOCAL, like everything in `heal/`: no storage, no
25
+ * console, no clock, no network (PREVENT-PI-004 / PREVENT-011). The verifier
26
+ * reads only types and the two ClosureEdge/ClosureGraph/ClosureResult shapes.
27
+ */
28
+
29
+ import type { ClosureGraph, ClosureResult } from "../reconstruct/types.js";
30
+ import type {
31
+ ClosureProofRow,
32
+ ClosureProofV2,
33
+ HealFailureCode,
34
+ HealMode,
35
+ HealTriadOutcome,
36
+ ProofVerification,
37
+ } from "./types.js";
38
+
39
+ /** Bytewise id comparator — matches `closure-opt.ts` / `reconstruct/closure.ts`. */
40
+ function byBytes(a: string, b: string): number {
41
+ return a < b ? -1 : a > b ? 1 : 0;
42
+ }
43
+
44
+ /** Requirement adjacency over an edge list, identical to `closure-opt`. */
45
+ function requirementAdjacency(edges: readonly ClosureProofRow[]): Map<string, string[]> {
46
+ const adj = new Map<string, string[]>();
47
+ const add = (key: string, value: string): void => {
48
+ const list = adj.get(key);
49
+ if (list === undefined) adj.set(key, [value]);
50
+ else list.push(value);
51
+ };
52
+ for (const row of edges) {
53
+ if (row.kind === "contradicts") continue; // not a traversal edge
54
+ if (row.kind === "depends") add(row.to, row.from);
55
+ else if (row.kind === "tool-pair") {
56
+ add(row.to, row.from);
57
+ add(row.from, row.to);
58
+ }
59
+ }
60
+ for (const list of adj.values()) list.sort(byBytes);
61
+ return adj;
62
+ }
63
+
64
+ /** Does a detour `start ⇒ via ⇒ ... ⇒ target` exist, excluding the direct hop? */
65
+ function alternatePathVia(
66
+ adj: ReadonlyMap<string, string[]>,
67
+ start: string,
68
+ target: string,
69
+ ): string | null {
70
+ for (const hop of (adj.get(start) ?? []).filter((n) => n !== target)) {
71
+ const seen = new Set<string>([start]);
72
+ const worklist: string[] = [hop];
73
+ while (worklist.length > 0) {
74
+ worklist.sort(byBytes);
75
+ const current = worklist.shift();
76
+ if (current === undefined) break;
77
+ if (current === target) return hop;
78
+ if (seen.has(current)) continue;
79
+ seen.add(current);
80
+ for (const next of adj.get(current) ?? []) {
81
+ if (!seen.has(next)) worklist.push(next);
82
+ }
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+
88
+ /**
89
+ * Verify a `ClosureProofV2` produced for an already-closed graph.
90
+ *
91
+ * Returns `{ ok: true, selected }` when the replay confirms the optimized plan is
92
+ * provably equivalent to the conservative closure — mode A may be used. On any
93
+ * divergence it returns `{ ok: false, codes }` listing every failure code; the
94
+ * caller routes to mode B (conservative) and, if that also fails, mode C (legacy,
95
+ * with `semanticLossStated`). The verifier is idempotent — replaying the same
96
+ * proof twice yields the same verdict, which `vc6a-acceptance.test.ts` asserts.
97
+ */
98
+ export function verifyProof(
99
+ proof: ClosureProofV2,
100
+ conservative: ClosureResult,
101
+ graph?: ClosureGraph,
102
+ ): ProofVerification {
103
+ const codes: HealFailureCode[] = [];
104
+ const inSelection = new Set(conservative.selected);
105
+ const anchors = new Set(
106
+ (graph?.nodes ?? []).filter((n) => n.anchor === true).map((n) => n.id),
107
+ );
108
+
109
+ // 1. Selected-set divergence is the hard gate (task 4: HEAL_PROOF_SET_MISMATCH).
110
+ const oracleSelected = [...conservative.selected].sort(byBytes);
111
+ const proofSelected = [...proof.selected].sort(byBytes);
112
+ const selectedMatch =
113
+ oracleSelected.length === proofSelected.length &&
114
+ oracleSelected.every((id, i) => id === proofSelected[i]);
115
+ if (!selectedMatch) codes.push("HEAL_PROOF_SET_MISMATCH");
116
+
117
+ // 2. Re-check each row against its own predicate. For a removal, the witness
118
+ // is validated against the OTHER rows (the reduced graph), so a removal
119
+ // never justifies another's witness — this catches both a missing-witness
120
+ // removal (HEAL-PROOF-003) and a protected edge that was removed
121
+ // (HEAL_PROOF_PROTECTED_REMOVED).
122
+
123
+ for (const row of proof.rows) {
124
+ // Out-of-scope rows (touch an unselected node) should never have been
125
+ // emitted; treat as a witness violation so a malformed proof fails loudly.
126
+ if (!inSelection.has(row.from) || !inSelection.has(row.to)) {
127
+ codes.push("HEAL_PROOF_WITNESS_INVALID");
128
+ continue;
129
+ }
130
+ if (row.decision === "removed") {
131
+ // A removal MUST carry a witness and the witness MUST still validate
132
+ // against the OTHER rows (the reduced graph) — never the row itself, so a
133
+ // removal never justifies another's witness (matches the optimizer's
134
+ // exclusion rule).
135
+ if (row.via === undefined) {
136
+ codes.push("HEAL_PROOF_WITNESS_INVALID");
137
+ continue;
138
+ }
139
+ const others = proof.rows.filter((r) => r !== row);
140
+ const witness = alternatePathVia(requirementAdjacency(others), row.to, row.from);
141
+ if (witness === null) {
142
+ codes.push("HEAL_PROOF_WITNESS_INVALID");
143
+ continue;
144
+ }
145
+ if (anchors.has(row.from) || anchors.has(row.to)) {
146
+ // Topology says removable, but an anchor edge is NEVER removable.
147
+ codes.push("HEAL_PROOF_PROTECTED_REMOVED");
148
+ continue;
149
+ }
150
+ if (row.kind === "tool-pair" || row.kind === "contradicts") {
151
+ // Both are protected regardless of topology.
152
+ codes.push("HEAL_PROOF_PROTECTED_REMOVED");
153
+ }
154
+ } else {
155
+ // A retained plain depends edge that claims `no-alternate-path` but
156
+ // actually HAS one (through the other rows) is an internal inconsistency:
157
+ // the optimizer kept a removable edge without removing it. The proof is
158
+ // internally contradictory, so it fails replay.
159
+ if (row.kind === "depends" && row.reason === "no-alternate-path") {
160
+ const others = proof.rows.filter((r) => r !== row);
161
+ const hasPath = alternatePathVia(requirementAdjacency(others), row.to, row.from);
162
+ if (hasPath !== null) codes.push("HEAL_PROOF_WITNESS_INVALID");
163
+ }
164
+ }
165
+ }
166
+
167
+ // 3. Completeness: every in-selection edge the optimizer considered must appear
168
+ // in the proof. A dropped row (the unique-injection test drops one) surfaces
169
+ // as HEAL_PROOF_INCOMPLETE even if the remaining rows replay fine — the proof
170
+ // must account for EVERY considered edge. `conservativeTraversals` is the
171
+ // optimizer's own record of how many edges it fed through, so `rows.length`
172
+ // must equal it exactly.
173
+ if (proof.rows.length < proof.conservativeTraversals) {
174
+ codes.push("HEAL_PROOF_INCOMPLETE");
175
+ }
176
+
177
+ if (codes.length === 0) {
178
+ return { ok: true, selected: proofSelected };
179
+ }
180
+ return { ok: false, codes: [...new Set(codes)] };
181
+ }
182
+
183
+ /**
184
+ * Drive the A/B/C resilient triad for one closure (task 5 wiring; VC6A exposes
185
+ * the decision; the actual selection/budget work is VC4C/VC5A respectively).
186
+ *
187
+ * - A: the optimized proof verified cleanly → ship the optimized plan.
188
+ * - B: proof rejected → fall back to the conservative VC4C closure (no opt).
189
+ * - C: B also failed (should not happen for a sound conservative closure) →
190
+ * state the legacy prompt path with `semanticLossStated`.
191
+ *
192
+ * This is PURE: it returns the verdict and which mode to use; it does not emit,
193
+ * store, or touch the network. The emit seam (`emit.ts`) consumes the outcome.
194
+ */
195
+ export function selectHealMode(
196
+ proof: ClosureProofV2,
197
+ conservative: ClosureResult,
198
+ graph?: ClosureGraph,
199
+ ): HealTriadOutcome {
200
+ const verification = verifyProof(proof, conservative, graph);
201
+ if (verification.ok) {
202
+ return {
203
+ mode: "A" as HealMode,
204
+ proof,
205
+ selected: [...proof.selected],
206
+ codes: [],
207
+ semanticLossStated: false,
208
+ };
209
+ }
210
+ // Mode B: conservative closure is sound by construction (it is the oracle the
211
+ // proof was measured against). Use it directly.
212
+ return {
213
+ mode: "B" as HealMode,
214
+ proof: null,
215
+ selected: [...conservative.selected],
216
+ codes: verification.codes,
217
+ semanticLossStated: false,
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Convenience for the rare B-failure path: if even the conservative oracle's
223
+ * selection cannot be honored, state the legacy prompt and MARK semantic loss.
224
+ * Mode C is the last resort and the only place semantic loss is ever admitted.
225
+ */
226
+ export function legacyFallback(conservative: ClosureResult, reason: HealFailureCode): HealTriadOutcome {
227
+ return {
228
+ mode: "C" as HealMode,
229
+ proof: null,
230
+ selected: [...conservative.selected],
231
+ codes: [reason, "HEAL_CLOSURE_REJECTED"],
232
+ semanticLossStated: true,
233
+ };
234
+ }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * vector-cortex/heal/types.ts — VC6A closure-optimization contract.
3
+ *
4
+ * Owns `ClosureProofV2` and `RestoreHintV1`.
5
+ *
6
+ * VC4C already performs the MANDATORY conservative closure (CONTRACTS §plan and
7
+ * closure): it recursively adds every `depends`/tool-pair predecessor to a fixed
8
+ * point, resolves contradictions, and preserves the anchor floor. VC6A does NOT
9
+ * re-close and does NOT change WHICH NODES ARE SELECTED — "VC6 only optimizes
10
+ * restoration/self-healing". It optimizes the PROOF EDGES of that already-closed
11
+ * graph by transitive reduction: an edge `a -> c` whose requirement is already
12
+ * carried by a path `a -> b -> c` is redundant and can be dropped from the
13
+ * traversal plan without changing the closed set.
14
+ *
15
+ * The optimization is therefore a claim, and a claim needs a receipt:
16
+ * `ClosureProofV2` records EVERY considered edge — retained or removed — with
17
+ * the reason. `proof.ts` replays those rows against the conservative VC4C oracle
18
+ * and rejects the optimization if the selected set diverges
19
+ * (`HEAL_PROOF_SET_MISMATCH`) or if a considered edge has no proof row
20
+ * (`HEAL_PROOF_INCOMPLETE`).
21
+ *
22
+ * PROTECTED EDGES ARE NEVER REMOVED. A tool-pair edge (PREVENT-PI-002), an edge
23
+ * touching an anchor node (PREVENT-PI-001's anchor floor), a
24
+ * contradiction-resolution edge, and a SOLE dependency edge (the only edge that
25
+ * pulls its prerequisite in) are all retained unconditionally, even when an
26
+ * alternate path exists. Only a redundant `depends` edge may go.
27
+ *
28
+ * Pure types + registered conformance IDs: no storage, no console, no network
29
+ * (PREVENT-PI-004 / PREVENT-011).
30
+ */
31
+
32
+ import type { ClosureEdge, ClosureEdgeKind } from "../reconstruct/types.js";
33
+
34
+ /**
35
+ * Why an edge survived the reduction. Ordered from strongest guarantee to
36
+ * weakest:
37
+ *
38
+ * - `tool-pair` — a tool call/result pair is atomic and never split
39
+ * (PREVENT-PI-002), so its edge is structural, not
40
+ * merely a traversal hint;
41
+ * - `anchor` — an endpoint is in the preserved anchor floor
42
+ * (PREVENT-PI-001), so the edge documents a floor
43
+ * obligation and is never elided;
44
+ * - `contradiction` — a `contradicts` edge participates in resolution; the
45
+ * resolver reads it directly, so removing it would
46
+ * change the resolved set, not just the traversal;
47
+ * - `sole-dependency`— the ONLY edge requiring this prerequisite; removing it
48
+ * would drop the prerequisite from the closure;
49
+ * - `no-alternate-path` — a plain `depends` edge with no `a -> ... -> c`
50
+ * detour, so it still carries its own requirement.
51
+ */
52
+ export type RetainReason =
53
+ | "tool-pair"
54
+ | "anchor"
55
+ | "contradiction"
56
+ | "sole-dependency"
57
+ | "no-alternate-path";
58
+
59
+ /**
60
+ * Why an edge was removed. Exactly one reason exists today — a redundant
61
+ * `depends` edge shadowed by a longer path — and it is spelled out rather than
62
+ * implied so a proof row can never be "removed, reason unstated"
63
+ * (HEAL-PROOF-003).
64
+ */
65
+ export type RemoveReason = "transitively-implied";
66
+
67
+ /**
68
+ * One proof row: an edge the optimizer CONSIDERED, and what it decided.
69
+ *
70
+ * Every considered edge produces exactly one row — including retained ones. A
71
+ * proof that lists only removals cannot be replayed against the oracle, because
72
+ * the verifier could not distinguish "retained deliberately" from "never
73
+ * examined" (the `HEAL_PROOF_INCOMPLETE` injection).
74
+ *
75
+ * `via` names the intermediate vertex of the witnessing path `from -> via -> to`
76
+ * for a removal. It is REQUIRED on a removal row (the witness IS the reason) and
77
+ * absent on a retention row.
78
+ */
79
+ export interface ClosureProofRow {
80
+ readonly from: string;
81
+ readonly to: string;
82
+ readonly kind: ClosureEdgeKind;
83
+ readonly decision: "retained" | "removed";
84
+ /** The stated reason. A removal without a reason fails verification. */
85
+ readonly reason: RetainReason | RemoveReason;
86
+ /** Witness vertex of the implying path (removals only). */
87
+ readonly via?: string;
88
+ }
89
+
90
+ /**
91
+ * The optimization receipt (`ClosureProofV2`).
92
+ *
93
+ * `selected` is copied UNCHANGED from the conservative VC4C closure — the
94
+ * optimizer never edits it, and the verifier re-derives it independently to
95
+ * prove that. `retainedEdges`/`removedEdges` are the optimized traversal plan;
96
+ * `rows` is the full considered-edge ledger, sorted deterministically.
97
+ *
98
+ * `conservativeTraversals` / `optimizedTraversals` are the edge-walk counts of
99
+ * the conservative and reduced plans; the sprint's acceptance bar is a >=20%
100
+ * median reduction across the fixture corpus, measured from these two numbers.
101
+ */
102
+ export interface ClosureProofV2 {
103
+ readonly schema: "closure-proof-v2";
104
+ readonly sessionId: string;
105
+ /** The closed selection, byte-identical to the conservative VC4C result. */
106
+ readonly selected: readonly string[];
107
+ /** Edges kept in the optimized traversal plan (sorted). */
108
+ readonly retainedEdges: readonly ClosureEdge[];
109
+ /** Edges dropped as transitively implied (sorted). */
110
+ readonly removedEdges: readonly ClosureEdge[];
111
+ /** One row per CONSIDERED edge — retained and removed alike (sorted). */
112
+ readonly rows: readonly ClosureProofRow[];
113
+ /** Edge walks the conservative plan performs. */
114
+ readonly conservativeTraversals: number;
115
+ /** Edge walks the optimized plan performs. */
116
+ readonly optimizedTraversals: number;
117
+ }
118
+
119
+ /**
120
+ * `RestoreHintV1` — the reader-only hint VC6B consumes to fetch missing sources.
121
+ *
122
+ * A hint names a node the optimized plan still needs and the CHEAPEST retained
123
+ * edge that reaches it (`viaEdgeFrom`), so the restorer can walk one edge rather
124
+ * than re-deriving the closure. `protectedNode` marks a node whose edge set was
125
+ * retained for a hard-safety reason (tool pair / anchor), telling VC6B to treat
126
+ * it as non-negotiable.
127
+ *
128
+ * Reader-only by contract: node IDENTITY plus the traversal hint, NEVER source
129
+ * bytes or prompt text (SECURITY_PRIVACY — the exact ledger is not training
130
+ * data and is never rendered through a diagnostic surface).
131
+ */
132
+ export interface RestoreHintV1 {
133
+ readonly schema: "restore-hint-v1";
134
+ readonly nodeId: string;
135
+ /** The retained edge's prerequisite endpoint, if the node has one. */
136
+ readonly viaEdgeFrom?: string;
137
+ /** True when the node is anchor-floor or tool-pair protected. */
138
+ readonly protectedNode: boolean;
139
+ /** Deterministic reason label mirroring the retention reason. */
140
+ readonly reason: RetainReason;
141
+ }
142
+
143
+ /** VC6A failure codes (registered HEAL codes). */
144
+ export type HealFailureCode =
145
+ /** The replayed selected set diverges from the conservative oracle. */
146
+ | "HEAL_PROOF_SET_MISMATCH"
147
+ /** A considered edge has no proof row (or a removal states no reason). */
148
+ | "HEAL_PROOF_INCOMPLETE"
149
+ /** A proof row removes an edge that is protected and must be retained. */
150
+ | "HEAL_PROOF_PROTECTED_REMOVED"
151
+ /** A removal row's witness path `from -> via -> to` does not exist. */
152
+ | "HEAL_PROOF_WITNESS_INVALID"
153
+ /** The underlying conservative closure itself failed — nothing to optimize. */
154
+ | "HEAL_CLOSURE_REJECTED";
155
+
156
+ /**
157
+ * Verifier verdict. `ok:true` means the optimized plan is provably equivalent to
158
+ * the conservative closure and mode A may be used. `ok:false` routes to mode B
159
+ * (the conservative VC4C closure, unoptimized) — never to a partially reduced
160
+ * plan.
161
+ */
162
+ export type ProofVerification =
163
+ | { readonly ok: true; readonly selected: readonly string[] }
164
+ | { readonly ok: false; readonly codes: readonly HealFailureCode[] };
165
+
166
+ /**
167
+ * The triad mode VC6A selects (TRIAD_RESILIENCE).
168
+ *
169
+ * A = the optimized closure whose proof verified;
170
+ * B = the VC4C conservative closure, forced by proof rejection — an
171
+ * INDEPENDENT algorithm (fixed-point worklist, no reduction) reached by a
172
+ * different code path in `reconstruct/closure.ts`;
173
+ * C = the legacy prompt, forced when BOTH closure paths fail (an unresolved
174
+ * contradiction or a structurally invalid graph), stating its loss of old
175
+ * semantic context.
176
+ */
177
+ export type HealMode = "A" | "B" | "C";
178
+
179
+ /** The outcome of the VC6A triad selection. */
180
+ export interface HealTriadOutcome {
181
+ readonly mode: HealMode;
182
+ /** The verified optimized proof (mode A only). */
183
+ readonly proof: ClosureProofV2 | null;
184
+ /** The selected node set actually used (empty only in mode C). */
185
+ readonly selected: readonly string[];
186
+ readonly codes: readonly HealFailureCode[];
187
+ /** Set only in mode C (TRIAD_RESILIENCE — "C states its semantic loss"). */
188
+ readonly semanticLossStated: boolean;
189
+ }
190
+
191
+ /** The two structured events the VC6A reporter emits. */
192
+ export type HealEventName =
193
+ | "vector_cortex_closure_optimized"
194
+ | "vector_cortex_closure_proof_rejected";
195
+
196
+ /**
197
+ * Aggregate-only closure-proof metrics for the dashboard (counts only, never
198
+ * node ids, edges, or source payloads).
199
+ */
200
+ export interface HealMetricsV1 {
201
+ readonly optimizations: number;
202
+ readonly proofRejections: number;
203
+ readonly retainedEdgeTotal: number;
204
+ readonly removedEdgeTotal: number;
205
+ readonly conservativeTraversalTotal: number;
206
+ readonly optimizedTraversalTotal: number;
207
+ }
208
+
209
+ /**
210
+ * Registered HEAL conformance ID range (HEAL-001..015). The acceptance test
211
+ * reads these rows from the v2 manifest and asserts each returns its manifest
212
+ * `ok`/`code`.
213
+ */
214
+ export const HEAL_IDS: readonly string[] = Array.from(
215
+ { length: 15 },
216
+ (_v, i) => `HEAL-${String(i + 1).padStart(3, "0")}`,
217
+ );
218
+
219
+ /** Named VC6A conformance assertions (the sprint's headline rows). */
220
+ export const HEAL_NAMED_IDS = [
221
+ "HEAL-REDUCE-001",
222
+ "HEAL-PROTECT-002",
223
+ "HEAL-PROOF-003",
224
+ ] as const;
225
+
226
+ export type { ClosureEdge, ClosureEdgeKind };
@@ -0,0 +1,152 @@
1
+ /**
2
+ * add-l0.ts — the L0 exact-match tier of the dedup cascade.
3
+ *
4
+ * L0 is three exact-hash probes sharing one enable flag and one MARK_ONLY flag:
5
+ * 1. contentHash — normalized dual-hash, bloom-accelerated (Sprint 9/10)
6
+ * 2. regionHash — the legacy pre-normalization hash (backward-compat)
7
+ * 3. summaryHash — same-topic incremental compactions
8
+ *
9
+ * Split out of add.ts so both files stay under the 300-line src/ soft limit.
10
+ * The tier is a pure decision: it either produces a collapse (returning the
11
+ * matched result) or reports the fall-through, and it never writes a new
12
+ * checkpoint — that stays in add.ts.
13
+ *
14
+ * All three probes are hash comparisons, so none of them computes a similarity
15
+ * score and none is invented for the audit trail.
16
+ */
17
+ import { createHash } from "node:crypto";
18
+ import type { StoredCheckpoint } from "../store.js";
19
+ import type { ContentDigest } from "../dedup/digest.js";
20
+ import { upsertCheckpoint, addTokensSaved, bumpDedupStats } from "../store/sqlite.js";
21
+ import type { BloomFilter } from "../store/bloom.js";
22
+ import type { DedupAuditRecorder } from "./dedup-audit.js";
23
+ import type { AddInput, AddResult } from "./types.js";
24
+ import type { VectorStore } from "./class.js";
25
+
26
+ /** Everything the L0 tier needs from the enclosing add() cascade. */
27
+ export interface L0Context {
28
+ store: VectorStore;
29
+ input: AddInput;
30
+ /** Checkpoints already stored for this session. */
31
+ all: StoredCheckpoint[];
32
+ /** Normalized content digest of the incoming region. */
33
+ digest: ContentDigest;
34
+ /** Legacy region hash of the incoming region. */
35
+ regionHash: string;
36
+ /** SHA-256 of the topic summary, when one was supplied. */
37
+ summaryHash?: string;
38
+ /** Tokens the original region occupied (the dedup "saved" base). */
39
+ origTokens: number;
40
+ /** Warm bloom accelerator for the content-hash probe. */
41
+ bloom: BloomFilter;
42
+ /** Cascade start time, for the monitoring latency figure. */
43
+ t0: number;
44
+ audit: DedupAuditRecorder;
45
+ }
46
+
47
+ /**
48
+ * Outcome of the L0 tier.
49
+ *
50
+ * `result` set → the tier collapsed the region and add() returns immediately.
51
+ * `markOnly` set → a probe matched but MARK_ONLY policy said store anyway, so
52
+ * the cascade continues and records the mark at the end.
53
+ */
54
+ export interface L0Outcome {
55
+ result?: AddResult;
56
+ markOnly: "L0" | null;
57
+ }
58
+
59
+ /** Run the L0 exact-match tier. */
60
+ export function runL0Tier(ctx: L0Context): L0Outcome {
61
+ const { store, input, all, digest, regionHash, origTokens, bloom, t0, audit } = ctx;
62
+ const cfg = store.cfg;
63
+ const onTier = input.onTier;
64
+ let markOnly: "L0" | null = null;
65
+
66
+ // 0. Content-hash dedup (Sprint 9) — catches identical content arriving under
67
+ // different regionText. Normalization handles case/whitespace/ANSI so
68
+ // variants collapse to one row. Dual-hash guards a single-hash collision.
69
+ // Sprint 10: bloom is the accelerator — a miss means "definitely new" and
70
+ // skips the scan; a hit is only a candidate, confirmed against `all` here.
71
+ // MARK_ONLY_L0 records the decision but does not collapse.
72
+ onTier?.({ tier: "L0", status: "scanning" });
73
+ if (cfg.L0_ENABLED && bloom.maybeHas(digest.contentHash)) {
74
+ const contentMatch = all.find(
75
+ (cp) =>
76
+ cp.contentHash === digest.contentHash &&
77
+ cp.contentHash2 === digest.contentHash2,
78
+ );
79
+ if (contentMatch) {
80
+ if (cfg.MARK_ONLY_L0) {
81
+ markOnly = "L0"; // Record-but-don't-collapse: fall through.
82
+ } else {
83
+ contentMatch.timestamp = input.timestamp;
84
+ upsertCheckpoint(contentMatch, store.stateDir);
85
+ bumpDedupStats(true, store.stateDir);
86
+ // Deduped: whole original region discarded, nothing new stored.
87
+ addTokensSaved(origTokens, store.stateDir);
88
+ store.record("L0", "deduped", "contentHash", Date.now() - t0, 1, contentMatch.checkpointId);
89
+ audit.deduped("L0", contentMatch.checkpointId, "contentHash");
90
+ onTier?.({ tier: "L0", status: "deduped", detail: "contentHash" });
91
+ return {
92
+ result: { checkpoint: contentMatch, deduped: true, reason: "contentHash" },
93
+ markOnly,
94
+ };
95
+ }
96
+ }
97
+ }
98
+
99
+ // 1. Legacy regionHash dedup (backward-compat) — part of L0 tier gating.
100
+ if (cfg.L0_ENABLED) {
101
+ const regionMatch = all.find((cp) => cp.regionHash === regionHash);
102
+ if (regionMatch) {
103
+ if (cfg.MARK_ONLY_L0) {
104
+ markOnly = "L0"; // fall through
105
+ } else {
106
+ bumpDedupStats(true, store.stateDir);
107
+ // Deduped: whole original region discarded, nothing new stored.
108
+ addTokensSaved(origTokens, store.stateDir);
109
+ store.record("L0", "deduped", "regionHash", Date.now() - t0, 1, regionMatch.checkpointId);
110
+ audit.deduped("L0", regionMatch.checkpointId, "regionHash");
111
+ onTier?.({ tier: "L0", status: "deduped", detail: "regionHash" });
112
+ return {
113
+ result: { checkpoint: regionMatch, deduped: true, reason: "regionHash" },
114
+ markOnly,
115
+ };
116
+ }
117
+ }
118
+ }
119
+
120
+ // 2. SummaryHash dedup — catches same-topic incremental compactions.
121
+ if (ctx.summaryHash && cfg.L0_ENABLED) {
122
+ const summaryMatch = all.find((cp) => cp.summaryHash === ctx.summaryHash);
123
+ if (summaryMatch) {
124
+ if (cfg.MARK_ONLY_L0) {
125
+ markOnly = "L0"; // fall through
126
+ } else {
127
+ summaryMatch.timestamp = input.timestamp;
128
+ upsertCheckpoint(summaryMatch, store.stateDir);
129
+ bumpDedupStats(true, store.stateDir);
130
+ // Deduped: whole original region discarded, nothing new stored.
131
+ addTokensSaved(origTokens, store.stateDir);
132
+ store.record("L0", "deduped", "summaryHash", Date.now() - t0, 1, summaryMatch.checkpointId);
133
+ audit.deduped("L0", summaryMatch.checkpointId, "summaryHash");
134
+ onTier?.({ tier: "L0", status: "deduped", detail: "summaryHash" });
135
+ return {
136
+ result: { checkpoint: summaryMatch, deduped: true, reason: "summaryHash" },
137
+ markOnly,
138
+ };
139
+ }
140
+ }
141
+ }
142
+ // L0 did not collapse this region.
143
+ onTier?.({ tier: "L0", status: "passed" });
144
+ return { markOnly };
145
+ }
146
+
147
+ /** SHA-256 of the topic summary (full 64-hex; 16-hex was collision-prone). */
148
+ export function computeSummaryHash(topicSummary?: string): string | undefined {
149
+ return topicSummary
150
+ ? createHash("sha256").update(topicSummary).digest("hex")
151
+ : undefined;
152
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * add-l1.ts — the L1 MinHash/LSH near-duplicate lookup.
3
+ *
4
+ * Split out of add.ts to keep both files under the 300-line src/ soft limit.
5
+ * L1 catches the one-word edits and rewordings L0's exact hashes miss: a cheap
6
+ * LSH bucket retrieval narrows the field, then a trigram verification
7
+ * (pg_trgm-equivalent) is the final gate. The verify is boolean, so the tier
8
+ * produces no similarity score.
9
+ */
10
+ import type { StoredCheckpoint } from "../store.js";
11
+ import {
12
+ minhashSignature,
13
+ SIGNATURE_VERSION,
14
+ NUM_HASHES,
15
+ } from "../dedup/l1-minhash.js";
16
+ import { lshBands } from "../dedup/l1-lsh.js";
17
+ import { isNearDuplicate } from "../dedup/l1-verify.js";
18
+ import { lshCandidateChunks } from "../store/sqlite.js";
19
+ import type { VectorStore } from "./class.js";
20
+
21
+ export /**
22
+ * L1 near-duplicate lookup: MinHash → LSH candidate retrieval → trigram verify.
23
+ * Returns the matching checkpoint or undefined. Bounded by a 100-candidate cap
24
+ * and a 20ms verify budget (QA #7/#15) so it never hangs a large session.
25
+ */
26
+ function findL1Duplicate(
27
+ store: VectorStore,
28
+ sessionId: string,
29
+ regionText: string,
30
+ all: StoredCheckpoint[],
31
+ ): StoredCheckpoint | undefined {
32
+ if (all.length === 0) return undefined;
33
+ const sig = minhashSignature(regionText);
34
+ if (sig.length !== NUM_HASHES) return undefined;
35
+ const bands = lshBands(sig, sessionId, SIGNATURE_VERSION);
36
+ // Cheap candidate retrieval (single query, capped). Exclude nothing yet —
37
+ // the new checkpoint has no id, so pass a sentinel that never matches.
38
+ const candidateIds = lshCandidateChunks(
39
+ bands,
40
+ sessionId,
41
+ "__new__",
42
+ store.stateDir,
43
+ 100,
44
+ );
45
+ if (candidateIds.length === 0) return undefined;
46
+ const byId = new Map(all.map((cp) => [cp.checkpointId, cp]));
47
+ const VERIFY_BUDGET_MS = 20;
48
+ const start = Date.now();
49
+ for (const id of candidateIds) {
50
+ if (Date.now() - start > VERIFY_BUDGET_MS) break; // QA #15: abort → "not dup"
51
+ const cand = byId.get(id);
52
+ if (!cand) continue;
53
+ const candText = cand.normalizedText ?? cand.summary ?? "";
54
+ if (isNearDuplicate(regionText, candText)) return cand;
55
+ }
56
+ return undefined;
57
+ }