@oscharko-dev/keiko 0.2.0-beta.6 → 0.2.0-beta.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 (210) hide show
  1. package/dist/ui/csp-hashes.json +14 -14
  2. package/dist/ui/static/404.html +1 -1
  3. package/dist/ui/static/__next.__PAGE__.txt +2 -2
  4. package/dist/ui/static/__next._full.txt +3 -3
  5. package/dist/ui/static/__next._head.txt +1 -1
  6. package/dist/ui/static/__next._index.txt +2 -2
  7. package/dist/ui/static/__next._tree.txt +2 -2
  8. package/dist/ui/static/_next/static/chunks/0i3jzgrj42so8.css +1 -0
  9. package/dist/ui/static/_next/static/chunks/1ru_021szp0u7.js +1 -0
  10. package/dist/ui/static/_next/static/chunks/1t7vb5d9ed2e7.js +1 -0
  11. package/dist/ui/static/_next/static/chunks/23o2c6pyjq92z.js +109 -0
  12. package/dist/ui/static/_not-found/__next._full.txt +2 -2
  13. package/dist/ui/static/_not-found/__next._head.txt +1 -1
  14. package/dist/ui/static/_not-found/__next._index.txt +2 -2
  15. package/dist/ui/static/_not-found/__next._not-found.__PAGE__.txt +1 -1
  16. package/dist/ui/static/_not-found/__next._not-found.txt +1 -1
  17. package/dist/ui/static/_not-found/__next._tree.txt +2 -2
  18. package/dist/ui/static/_not-found.html +1 -1
  19. package/dist/ui/static/_not-found.txt +2 -2
  20. package/dist/ui/static/fonts/OFL.txt +93 -0
  21. package/dist/ui/static/fonts/jetbrains-mono-latin-wght-normal.woff2 +0 -0
  22. package/dist/ui/static/index.html +1 -1
  23. package/dist/ui/static/index.txt +3 -3
  24. package/dist/ui/static/launch/__next._full.txt +3 -3
  25. package/dist/ui/static/launch/__next._head.txt +1 -1
  26. package/dist/ui/static/launch/__next._index.txt +2 -2
  27. package/dist/ui/static/launch/__next._tree.txt +2 -2
  28. package/dist/ui/static/launch/__next.launch.__PAGE__.txt +2 -2
  29. package/dist/ui/static/launch/__next.launch.txt +1 -1
  30. package/dist/ui/static/launch.html +1 -1
  31. package/dist/ui/static/launch.txt +3 -3
  32. package/dist/ui/static/local-knowledge/__next._full.txt +3 -3
  33. package/dist/ui/static/local-knowledge/__next._head.txt +1 -1
  34. package/dist/ui/static/local-knowledge/__next._index.txt +2 -2
  35. package/dist/ui/static/local-knowledge/__next._tree.txt +2 -2
  36. package/dist/ui/static/local-knowledge/__next.local-knowledge.__PAGE__.txt +2 -2
  37. package/dist/ui/static/local-knowledge/__next.local-knowledge.txt +1 -1
  38. package/dist/ui/static/local-knowledge/capsule/__next._full.txt +3 -3
  39. package/dist/ui/static/local-knowledge/capsule/__next._head.txt +1 -1
  40. package/dist/ui/static/local-knowledge/capsule/__next._index.txt +2 -2
  41. package/dist/ui/static/local-knowledge/capsule/__next._tree.txt +2 -2
  42. package/dist/ui/static/local-knowledge/capsule/__next.local-knowledge.capsule.__PAGE__.txt +2 -2
  43. package/dist/ui/static/local-knowledge/capsule/__next.local-knowledge.capsule.txt +1 -1
  44. package/dist/ui/static/local-knowledge/capsule/__next.local-knowledge.txt +1 -1
  45. package/dist/ui/static/local-knowledge/capsule.html +1 -1
  46. package/dist/ui/static/local-knowledge/capsule.txt +3 -3
  47. package/dist/ui/static/local-knowledge.html +1 -1
  48. package/dist/ui/static/local-knowledge.txt +3 -3
  49. package/dist/ui/static/memoriaviva/__next._full.txt +2 -2
  50. package/dist/ui/static/memoriaviva/__next._head.txt +1 -1
  51. package/dist/ui/static/memoriaviva/__next._index.txt +2 -2
  52. package/dist/ui/static/memoriaviva/__next._tree.txt +2 -2
  53. package/dist/ui/static/memoriaviva/__next.memoriaviva.__PAGE__.txt +1 -1
  54. package/dist/ui/static/memoriaviva/__next.memoriaviva.txt +1 -1
  55. package/dist/ui/static/memoriaviva/consolidation/__next._full.txt +2 -2
  56. package/dist/ui/static/memoriaviva/consolidation/__next._head.txt +1 -1
  57. package/dist/ui/static/memoriaviva/consolidation/__next._index.txt +2 -2
  58. package/dist/ui/static/memoriaviva/consolidation/__next._tree.txt +2 -2
  59. package/dist/ui/static/memoriaviva/consolidation/__next.memoriaviva.consolidation.__PAGE__.txt +1 -1
  60. package/dist/ui/static/memoriaviva/consolidation/__next.memoriaviva.consolidation.txt +1 -1
  61. package/dist/ui/static/memoriaviva/consolidation/__next.memoriaviva.txt +1 -1
  62. package/dist/ui/static/memoriaviva/consolidation.html +1 -1
  63. package/dist/ui/static/memoriaviva/consolidation.txt +2 -2
  64. package/dist/ui/static/memoriaviva/detail/__next._full.txt +2 -2
  65. package/dist/ui/static/memoriaviva/detail/__next._head.txt +1 -1
  66. package/dist/ui/static/memoriaviva/detail/__next._index.txt +2 -2
  67. package/dist/ui/static/memoriaviva/detail/__next._tree.txt +2 -2
  68. package/dist/ui/static/memoriaviva/detail/__next.memoriaviva.detail.__PAGE__.txt +1 -1
  69. package/dist/ui/static/memoriaviva/detail/__next.memoriaviva.detail.txt +1 -1
  70. package/dist/ui/static/memoriaviva/detail/__next.memoriaviva.txt +1 -1
  71. package/dist/ui/static/memoriaviva/detail.html +1 -1
  72. package/dist/ui/static/memoriaviva/detail.txt +2 -2
  73. package/dist/ui/static/memoriaviva/review-queue/__next._full.txt +2 -2
  74. package/dist/ui/static/memoriaviva/review-queue/__next._head.txt +1 -1
  75. package/dist/ui/static/memoriaviva/review-queue/__next._index.txt +2 -2
  76. package/dist/ui/static/memoriaviva/review-queue/__next._tree.txt +2 -2
  77. package/dist/ui/static/memoriaviva/review-queue/__next.memoriaviva.review-queue.__PAGE__.txt +1 -1
  78. package/dist/ui/static/memoriaviva/review-queue/__next.memoriaviva.review-queue.txt +1 -1
  79. package/dist/ui/static/memoriaviva/review-queue/__next.memoriaviva.txt +1 -1
  80. package/dist/ui/static/memoriaviva/review-queue.html +1 -1
  81. package/dist/ui/static/memoriaviva/review-queue.txt +2 -2
  82. package/dist/ui/static/memoriaviva.html +1 -1
  83. package/dist/ui/static/memoriaviva.txt +2 -2
  84. package/dist/ui/static/sw.js +7 -3
  85. package/node_modules/@oscharko-dev/keiko-cli/dist/.tsbuildinfo +1 -1
  86. package/node_modules/@oscharko-dev/keiko-cli/dist/memory.d.ts.map +1 -1
  87. package/node_modules/@oscharko-dev/keiko-cli/dist/memory.js +4 -5
  88. package/node_modules/@oscharko-dev/keiko-cli/package.json +1 -1
  89. package/node_modules/@oscharko-dev/keiko-contracts/dist/.tsbuildinfo +1 -1
  90. package/node_modules/@oscharko-dev/keiko-contracts/dist/index.d.ts +1 -1
  91. package/node_modules/@oscharko-dev/keiko-contracts/dist/index.js +1 -1
  92. package/node_modules/@oscharko-dev/keiko-contracts/package.json +1 -1
  93. package/node_modules/@oscharko-dev/keiko-evaluations/dist/.tsbuildinfo +1 -1
  94. package/node_modules/@oscharko-dev/keiko-evaluations/package.json +1 -1
  95. package/node_modules/@oscharko-dev/keiko-evidence/dist/.tsbuildinfo +1 -1
  96. package/node_modules/@oscharko-dev/keiko-evidence/dist/qualityIntelligence/figmaSnapshot/store.d.ts +5 -0
  97. package/node_modules/@oscharko-dev/keiko-evidence/dist/qualityIntelligence/figmaSnapshot/store.d.ts.map +1 -1
  98. package/node_modules/@oscharko-dev/keiko-evidence/dist/qualityIntelligence/figmaSnapshot/store.js +16 -0
  99. package/node_modules/@oscharko-dev/keiko-evidence/package.json +1 -1
  100. package/node_modules/@oscharko-dev/keiko-harness/dist/.tsbuildinfo +1 -1
  101. package/node_modules/@oscharko-dev/keiko-harness/package.json +1 -1
  102. package/node_modules/@oscharko-dev/keiko-local-knowledge/dist/.tsbuildinfo +1 -1
  103. package/node_modules/@oscharko-dev/keiko-local-knowledge/dist/parsers/pdf-parser.d.ts.map +1 -1
  104. package/node_modules/@oscharko-dev/keiko-local-knowledge/dist/parsers/pdf-parser.js +0 -10
  105. package/node_modules/@oscharko-dev/keiko-local-knowledge/dist/parsers/types.d.ts +2 -2
  106. package/node_modules/@oscharko-dev/keiko-local-knowledge/dist/parsers/types.d.ts.map +1 -1
  107. package/node_modules/@oscharko-dev/keiko-local-knowledge/dist/parsers/types.js +3 -3
  108. package/node_modules/@oscharko-dev/keiko-local-knowledge/package.json +1 -1
  109. package/node_modules/@oscharko-dev/keiko-memory-capture/package.json +1 -1
  110. package/node_modules/@oscharko-dev/keiko-memory-consolidation/package.json +1 -1
  111. package/node_modules/@oscharko-dev/keiko-memory-governance/dist/.tsbuildinfo +1 -1
  112. package/node_modules/@oscharko-dev/keiko-memory-governance/dist/index.d.ts +1 -1
  113. package/node_modules/@oscharko-dev/keiko-memory-governance/dist/index.d.ts.map +1 -1
  114. package/node_modules/@oscharko-dev/keiko-memory-governance/dist/index.js +1 -1
  115. package/node_modules/@oscharko-dev/keiko-memory-governance/dist/maintenance.d.ts +2 -16
  116. package/node_modules/@oscharko-dev/keiko-memory-governance/dist/maintenance.d.ts.map +1 -1
  117. package/node_modules/@oscharko-dev/keiko-memory-governance/dist/maintenance.js +49 -48
  118. package/node_modules/@oscharko-dev/keiko-memory-governance/dist/retention.d.ts +2 -1
  119. package/node_modules/@oscharko-dev/keiko-memory-governance/dist/retention.d.ts.map +1 -1
  120. package/node_modules/@oscharko-dev/keiko-memory-governance/dist/retention.js +15 -0
  121. package/node_modules/@oscharko-dev/keiko-memory-governance/package.json +1 -1
  122. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/.tsbuildinfo +1 -1
  123. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/decay.d.ts +2 -0
  124. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/decay.d.ts.map +1 -0
  125. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/decay.js +22 -0
  126. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/diversity.d.ts +8 -0
  127. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/diversity.d.ts.map +1 -0
  128. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/diversity.js +87 -0
  129. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/index.d.ts +4 -2
  130. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/index.d.ts.map +1 -1
  131. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/index.js +4 -1
  132. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/ranking.d.ts +5 -1
  133. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/ranking.d.ts.map +1 -1
  134. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/ranking.js +140 -21
  135. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/recency.d.ts.map +1 -1
  136. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/recency.js +4 -10
  137. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/retrieve.d.ts.map +1 -1
  138. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/retrieve.js +11 -1
  139. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/strength.d.ts +9 -0
  140. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/strength.d.ts.map +1 -0
  141. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/strength.js +51 -0
  142. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/types.d.ts +11 -0
  143. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/types.d.ts.map +1 -1
  144. package/node_modules/@oscharko-dev/keiko-memory-retrieval/dist/types.js +7 -0
  145. package/node_modules/@oscharko-dev/keiko-memory-retrieval/package.json +1 -1
  146. package/node_modules/@oscharko-dev/keiko-memory-vault/dist/.tsbuildinfo +1 -1
  147. package/node_modules/@oscharko-dev/keiko-memory-vault/dist/access.d.ts +3 -0
  148. package/node_modules/@oscharko-dev/keiko-memory-vault/dist/access.d.ts.map +1 -1
  149. package/node_modules/@oscharko-dev/keiko-memory-vault/dist/access.js +31 -1
  150. package/node_modules/@oscharko-dev/keiko-memory-vault/dist/schema.d.ts +1 -1
  151. package/node_modules/@oscharko-dev/keiko-memory-vault/dist/schema.d.ts.map +1 -1
  152. package/node_modules/@oscharko-dev/keiko-memory-vault/dist/schema.js +16 -1
  153. package/node_modules/@oscharko-dev/keiko-memory-vault/dist/types.d.ts +1 -0
  154. package/node_modules/@oscharko-dev/keiko-memory-vault/dist/types.d.ts.map +1 -1
  155. package/node_modules/@oscharko-dev/keiko-memory-vault/dist/vault.d.ts.map +1 -1
  156. package/node_modules/@oscharko-dev/keiko-memory-vault/dist/vault.js +4 -1
  157. package/node_modules/@oscharko-dev/keiko-memory-vault/package.json +1 -1
  158. package/node_modules/@oscharko-dev/keiko-model-gateway/dist/.tsbuildinfo +1 -1
  159. package/node_modules/@oscharko-dev/keiko-model-gateway/package.json +1 -1
  160. package/node_modules/@oscharko-dev/keiko-quality-intelligence/dist/.tsbuildinfo +1 -1
  161. package/node_modules/@oscharko-dev/keiko-quality-intelligence/package.json +1 -1
  162. package/node_modules/@oscharko-dev/keiko-sdk/dist/.tsbuildinfo +1 -1
  163. package/node_modules/@oscharko-dev/keiko-sdk/package.json +1 -1
  164. package/node_modules/@oscharko-dev/keiko-security/dist/.tsbuildinfo +1 -1
  165. package/node_modules/@oscharko-dev/keiko-security/package.json +1 -1
  166. package/node_modules/@oscharko-dev/keiko-server/dist/.tsbuildinfo +1 -1
  167. package/node_modules/@oscharko-dev/keiko-server/dist/chat-handlers.d.ts.map +1 -1
  168. package/node_modules/@oscharko-dev/keiko-server/dist/chat-handlers.js +44 -65
  169. package/node_modules/@oscharko-dev/keiko-server/dist/deps.d.ts +2 -0
  170. package/node_modules/@oscharko-dev/keiko-server/dist/deps.d.ts.map +1 -1
  171. package/node_modules/@oscharko-dev/keiko-server/dist/deps.js +1 -0
  172. package/node_modules/@oscharko-dev/keiko-server/dist/gateway-setup.d.ts.map +1 -1
  173. package/node_modules/@oscharko-dev/keiko-server/dist/gateway-setup.js +348 -64
  174. package/node_modules/@oscharko-dev/keiko-server/dist/memory-conv-handlers.d.ts.map +1 -1
  175. package/node_modules/@oscharko-dev/keiko-server/dist/memory-conv-handlers.js +34 -5
  176. package/node_modules/@oscharko-dev/keiko-server/dist/memory-embedding.d.ts +12 -2
  177. package/node_modules/@oscharko-dev/keiko-server/dist/memory-embedding.d.ts.map +1 -1
  178. package/node_modules/@oscharko-dev/keiko-server/dist/memory-embedding.js +127 -0
  179. package/node_modules/@oscharko-dev/keiko-server/dist/memory-handlers.d.ts.map +1 -1
  180. package/node_modules/@oscharko-dev/keiko-server/dist/memory-handlers.js +40 -11
  181. package/node_modules/@oscharko-dev/keiko-server/dist/memory-maintenance-handlers.d.ts +16 -3
  182. package/node_modules/@oscharko-dev/keiko-server/dist/memory-maintenance-handlers.d.ts.map +1 -1
  183. package/node_modules/@oscharko-dev/keiko-server/dist/memory-maintenance-handlers.js +72 -50
  184. package/node_modules/@oscharko-dev/keiko-server/dist/memory-retrieval-signals.d.ts +12 -0
  185. package/node_modules/@oscharko-dev/keiko-server/dist/memory-retrieval-signals.d.ts.map +1 -0
  186. package/node_modules/@oscharko-dev/keiko-server/dist/memory-retrieval-signals.js +84 -0
  187. package/node_modules/@oscharko-dev/keiko-server/dist/memory-salience.d.ts.map +1 -1
  188. package/node_modules/@oscharko-dev/keiko-server/dist/memory-salience.js +11 -6
  189. package/node_modules/@oscharko-dev/keiko-server/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts +15 -0
  190. package/node_modules/@oscharko-dev/keiko-server/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts.map +1 -1
  191. package/node_modules/@oscharko-dev/keiko-server/dist/qualityIntelligence/figmaSnapshotRoutes.js +105 -0
  192. package/node_modules/@oscharko-dev/keiko-server/dist/routes.d.ts.map +1 -1
  193. package/node_modules/@oscharko-dev/keiko-server/dist/routes.js +2 -1
  194. package/node_modules/@oscharko-dev/keiko-server/package.json +1 -1
  195. package/node_modules/@oscharko-dev/keiko-tools/dist/.tsbuildinfo +1 -1
  196. package/node_modules/@oscharko-dev/keiko-tools/package.json +1 -1
  197. package/node_modules/@oscharko-dev/keiko-verification/dist/.tsbuildinfo +1 -1
  198. package/node_modules/@oscharko-dev/keiko-verification/package.json +1 -1
  199. package/node_modules/@oscharko-dev/keiko-workflows/dist/.tsbuildinfo +1 -1
  200. package/node_modules/@oscharko-dev/keiko-workflows/package.json +1 -1
  201. package/node_modules/@oscharko-dev/keiko-workspace/dist/.tsbuildinfo +1 -1
  202. package/node_modules/@oscharko-dev/keiko-workspace/package.json +1 -1
  203. package/package.json +2 -1
  204. package/dist/ui/static/_next/static/chunks/0-qhhdvxg2j_y.js +0 -1
  205. package/dist/ui/static/_next/static/chunks/0ke4ratkgvcxo.css +0 -1
  206. package/dist/ui/static/_next/static/chunks/3vf3oh2-sl2nc.js +0 -1
  207. package/dist/ui/static/_next/static/chunks/3wmd4-2vznp2g.js +0 -106
  208. /package/dist/ui/static/_next/static/{fQMXe8UmV01bh25WOoIt3 → Ppze_8n_i3yc1FS_Qdj0I}/_buildManifest.js +0 -0
  209. /package/dist/ui/static/_next/static/{fQMXe8UmV01bh25WOoIt3 → Ppze_8n_i3yc1FS_Qdj0I}/_clientMiddlewareManifest.js +0 -0
  210. /package/dist/ui/static/_next/static/{fQMXe8UmV01bh25WOoIt3 → Ppze_8n_i3yc1FS_Qdj0I}/_ssgManifest.js +0 -0
@@ -14,6 +14,7 @@
14
14
  // function is inert (embedMemoryText -> null, embedAndStoreMemory -> no-op) and the caller keeps
15
15
  // its pre-semantic behaviour byte-for-byte.
16
16
  import { requestOpenAIEmbedding, } from "@oscharko-dev/keiko-model-gateway";
17
+ import { randomUUID } from "node:crypto";
17
18
  import { currentGatewayConfig } from "./deps.js";
18
19
  import { selectEmbeddingModelId } from "./local-knowledge-handlers.js";
19
20
  const MEMORY_VECTOR_METRIC = "cosine";
@@ -135,3 +136,129 @@ export function cosineSimilarity(a, b) {
135
136
  return 0;
136
137
  return cosine > 1 ? 1 : cosine;
137
138
  }
139
+ // ─── Semantic novelty gate at capture (#204, O-F1) ──────────────────────────────
140
+ // Lexical Jaccard dedup at capture misses semantic restatements ("I use Postgres" vs "my database is
141
+ // PostgreSQL"). This catches them BEFORE a second copy is stored: embed the candidate once, compare
142
+ // to the in-scope stored vectors, and if it is NEAR-IDENTICAL to an existing memory, reinforce that
143
+ // canonical memory instead of duplicating it.
144
+ //
145
+ // SAFETY: the threshold is deliberately HIGH (0.95) and the gate is applied ONLY to the low-stakes
146
+ // salience firehose — NOT to explicit user instructions. A pure cosine signal cannot tell a
147
+ // paraphrase ("uses PostgreSQL") from a value-change ("region is eu-central-1" vs "us-east-1"), so a
148
+ // lower threshold could merge a contradicting update. At 0.95 only near-verbatim restatements merge;
149
+ // value-changes (differing entities/numbers) stay below it and are inserted normally, where
150
+ // consolidation's conflict detection backstops them. Merging reinforces the canonical rather than
151
+ // deleting, so even a false merge loses no stored fact. Graceful: with no embedder the candidate
152
+ // embedding is null and the gate is inert (prior lexical-only behaviour, byte-for-byte).
153
+ export const SEMANTIC_DEDUP_COSINE_THRESHOLD = 0.95;
154
+ // Pure: the id of the nearest in-scope memory whose cosine to the candidate is at/above the
155
+ // threshold, or null (no candidate embedding, no neighbours, or none similar enough). First-max wins
156
+ // on ties so the result is deterministic for a fixed neighbour iteration order.
157
+ export function findSemanticDuplicate(candidate, neighbors, threshold = SEMANTIC_DEDUP_COSINE_THRESHOLD) {
158
+ if (candidate === null)
159
+ return null;
160
+ let bestId = null;
161
+ let bestSim = -1;
162
+ for (const [id, row] of neighbors) {
163
+ const sim = cosineSimilarity(candidate.vector, row.vector);
164
+ if (sim > bestSim) {
165
+ bestSim = sim;
166
+ bestId = id;
167
+ }
168
+ }
169
+ return bestSim >= threshold ? bestId : null;
170
+ }
171
+ // ─── Semantic auto-linking at capture (#204, O-P4) ──────────────────────────────
172
+ // A-MEM (2502.12110) self-organises memory by LINKING a new note to its semantic neighbours at
173
+ // write time, so associative recall has structure to traverse immediately. Memoria Viva already
174
+ // runs graph-proximity recall live (the ranker's `graph` subscore), but until now the only edges
175
+ // were supersedes/correction links and the batch consolidation pass — a fresh capture had no
176
+ // associations. This forms them deterministically from the SAME embedding used for the novelty
177
+ // gate: a novel capture is linked to its nearest in-scope neighbours that fall in a "related" band
178
+ // (similar enough to associate, below the dedup threshold so they are not the same fact). No model
179
+ // call, no non-determinism — a pure cosine band over vectors already in hand.
180
+ // Lower bound of the related band. Above this two memory bodies are topically associated; below it
181
+ // the link would be noise. Conservative starting point for text-embedding-3-large; tunable per the
182
+ // opt-in flag before it is switched on by default.
183
+ export const RELATED_LINK_COSINE_THRESHOLD = 0.82;
184
+ // At most this many associations per capture, so the graph stays sparse and traversal stays cheap.
185
+ export const MAX_AUTO_LINKS = 3;
186
+ // Pure: the in-scope neighbours whose cosine to the candidate falls in the band [lower, upper) —
187
+ // associated but not a duplicate — ranked by similarity desc (id asc tiebreak) and capped at
188
+ // maxLinks. Empty for a null candidate or when nothing lands in the band. Deterministic for a fixed
189
+ // neighbour set.
190
+ export function findRelatedNeighbors(candidate, neighbors, lower = RELATED_LINK_COSINE_THRESHOLD, upper = SEMANTIC_DEDUP_COSINE_THRESHOLD, maxLinks = MAX_AUTO_LINKS) {
191
+ if (candidate === null)
192
+ return [];
193
+ const scored = [];
194
+ for (const [id, row] of neighbors) {
195
+ const similarity = cosineSimilarity(candidate.vector, row.vector);
196
+ if (similarity >= lower && similarity < upper)
197
+ scored.push({ id, similarity });
198
+ }
199
+ scored.sort((a, b) => a.similarity !== b.similarity ? b.similarity - a.similarity : a.id.localeCompare(b.id));
200
+ return scored.slice(0, maxLinks).map((n) => n.id);
201
+ }
202
+ // Opt-in (KEIKO_MEMORY_AUTO_LINK=1, default off => byte-identical: no edges, no behaviour change).
203
+ // Best-effort: a rejected edge insert must never break the capture that already succeeded.
204
+ function autoLinkRelatedMemories(deps, vault, fromId, embedding, neighbors) {
205
+ if (deps.env.KEIKO_MEMORY_AUTO_LINK !== "1")
206
+ return;
207
+ const relatedIds = findRelatedNeighbors(embedding, neighbors);
208
+ if (relatedIds.length === 0)
209
+ return;
210
+ const nowMs = Date.now();
211
+ for (const toId of relatedIds) {
212
+ try {
213
+ vault.insertEdge({
214
+ id: randomUUID(),
215
+ schemaVersion: "1",
216
+ fromMemoryId: fromId,
217
+ toMemoryId: toId,
218
+ kind: "related",
219
+ createdAt: nowMs,
220
+ provenanceSummary: "semantic auto-link",
221
+ });
222
+ }
223
+ catch {
224
+ // Validator / storage rejection — association enrichment is best-effort, never fatal.
225
+ }
226
+ }
227
+ }
228
+ // Bounds the neighbour set so the cosine sweep stays cheap on a large vault (mirrors the lexical
229
+ // dedup corpus bound). Scope-local list preserves cross-scope isolation.
230
+ const MAX_DEDUP_NEIGHBORS = 200;
231
+ function gatherScopeEmbeddings(vault, scope) {
232
+ const ids = vault
233
+ .listMemoriesByScope(scope)
234
+ .slice(0, MAX_DEDUP_NEIGHBORS)
235
+ .map((record) => record.id);
236
+ return ids.length === 0 ? new Map() : vault.getEmbeddings(ids);
237
+ }
238
+ // Insert a freshly-built salience capture record UNLESS it is a semantic near-duplicate of an
239
+ // existing in-scope memory, in which case reinforce the canonical (recordAccess) and skip the
240
+ // duplicate. Embeds the body exactly ONCE (reused for both the novelty check and storage), so this
241
+ // replaces — not adds to — the prior best-effort embed-on-capture call. Never throws past the
242
+ // vault's own guards; a null embedding degrades to a plain insert.
243
+ export async function insertSalienceMemoryWithNoveltyGate(deps, vault, record) {
244
+ const embedding = await embedMemoryText(deps, record.body);
245
+ const neighbors = gatherScopeEmbeddings(vault, record.scope);
246
+ const duplicateOf = findSemanticDuplicate(embedding, neighbors);
247
+ if (duplicateOf !== null) {
248
+ vault.recordAccess([duplicateOf], Date.now());
249
+ return { inserted: null, mergedInto: duplicateOf };
250
+ }
251
+ const inserted = vault.insertMemory(record);
252
+ if (embedding !== null) {
253
+ try {
254
+ vault.upsertEmbedding(inserted.id, embedding);
255
+ }
256
+ catch {
257
+ // gateEmbeddingInput / storage rejection — capture already succeeded; drop the embedding.
258
+ }
259
+ // A-MEM-style associative linking (#204, O-P4). Reuses the neighbour set already fetched for the
260
+ // novelty gate — no extra IO. Opt-in (default off => no edges, byte-identical).
261
+ autoLinkRelatedMemories(deps, vault, inserted.id, embedding, neighbors);
262
+ }
263
+ return { inserted, mergedInto: null };
264
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"memory-handlers.d.ts","sourceRoot":"","sources":["../src/memory-handlers.ts"],"names":[],"mappings":"AAkBA,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,kCAAkC,CAAC;AAoC1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAY,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA6SvE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CA8BtF;AAID,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CAmB5F;AAID,wBAAgB,eAAe,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CAqBnF;AAwDD,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CA+BtB;AAID,wBAAgB,eAAe,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CA6BnF;AAID,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CA6BrF;AAID,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CAsCtB;AAkND,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CA0BtB;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CAiBtB;AAKD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CAkCtB;AAsMD,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CAiBtB;AA8GD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CA8CtB;AA0ID,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CAc9F;AAyBD,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CAgCtB;AAOD,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EACnC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,kCAAkC,EAAE,WAAW,KAAK,IAAI,EACvF,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GACjD,gBAAgB,CASlB"}
1
+ {"version":3,"file":"memory-handlers.d.ts","sourceRoot":"","sources":["../src/memory-handlers.ts"],"names":[],"mappings":"AAkBA,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,kCAAkC,CAAC;AAqC1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAY,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA6SvE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CA8BtF;AAID,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CAmB5F;AAID,wBAAgB,eAAe,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CAqBnF;AAwDD,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CA+BtB;AAID,wBAAgB,eAAe,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CA6BnF;AAID,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CA6BrF;AAID,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CAsCtB;AAkND,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CA0BtB;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CAiBtB;AAKD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CAkCtB;AAkND,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CAiBtB;AA8GD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CA8CtB;AAyJD,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CAc9F;AAyBD,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CAoCtB;AAOD,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EACnC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,kCAAkC,EAAE,WAAW,KAAK,IAAI,EACvF,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GACjD,gBAAgB,CASlB"}
@@ -15,7 +15,7 @@
15
15
  // Every response is redacted through `deps.redactor` before serialisation to honour D9.
16
16
  import { randomUUID } from "node:crypto";
17
17
  import { createMemoryVault, MemoryStorageError, } from "@oscharko-dev/keiko-memory-vault";
18
- import { GovernanceError, buildArchiveOperation, buildConflictTransitions, buildCorrection, buildForgetOperations, buildPinOperation, buildUnpinOperation, detectConflictPair, selectMemoriesForForget, } from "@oscharko-dev/keiko-memory-governance";
18
+ import { GovernanceError, buildArchiveOperation, buildConflictTransitions, buildCorrection, buildForgetOperations, buildPinOperation, buildUnpinOperation, detectConflictPair, selectMemoriesForForget, supersededValidity, } from "@oscharko-dev/keiko-memory-governance";
19
19
  import { checkStatusTransition, MEMORY_SCOPE_KINDS, MEMORY_STATUSES, MEMORY_TYPES, MEMORY_SENSITIVITIES, validateMemoryScope, } from "@oscharko-dev/keiko-contracts";
20
20
  import { errorBody } from "./routes.js";
21
21
  import { auditRunIdFor, recordMemoryAudit } from "./memory-audit-handler.js";
@@ -826,7 +826,12 @@ function findMemoryById(memories, id) {
826
826
  }
827
827
  function persistConflictTransitions(vault, resolution, reason) {
828
828
  for (const transition of resolution.statusTransitions) {
829
- vault.updateMemory(transition.memoryId, { status: transition.to, staleReason: reason }, transition.transitionedAt);
829
+ // Bi-temporal-lite (#204, C1): a record losing a conflict and being SUPERSEDED gets its belief
830
+ // window closed at the transition time, same as the correction path. Other transitions (e.g.
831
+ // the winner re-accepted) leave validity untouched.
832
+ const existing = transition.to === "superseded" ? vault.getMemory(transition.memoryId) : undefined;
833
+ const validity = existing !== undefined ? supersededValidity(existing, transition.transitionedAt) : null;
834
+ vault.updateMemory(transition.memoryId, { status: transition.to, staleReason: reason, ...(validity !== null ? { validity } : {}) }, transition.transitionedAt);
830
835
  }
831
836
  }
832
837
  function persistConflictSupersessions(vault, deps, memories, supersessions, nowMs) {
@@ -851,6 +856,11 @@ function executeConflictResolution(vault, deps, input) {
851
856
  const resolution = buildConflictTransitions(memories, { winner: input.winner, losers: input.losers }, { reviewerId: DEFAULT_REVIEWER_ID, nowMs });
852
857
  persistConflictTransitions(vault, resolution, input.reason);
853
858
  const edgeIds = persistConflictSupersessions(vault, deps, memories, resolution.supersessions, nowMs);
859
+ // Outcome-driven forgetting (#204, O-V1): the winner proved more correct (utility 1), the losers
860
+ // proved wrong (utility 0). These bias the maintenance utility factor toward keeping the winner
861
+ // and forgetting the superseded losers.
862
+ vault.recordOutcome([input.winner], 1, nowMs);
863
+ vault.recordOutcome(input.losers, 0, nowMs);
854
864
  return {
855
865
  resolved: true,
856
866
  winner: input.winner,
@@ -1054,14 +1064,21 @@ function buildAcceptProposalPatch(origins) {
1054
1064
  function buildCorrectionAcceptanceUpdates(proposalId, acceptPatch, origins, nowMs) {
1055
1065
  return [
1056
1066
  { id: proposalId, patch: acceptPatch, nowMs },
1057
- ...origins.map(({ edge, original }) => ({
1058
- id: original.id,
1059
- patch: {
1060
- status: "superseded",
1061
- staleReason: edge.provenanceSummary ?? "accepted correction",
1062
- },
1063
- nowMs,
1064
- })),
1067
+ ...origins.map(({ edge, original }) => {
1068
+ // Bi-temporal-lite (#204, C1): close the superseded fact's belief window at acceptance time so
1069
+ // it drops out of default retrieval and "as of date T" stays answerable. Additive — only when
1070
+ // it forms a valid, non-extending interval.
1071
+ const validity = supersededValidity(original, nowMs);
1072
+ return {
1073
+ id: original.id,
1074
+ patch: {
1075
+ status: "superseded",
1076
+ staleReason: edge.provenanceSummary ?? "accepted correction",
1077
+ ...(validity !== null ? { validity } : {}),
1078
+ },
1079
+ nowMs,
1080
+ };
1081
+ }),
1065
1082
  ];
1066
1083
  }
1067
1084
  function recordCorrectionSupersessionAudits(deps, acceptedCorrection, origins, nowMs) {
@@ -1093,6 +1110,14 @@ function acceptMemoryProposal(vault, deps, id) {
1093
1110
  if (updated === undefined) {
1094
1111
  throw new GovernanceError("invalid-resolution", "acceptance update produced no records");
1095
1112
  }
1113
+ // Outcome-driven forgetting (#204, O-V1): acceptance is a positive retention outcome for the
1114
+ // proposal; any origin it supersedes proved wrong (utility 0). Both feed the maintenance utility
1115
+ // factor so the kept memory resists disuse decay and the corrected-away origin fades sooner.
1116
+ vault.recordOutcome([id], 1, nowMs);
1117
+ const supersededOriginIds = origins.map((origin) => origin.original.id);
1118
+ if (supersededOriginIds.length > 0) {
1119
+ vault.recordOutcome(supersededOriginIds, 0, nowMs);
1120
+ }
1096
1121
  recordCorrectionSupersessionAudits(deps, updated, origins, nowMs);
1097
1122
  return { status: 200, body: { memory: redactMemory(deps, updated) } };
1098
1123
  }
@@ -1146,7 +1171,11 @@ export async function handleRejectMemoryProposal(ctx, deps) {
1146
1171
  const existing = ensureRejectableMemory(vault.getMemory(id));
1147
1172
  if (isRouteResult(existing))
1148
1173
  return existing;
1149
- const updated = vault.updateMemory(id, { status: "rejected", staleReason: reason }, Date.now());
1174
+ const nowMs = Date.now();
1175
+ const updated = vault.updateMemory(id, { status: "rejected", staleReason: reason }, nowMs);
1176
+ // Outcome-driven forgetting (#204, O-V1): a user rejection is a negative retention outcome
1177
+ // (utility 0), so the rejected memory fades faster under maintenance instead of lingering.
1178
+ vault.recordOutcome([id], 0, nowMs);
1150
1179
  return { status: 200, body: { memory: redactMemory(deps, updated) } };
1151
1180
  }
1152
1181
  catch (err) {
@@ -5,8 +5,6 @@ import type { UiHandlerDeps } from "./deps.js";
5
5
  import type { RouteContext, RouteResult } from "./routes.js";
6
6
  export interface MaintenanceCounts {
7
7
  promoted: number;
8
- reinforced: number;
9
- decayed: number;
10
8
  archived: number;
11
9
  forgotten: number;
12
10
  superseded: number;
@@ -17,6 +15,21 @@ export interface MaintenanceCounts {
17
15
  export interface MaintenanceResult extends MaintenanceCounts {
18
16
  readonly reviewItems: readonly ReviewItem[];
19
17
  }
20
- export declare function runMemoryMaintenance(vault: MemoryVaultStore, evidenceStore?: EvidenceStore): MaintenanceResult;
18
+ export interface RunMaintenanceOptions {
19
+ /** Injected clock. Defaults to Date.now(). Pass a fixed value to make the pass replay-stable. */
20
+ readonly nowMs?: number;
21
+ }
22
+ export declare function runMemoryMaintenance(vault: MemoryVaultStore, evidenceStore?: EvidenceStore, options?: RunMaintenanceOptions): MaintenanceResult;
23
+ export declare const MEMORY_AUTO_MAINTENANCE_MIN_INTERVAL_MS: number;
24
+ export declare function isMaintenanceDue(lastRunAtMs: number | undefined, nowMs: number, minIntervalMs?: number): boolean;
25
+ export interface AutoMaintenanceState {
26
+ lastRunAtMs?: number;
27
+ }
28
+ export interface MaybeRunAutoMaintenanceOptions {
29
+ readonly nowMs: number;
30
+ readonly enabled: boolean;
31
+ readonly minIntervalMs?: number;
32
+ }
33
+ export declare function maybeRunAutoMaintenance(vault: MemoryVaultStore, evidenceStore: EvidenceStore | undefined, state: AutoMaintenanceState, options: MaybeRunAutoMaintenanceOptions): MaintenanceResult | null;
21
34
  export declare function handleRunMaintenance(ctx: RouteContext, deps: UiHandlerDeps): RouteResult;
22
35
  //# sourceMappingURL=memory-maintenance-handlers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory-maintenance-handlers.d.ts","sourceRoot":"","sources":["../src/memory-maintenance-handlers.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAc7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI7D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB;IAC1D,QAAQ,CAAC,WAAW,EAAE,SAAS,UAAU,EAAE,CAAC;CAC7C;AAkND,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,gBAAgB,EACvB,aAAa,CAAC,EAAE,aAAa,GAC5B,iBAAiB,CAuBnB;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CAWxF"}
1
+ {"version":3,"file":"memory-maintenance-handlers.d.ts","sourceRoot":"","sources":["../src/memory-maintenance-handlers.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAc7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI7D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB;IAC1D,QAAQ,CAAC,WAAW,EAAE,SAAS,UAAU,EAAE,CAAC;CAC7C;AAuMD,MAAM,WAAW,qBAAqB;IACpC,iGAAiG;IACjG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,gBAAgB,EACvB,aAAa,CAAC,EAAE,aAAa,EAC7B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,iBAAiB,CAkCnB;AAQD,eAAO,MAAM,uCAAuC,QAAqB,CAAC;AAI1E,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,KAAK,EAAE,MAAM,EACb,aAAa,GAAE,MAAgD,GAC9D,OAAO,CAIT;AAGD,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAMD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,gBAAgB,EACvB,aAAa,EAAE,aAAa,GAAG,SAAS,EACxC,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,8BAA8B,GACtC,iBAAiB,GAAG,IAAI,CAS1B;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CAWxF"}
@@ -6,8 +6,10 @@
6
6
  // 2. Run consolidation on the accepted subset; persist auto-applicable relationship edges and
7
7
  // return unresolved review items for MemoriaViva or CLI operators. Conflict and merge review
8
8
  // items are NEVER auto-applied here.
9
- // 3. Compute the maintenance plan and apply it: promote (-> accepted), reinforce / decay
10
- // (confidence patch), archive (-> archived), forget (vault delete + tombstone + reason).
9
+ // 3. Compute the maintenance plan and apply it: promote (-> accepted), archive (-> archived),
10
+ // forget (vault delete + tombstone + reason). Confidence is immutable provenance and is never
11
+ // patched here (O-V2): reuse strengthens memories live in retrieval ranking, and disuse-decay
12
+ // is computed on the fly via the strength curve that gates archive/forget.
11
13
  // 4. Emit one audit event per applied effect and return the counts.
12
14
  //
13
15
  // CSRF: the server dispatch layer enforces x-keiko-csrf for POST, so this route is guarded without
@@ -21,8 +23,6 @@ import { recordMemoryAudit } from "./memory-audit-handler.js";
21
23
  function emptyCounts() {
22
24
  return {
23
25
  promoted: 0,
24
- reinforced: 0,
25
- decayed: 0,
26
26
  archived: 0,
27
27
  forgotten: 0,
28
28
  superseded: 0,
@@ -44,24 +44,20 @@ function resolveVault(deps) {
44
44
  }
45
45
  return deps.memoryVault;
46
46
  }
47
- function emitAudit(evidenceStore, kind, surface, summary, extra) {
47
+ function emitAudit(evidenceStore, nowMs, kind, surface, summary, extra) {
48
48
  if (evidenceStore === undefined)
49
49
  return;
50
50
  const event = {
51
51
  schemaVersion: "1",
52
52
  kind,
53
53
  eventId: randomUUID(),
54
- occurredAt: Date.now(),
54
+ occurredAt: nowMs,
55
55
  initiatorSurface: surface,
56
56
  summary,
57
57
  ...extra,
58
58
  };
59
59
  recordMemoryAudit({ evidenceStore }, event);
60
60
  }
61
- // Patch a record's confidence by rebuilding the provenance envelope (confidence lives there).
62
- function patchConfidence(vault, record, confidence) {
63
- vault.updateMemory(record.id, { provenance: { ...record.provenance, confidence } }, Date.now());
64
- }
65
61
  function recordsById(records) {
66
62
  const map = new Map();
67
63
  for (const record of records)
@@ -82,9 +78,9 @@ function applyEdges(vault, edges) {
82
78
  }
83
79
  return created;
84
80
  }
85
- function runConsolidationPass(vault, records, counts) {
81
+ function runConsolidationPass(vault, nowMs, records, counts) {
86
82
  const result = runConsolidation(records, {
87
- nowMs: Date.now(),
83
+ nowMs,
88
84
  newEdgeId: () => randomUUID(),
89
85
  newReviewItemId: () => randomUUID(),
90
86
  });
@@ -94,48 +90,41 @@ function runConsolidationPass(vault, records, counts) {
94
90
  counts.reviewItems.push(...result.reviewItems);
95
91
  }
96
92
  // ─── Plan application ──────────────────────────────────────────────────────────
97
- // Applies the reinforce / decay / archive / forget effects on the post-consolidation snapshot.
98
- // Promotions are applied SEPARATELY and BEFORE consolidation (see runMemoryMaintenance) so that
99
- // freshly-accepted memories are visible to conflict detection within the same maintenance pass.
100
- function applyDecayEffects(vault, evidenceStore, plan, byId, counts) {
101
- applyConfidencePatches(vault, plan.reinforce, byId, (n) => (counts.reinforced += n));
102
- applyConfidencePatches(vault, plan.decay, byId, (n) => (counts.decayed += n));
103
- applyArchives(vault, evidenceStore, plan.archive, byId, counts);
104
- applyForgets(vault, evidenceStore, plan.forget, byId, counts);
105
- }
106
- function applyPromotions(vault, evidenceStore, ids, byId, counts) {
93
+ // Applies the archive / forget effects on the post-consolidation snapshot. Promotions are applied
94
+ // SEPARATELY and BEFORE consolidation (see runMemoryMaintenance) so that freshly-accepted memories
95
+ // are visible to conflict detection within the same maintenance pass.
96
+ //
97
+ // Confidence is NEVER mutated here (#204, O-V2): reinforcement-on-reuse is realised live in
98
+ // retrieval ranking, and disuse-decay is computed on the fly via the strength curve that already
99
+ // gates archive/forget so provenance stays intact and every run is idempotent.
100
+ function applyFadeEffects(vault, evidenceStore, nowMs, plan, byId, counts) {
101
+ applyArchives(vault, evidenceStore, nowMs, plan.archive, byId, counts);
102
+ applyForgets(vault, evidenceStore, nowMs, plan.forget, byId, counts);
103
+ }
104
+ function applyPromotions(vault, evidenceStore, nowMs, ids, byId, counts) {
107
105
  for (const id of ids) {
108
106
  const record = byId.get(id);
109
107
  if (record === undefined)
110
108
  continue;
111
- vault.updateMemory(id, { status: "accepted" }, Date.now());
109
+ vault.updateMemory(id, { status: "accepted" }, nowMs);
112
110
  counts.promoted += 1;
113
- emitAudit(evidenceStore, "memory:accepted", "memory-center", "Promoted a strong proposed memory.", { memoryId: id, scope: record.scope });
114
- }
115
- }
116
- function applyConfidencePatches(vault, patches, byId, bump) {
117
- for (const patch of patches) {
118
- const record = byId.get(patch.id);
119
- if (record === undefined)
120
- continue;
121
- patchConfidence(vault, record, patch.confidence);
122
- bump(1);
111
+ emitAudit(evidenceStore, nowMs, "memory:accepted", "memory-center", "Promoted a strong proposed memory.", { memoryId: id, scope: record.scope });
123
112
  }
124
113
  }
125
- function applyArchives(vault, evidenceStore, ids, byId, counts) {
114
+ function applyArchives(vault, evidenceStore, nowMs, ids, byId, counts) {
126
115
  for (const id of ids) {
127
116
  const record = byId.get(id);
128
117
  if (record === undefined)
129
118
  continue;
130
- vault.updateMemory(id, { status: "archived" }, Date.now());
119
+ vault.updateMemory(id, { status: "archived" }, nowMs);
131
120
  counts.archived += 1;
132
- emitAudit(evidenceStore, "memory:archived", "retention", "Archived a faded memory.", {
121
+ emitAudit(evidenceStore, nowMs, "memory:archived", "retention", "Archived a faded memory.", {
133
122
  memoryId: id,
134
123
  scope: record.scope,
135
124
  });
136
125
  }
137
126
  }
138
- function applyForgets(vault, evidenceStore, forgets, byId, counts) {
127
+ function applyForgets(vault, evidenceStore, nowMs, forgets, byId, counts) {
139
128
  for (const forget of forgets) {
140
129
  const record = byId.get(forget.id);
141
130
  if (record === undefined)
@@ -144,20 +133,21 @@ function applyForgets(vault, evidenceStore, forgets, byId, counts) {
144
133
  tombstone: true,
145
134
  forgetterSurface: "memory-maintenance",
146
135
  reason: forget.reason,
147
- nowMs: Date.now(),
136
+ nowMs,
148
137
  });
149
138
  counts.forgotten += 1;
150
- emitAudit(evidenceStore, "memory:forgotten", "retention", `Forgot a memory (${forget.reason}).`, {
139
+ emitAudit(evidenceStore, nowMs, "memory:forgotten", "retention", `Forgot a memory (${forget.reason}).`, {
151
140
  memoryId: forget.id,
152
141
  scope: record.scope,
153
142
  tombstoned: true,
154
143
  });
155
144
  }
156
145
  }
157
- // Reusable maintenance core. Drives consolidation + the governance plan against a vault, emitting
158
- // audit events when an evidence store is supplied. Exported so both the BFF route handler and the
159
- // `keiko memory maintain` CLI run the SAME pass no duplicated orchestration.
160
- export function runMemoryMaintenance(vault, evidenceStore) {
146
+ export function runMemoryMaintenance(vault, evidenceStore, options) {
147
+ // ONE clock for the whole pass: both plan phases and every vault write / audit timestamp use the
148
+ // same nowMs, so selection is consistent within a run and fully replay-stable when nowMs is
149
+ // injected (the deterministic-verification invariant the rest of the stack honours).
150
+ const nowMs = options?.nowMs ?? Date.now();
161
151
  const counts = emptyCounts();
162
152
  // Phase 1 — promote strong `proposed` memories FIRST. Consolidation and conflict detection only
163
153
  // inspect `accepted` records, so without this a vault full of freshly-captured `proposed`
@@ -165,22 +155,54 @@ export function runMemoryMaintenance(vault, evidenceStore) {
165
155
  // resolved. Promoting up front makes a single "Run maintenance" fully effective.
166
156
  const beforePromote = vault.listMemories({ includeExpired: true });
167
157
  const promoteStats = vault.getAccessStats();
168
- const promotePlan = planMemoryMaintenance(beforePromote, promoteStats, { nowMs: Date.now() });
169
- applyPromotions(vault, evidenceStore, promotePlan.promote, recordsById(beforePromote), counts);
158
+ const promotePlan = planMemoryMaintenance(beforePromote, promoteStats, { nowMs });
159
+ applyPromotions(vault, evidenceStore, nowMs, promotePlan.promote, recordsById(beforePromote), counts);
170
160
  // Phase 2 — consolidate the now-accepted set: link safe near-duplicate metadata and surface
171
161
  // conflicts / merges as explicit review items. Status mutations require a later governed review.
172
162
  const accepted = vault
173
163
  .listMemories({ includeExpired: true })
174
164
  .filter((record) => record.status === "accepted");
175
- runConsolidationPass(vault, accepted, counts);
176
- // Phase 3 — reinforce / decay / archive / forget on the post-consolidation snapshot. The access
177
- // stats feed the strength model.
165
+ runConsolidationPass(vault, nowMs, accepted, counts);
166
+ // Phase 3 — archive / forget on the post-consolidation snapshot. The access stats feed the
167
+ // strength model; confidence itself is never mutated (O-V2).
178
168
  const all = vault.listMemories({ includeExpired: true });
179
169
  const accessStats = vault.getAccessStats();
180
- const plan = planMemoryMaintenance(all, accessStats, { nowMs: Date.now() });
181
- applyDecayEffects(vault, evidenceStore, plan, recordsById(all), counts);
170
+ const plan = planMemoryMaintenance(all, accessStats, { nowMs });
171
+ applyFadeEffects(vault, evidenceStore, nowMs, plan, recordsById(all), counts);
182
172
  return counts;
183
173
  }
174
+ // ─── Bounded autonomous maintenance (#204, O-V4) ───────────────────────────────
175
+ // The strength/decay/forget pass only "lives" if it actually runs. Rather than a free-running
176
+ // background loop (forbidden by the no-unbounded-hidden-activity invariant), maintenance is fired
177
+ // opportunistically — once memory is used — and rate-limited to at most once per interval. The pass
178
+ // itself is already bounded (maxForgetPerRun) and audited, so an autonomous fire is governed.
179
+ export const MEMORY_AUTO_MAINTENANCE_MIN_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6 hours
180
+ // Pure: due iff never run, or the interval has elapsed. A non-finite/negative interval is treated as
181
+ // "never auto-run" so a misconfiguration can only DISABLE, never spin.
182
+ export function isMaintenanceDue(lastRunAtMs, nowMs, minIntervalMs = MEMORY_AUTO_MAINTENANCE_MIN_INTERVAL_MS) {
183
+ if (!Number.isFinite(minIntervalMs) || minIntervalMs <= 0)
184
+ return false;
185
+ if (lastRunAtMs === undefined)
186
+ return true;
187
+ return nowMs - lastRunAtMs >= minIntervalMs;
188
+ }
189
+ // Runs ONE bounded maintenance pass iff enabled AND due, advancing the cursor BEFORE running so a
190
+ // re-entrant call within the same tick cannot double-fire. Returns the result, or null when skipped.
191
+ // Never throws: a maintenance fault must not break the caller (e.g. a chat turn); it is swallowed
192
+ // after advancing the cursor so a persistently-failing pass cannot hot-loop.
193
+ export function maybeRunAutoMaintenance(vault, evidenceStore, state, options) {
194
+ if (!options.enabled)
195
+ return null;
196
+ if (!isMaintenanceDue(state.lastRunAtMs, options.nowMs, options.minIntervalMs))
197
+ return null;
198
+ state.lastRunAtMs = options.nowMs;
199
+ try {
200
+ return runMemoryMaintenance(vault, evidenceStore, { nowMs: options.nowMs });
201
+ }
202
+ catch {
203
+ return null;
204
+ }
205
+ }
184
206
  export function handleRunMaintenance(ctx, deps) {
185
207
  void ctx;
186
208
  const vault = resolveVault(deps);
@@ -0,0 +1,12 @@
1
+ import type { MemoryId, MemoryScope } from "@oscharko-dev/keiko-contracts/memory";
2
+ import { type RankingFusionMode } from "@oscharko-dev/keiko-memory-retrieval";
3
+ import type { MemoryVaultStore } from "@oscharko-dev/keiko-memory-vault";
4
+ import type { UiHandlerDeps } from "./deps.js";
5
+ export declare function conversationFusionMode(deps: UiHandlerDeps): RankingFusionMode;
6
+ export interface ConversationRetrievalSignals {
7
+ readonly semanticById?: ReadonlyMap<MemoryId, number> | undefined;
8
+ readonly strengthById: ReadonlyMap<MemoryId, number>;
9
+ readonly embeddingById: ReadonlyMap<MemoryId, Float32Array>;
10
+ }
11
+ export declare function buildConversationRetrievalSignals(deps: UiHandlerDeps, vault: MemoryVaultStore, queryText: string | undefined, scopes: readonly MemoryScope[], nowMs: number, safeForSecondaryModel: boolean): Promise<ConversationRetrievalSignals>;
12
+ //# sourceMappingURL=memory-retrieval-signals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-retrieval-signals.d.ts","sourceRoot":"","sources":["../src/memory-retrieval-signals.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,QAAQ,EAAgB,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAChG,OAAO,EAKL,KAAK,iBAAiB,EACvB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAE7F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAwD/C,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,iBAAiB,CAE7E;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,YAAY,CAAC,EAAE,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAClE,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAIrD,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;CAC7D;AAED,wBAAsB,iCAAiC,CACrD,IAAI,EAAE,aAAa,EACnB,KAAK,EAAE,gBAAgB,EACvB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,KAAK,EAAE,MAAM,EACb,qBAAqB,EAAE,OAAO,GAC7B,OAAO,CAAC,4BAA4B,CAAC,CAkBvC"}
@@ -0,0 +1,84 @@
1
+ // Shared conversation-retrieval signal builder (#204, O-F4).
2
+ //
3
+ // Both conversation retrieval surfaces — the desktop chat path (chat-handlers) and the BFF
4
+ // /api/memory/context route (memory-conv-handlers) — need the same two model/usage-derived ranking
5
+ // signals on top of the pure lexical ranker:
6
+ // - semanticById: per-memory cosine of the query embedding to each candidate's stored vector
7
+ // (embedding-based recall), gated by the secondary-model egress check.
8
+ // - strengthById: per-memory reinforcement strength from the vault's access counters (O-P1).
9
+ // Previously only the chat path built them; the BFF route silently ran lexical-only. Centralising
10
+ // them here keeps the two surfaces from drifting (the same class of duplication C3 guards for
11
+ // suppression) and gives any future consumer of the route the stronger embedding signal by default.
12
+ //
13
+ // Pure of policy: the caller decides whether the query is egress-safe and passes that in. Graceful:
14
+ // no embedding model => semanticById undefined (byte-identical lexical fallback); empty access
15
+ // history => strengthById empty (the ranker zeroes its weight).
16
+ import { buildStrengthById, DEFAULT_LIST_BY_SCOPE_MAX_RESULTS, DEFAULT_STALE_CONFIDENCE_THRESHOLD, isMemorySuppressed, } from "@oscharko-dev/keiko-memory-retrieval";
17
+ import { cosineSimilarity, embedMemoryText } from "./memory-embedding.js";
18
+ // A candidate is worth scoring iff the ranker could surface it. A superset of the ranked set is
19
+ // harmless: ids the ranker filters out simply never read their semantic score.
20
+ function isSemanticRetrievalCandidate(record, nowMs) {
21
+ if (record.status === "superseded")
22
+ return false;
23
+ return !isMemorySuppressed(record, nowMs, DEFAULT_STALE_CONFIDENCE_THRESHOLD).suppressed;
24
+ }
25
+ function gatherCandidateIds(vault, scopes, nowMs) {
26
+ const ids = [];
27
+ const seen = new Set();
28
+ for (const scope of scopes) {
29
+ for (const record of vault.listMemoriesByScope(scope, {
30
+ includeExpired: true,
31
+ limit: DEFAULT_LIST_BY_SCOPE_MAX_RESULTS,
32
+ })) {
33
+ if (!isSemanticRetrievalCandidate(record, nowMs))
34
+ continue;
35
+ if (seen.has(record.id))
36
+ continue;
37
+ seen.add(record.id);
38
+ ids.push(record.id);
39
+ }
40
+ }
41
+ return ids;
42
+ }
43
+ // Per-memory semantic score map for the candidate set, or undefined when no embedding model is
44
+ // configured (query embedding null) — that undefined drives the byte-identical lexical fallback in
45
+ // the ranker. A candidate whose stored vector is missing is omitted (semantic subscore 0 for it).
46
+ // Query-cosine scores for the candidate set, computed from the already-fetched embeddings. Gated by
47
+ // the egress check (it embeds the query). Returns undefined when no model / no query embedding.
48
+ async function semanticScoresFrom(deps, queryText, candidateIds, embeddings) {
49
+ const queryEmbedding = await embedMemoryText(deps, queryText);
50
+ if (queryEmbedding === null)
51
+ return undefined;
52
+ const scores = new Map();
53
+ for (const id of candidateIds) {
54
+ const stored = embeddings.get(id);
55
+ if (stored === undefined)
56
+ continue;
57
+ scores.set(id, cosineSimilarity(queryEmbedding.vector, stored.vector));
58
+ }
59
+ return scores;
60
+ }
61
+ // Signal-fusion mode for the conversation retrieval surfaces (#204, O-F2). Opt-in via env to keep
62
+ // the release on the byte-identical weighted-sum default; set KEIKO_MEMORY_FUSION=rrf to enable
63
+ // rank-based Reciprocal Rank Fusion across both the chat and BFF paths.
64
+ export function conversationFusionMode(deps) {
65
+ return deps.env.KEIKO_MEMORY_FUSION === "rrf" ? "rrf" : "weighted-sum";
66
+ }
67
+ export async function buildConversationRetrievalSignals(deps, vault, queryText, scopes, nowMs, safeForSecondaryModel) {
68
+ const strengthById = buildStrengthById(vault.getAccessStats(), nowMs);
69
+ const candidateIds = gatherCandidateIds(vault, scopes, nowMs);
70
+ const embeddings = candidateIds.length > 0
71
+ ? vault.getEmbeddings(candidateIds)
72
+ : new Map();
73
+ const embeddingById = new Map();
74
+ for (const [id, row] of embeddings)
75
+ embeddingById.set(id, row.vector);
76
+ const semanticById = safeForSecondaryModel && queryText !== undefined && queryText.length > 0 && embeddings.size > 0
77
+ ? await semanticScoresFrom(deps, queryText, candidateIds, embeddings)
78
+ : undefined;
79
+ return {
80
+ strengthById,
81
+ embeddingById,
82
+ ...(semanticById !== undefined ? { semanticById } : {}),
83
+ };
84
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"memory-salience.d.ts","sourceRoot":"","sources":["../src/memory-salience.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,wCAAwC,CAAC;AAU3F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,OAAO,EAEL,KAAK,gCAAgC,EACtC,MAAM,kCAAkC,CAAC;AAqH1C,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;CAC5D;AAID,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,mBAAmB,EAC5B,OAAO,EAAE,gCAAgC,EACzC,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,SAAS,4BAA4B,EAAE,CAAC,CA2ClD"}
1
+ {"version":3,"file":"memory-salience.d.ts","sourceRoot":"","sources":["../src/memory-salience.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,wCAAwC,CAAC;AAU3F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,OAAO,EAEL,KAAK,gCAAgC,EACtC,MAAM,kCAAkC,CAAC;AA0H1C,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;CAC5D;AAID,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,mBAAmB,EAC5B,OAAO,EAAE,gCAAgC,EACzC,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,SAAS,4BAA4B,EAAE,CAAC,CA2ClD"}