@remnic/core 9.3.717 → 9.3.718

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 (250) hide show
  1. package/dist/access-boundary.d.ts +5 -5
  2. package/dist/access-boundary.js +9 -9
  3. package/dist/access-cli.js +23 -23
  4. package/dist/access-http.d.ts +5 -5
  5. package/dist/access-http.js +13 -13
  6. package/dist/access-mcp.d.ts +5 -5
  7. package/dist/access-mcp.js +12 -12
  8. package/dist/access-operations-batch.js +10 -10
  9. package/dist/access-operations.d.ts +5 -5
  10. package/dist/access-operations.js +11 -11
  11. package/dist/{access-service-d4Tk-Dlr.d.ts → access-service-CZbnt0t2.d.ts} +3 -3
  12. package/dist/access-service.d.ts +5 -5
  13. package/dist/access-service.js +8 -8
  14. package/dist/access-surface-catalog.d.ts +5 -5
  15. package/dist/action-confidence.d.ts +1 -1
  16. package/dist/active-memory-bridge.d.ts +1 -1
  17. package/dist/active-recall.d.ts +1 -1
  18. package/dist/active-recall.js +1 -1
  19. package/dist/behavior-learner.d.ts +1 -1
  20. package/dist/behavior-signals.d.ts +1 -1
  21. package/dist/bootstrap.d.ts +4 -4
  22. package/dist/briefing.d.ts +3 -1
  23. package/dist/briefing.js +3 -3
  24. package/dist/buffer-surprise-report.d.ts +1 -1
  25. package/dist/buffer.d.ts +1 -1
  26. package/dist/calibration.d.ts +1 -1
  27. package/dist/capabilities.d.ts +1 -1
  28. package/dist/{catalog-CbR0CsUw.d.ts → catalog-CEYlMs9X.d.ts} +1 -1
  29. package/dist/causal-behavior.d.ts +1 -1
  30. package/dist/causal-consolidation.d.ts +1 -1
  31. package/dist/causal-consolidation.js +4 -4
  32. package/dist/{chunk-JEIROMLX.js → chunk-2COL35PK.js} +95 -9
  33. package/dist/chunk-2COL35PK.js.map +1 -0
  34. package/dist/{chunk-XZTUMWKQ.js → chunk-2PTBLS2S.js} +2 -2
  35. package/dist/{chunk-2Y6LMOQQ.js → chunk-2VHMVWPU.js} +2 -2
  36. package/dist/{chunk-6UU43ZGD.js → chunk-4KZGLDJJ.js} +2 -2
  37. package/dist/{chunk-K44MIN6D.js → chunk-5RHM43BJ.js} +3 -3
  38. package/dist/{chunk-2B43BYYZ.js → chunk-72VDBALG.js} +18 -18
  39. package/dist/{chunk-UY4JMEAK.js → chunk-7EEVXW7X.js} +2 -2
  40. package/dist/{chunk-XCTDHF7U.js → chunk-A5TUPZ3N.js} +33 -1
  41. package/dist/chunk-A5TUPZ3N.js.map +1 -0
  42. package/dist/{chunk-WI4QX7GH.js → chunk-A66YRSOS.js} +2 -2
  43. package/dist/{chunk-EM22PCQR.js → chunk-ANI5DUAE.js} +8 -6
  44. package/dist/{chunk-EM22PCQR.js.map → chunk-ANI5DUAE.js.map} +1 -1
  45. package/dist/{chunk-VZQXJNBD.js → chunk-APUJIMDM.js} +2 -2
  46. package/dist/{chunk-MPHDVHIB.js → chunk-DIFU33GU.js} +2 -2
  47. package/dist/{chunk-DXGS5YOQ.js → chunk-EH27LHML.js} +2 -2
  48. package/dist/{chunk-YAINYEM3.js → chunk-F3PVXKHP.js} +25 -1398
  49. package/dist/chunk-F3PVXKHP.js.map +1 -0
  50. package/dist/{chunk-DZYARLJ7.js → chunk-HLIR3RJ7.js} +2 -2
  51. package/dist/{chunk-6FUJ2VYY.js → chunk-JAKYZGV4.js} +3 -3
  52. package/dist/{chunk-ZAVTEQFM.js → chunk-JIZGYQYH.js} +3 -3
  53. package/dist/{chunk-QOIONSOI.js → chunk-KRGC4EDX.js} +2 -2
  54. package/dist/{chunk-AJOIM6UR.js → chunk-L5ASZA2F.js} +2 -2
  55. package/dist/{chunk-I56LKMF7.js → chunk-LXSG4L55.js} +2 -2
  56. package/dist/{chunk-MSVSP5VO.js → chunk-N3DUH6OO.js} +1 -1
  57. package/dist/chunk-N3DUH6OO.js.map +1 -0
  58. package/dist/{chunk-CTUWGJRI.js → chunk-NFRBADNR.js} +166 -28
  59. package/dist/chunk-NFRBADNR.js.map +1 -0
  60. package/dist/chunk-OJU73BUX.js +1688 -0
  61. package/dist/chunk-OJU73BUX.js.map +1 -0
  62. package/dist/{chunk-JVEMUX6L.js → chunk-OZ2KQ7HF.js} +2 -2
  63. package/dist/{chunk-OUDAU3T5.js → chunk-P5IPFZVH.js} +4 -4
  64. package/dist/{chunk-KHQ433G5.js → chunk-REWQAFSA.js} +2 -2
  65. package/dist/{chunk-V7GDTQ6I.js → chunk-S35FX6MU.js} +2 -2
  66. package/dist/{chunk-MTHNJCOJ.js → chunk-S3OPGGZB.js} +2 -2
  67. package/dist/{chunk-ZXLSNG3T.js → chunk-U5H7WHTS.js} +6 -4
  68. package/dist/{chunk-ZXLSNG3T.js.map → chunk-U5H7WHTS.js.map} +1 -1
  69. package/dist/{chunk-BZAK3RBN.js → chunk-UCJ5CDTK.js} +6 -6
  70. package/dist/{chunk-BF2426TN.js → chunk-URM3HW2R.js} +2 -2
  71. package/dist/{chunk-VO4UICII.js → chunk-YTZLBBEB.js} +2 -2
  72. package/dist/{chunk-OC56UWV5.js → chunk-YYOK4GVF.js} +2 -2
  73. package/dist/{cli-insNLYs6.d.ts → cli-1orcdXOY.d.ts} +3 -3
  74. package/dist/cli.d.ts +6 -6
  75. package/dist/cli.js +27 -27
  76. package/dist/compounding/engine.d.ts +1 -1
  77. package/dist/compounding/engine.js +3 -3
  78. package/dist/compounding/preference-consolidator.d.ts +1 -1
  79. package/dist/compression-optimizer.d.ts +1 -1
  80. package/dist/config.d.ts +1 -1
  81. package/dist/config.js +1 -1
  82. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  83. package/dist/connectors/codex-materialize-runner.js +3 -3
  84. package/dist/connectors/codex-materialize.d.ts +1 -1
  85. package/dist/connectors/index.d.ts +1 -1
  86. package/dist/connectors/index.js +3 -3
  87. package/dist/consolidation-provenance-check.d.ts +1 -1
  88. package/dist/consolidation-undo.d.ts +1 -1
  89. package/dist/contradiction/index.d.ts +1 -1
  90. package/dist/conversation-index/backend.d.ts +1 -1
  91. package/dist/conversation-index/chunker.d.ts +1 -1
  92. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  93. package/dist/conversation-index/indexer.d.ts +1 -1
  94. package/dist/conversation-index/search.d.ts +1 -1
  95. package/dist/day-summary.d.ts +1 -1
  96. package/dist/delinearize.d.ts +1 -1
  97. package/dist/direct-answer-wiring.d.ts +1 -1
  98. package/dist/direct-answer.d.ts +1 -1
  99. package/dist/embedding-fallback.d.ts +1 -1
  100. package/dist/enrichment/index.d.ts +1 -1
  101. package/dist/entity-retrieval.d.ts +1 -1
  102. package/dist/entity-retrieval.js +3 -3
  103. package/dist/entity-schema.d.ts +1 -1
  104. package/dist/explicit-capture.d.ts +4 -4
  105. package/dist/extraction-faithfulness.d.ts +1 -1
  106. package/dist/extraction-judge-telemetry.d.ts +1 -1
  107. package/dist/extraction-judge-training.d.ts +1 -1
  108. package/dist/extraction-judge.d.ts +1 -1
  109. package/dist/extraction.d.ts +1 -1
  110. package/dist/fallback-llm.d.ts +1 -1
  111. package/dist/identity-continuity.d.ts +1 -1
  112. package/dist/importance.d.ts +1 -1
  113. package/dist/index.d.ts +9 -9
  114. package/dist/index.js +33 -33
  115. package/dist/intent.d.ts +1 -1
  116. package/dist/lcm/engine.d.ts +1 -1
  117. package/dist/lcm/index.d.ts +1 -1
  118. package/dist/lcm/tools.d.ts +1 -1
  119. package/dist/lifecycle.d.ts +1 -1
  120. package/dist/live-connectors-runner.d.ts +1 -1
  121. package/dist/local-llm.d.ts +1 -1
  122. package/dist/maintenance/memory-governance.d.ts +1 -1
  123. package/dist/maintenance/memory-governance.js +3 -3
  124. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  125. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  126. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  127. package/dist/memory-action-policy.d.ts +1 -1
  128. package/dist/memory-cache.d.ts +1 -1
  129. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  130. package/dist/memory-projection-store.d.ts +1 -1
  131. package/dist/memory-provenance.d.ts +1 -1
  132. package/dist/memory-worth-outcomes.d.ts +1 -1
  133. package/dist/models-json.d.ts +1 -1
  134. package/dist/namespaces/migrate.d.ts +2 -2
  135. package/dist/namespaces/migrate.js +4 -4
  136. package/dist/namespaces/principal.d.ts +1 -1
  137. package/dist/namespaces/search.d.ts +1 -1
  138. package/dist/namespaces/storage.d.ts +2 -2
  139. package/dist/namespaces/storage.js +3 -3
  140. package/dist/native-knowledge.d.ts +1 -1
  141. package/dist/operator-toolkit.d.ts +1 -1
  142. package/dist/operator-toolkit.js +9 -9
  143. package/dist/orchestration/maintenance.d.ts +2 -2
  144. package/dist/orchestration/maintenance.js +5 -5
  145. package/dist/{orchestrator-XPKhY2OV.d.ts → orchestrator-kOxredSr.d.ts} +20 -3
  146. package/dist/orchestrator.d.ts +4 -4
  147. package/dist/orchestrator.js +16 -16
  148. package/dist/patterns-cli.d.ts +1 -1
  149. package/dist/policy-runtime.d.ts +1 -1
  150. package/dist/provenance.d.ts +1 -1
  151. package/dist/qmd-recall-cache.d.ts +1 -1
  152. package/dist/qmd.d.ts +1 -1
  153. package/dist/recall-disclosure-escalation.d.ts +1 -1
  154. package/dist/recall-explain-renderer.d.ts +1 -1
  155. package/dist/recall-explain-renderer.js +3 -3
  156. package/dist/recall-planner-llm.d.ts +1 -1
  157. package/dist/recall-state.d.ts +1 -1
  158. package/dist/recall-tag-filter.d.ts +1 -1
  159. package/dist/recall-xray-cli.d.ts +1 -1
  160. package/dist/recall-xray-cli.js +4 -4
  161. package/dist/recall-xray-renderer.d.ts +1 -1
  162. package/dist/recall-xray-renderer.js +3 -3
  163. package/dist/recall-xray.d.ts +1 -1
  164. package/dist/recall-xray.js +2 -2
  165. package/dist/resolve-auth-token.d.ts +1 -1
  166. package/dist/resume-bundles.js +2 -2
  167. package/dist/retrieval-agents.d.ts +1 -1
  168. package/dist/retrieval-tiers.d.ts +1 -1
  169. package/dist/routing/engine.d.ts +1 -1
  170. package/dist/routing/store.d.ts +1 -1
  171. package/dist/schemas.d.ts +22 -22
  172. package/dist/search/embed-helper.d.ts +1 -1
  173. package/dist/search/factory.d.ts +1 -1
  174. package/dist/search/index.d.ts +1 -1
  175. package/dist/search/lancedb-backend.d.ts +1 -1
  176. package/dist/search/meilisearch-backend.d.ts +1 -1
  177. package/dist/search/noop-backend.d.ts +1 -1
  178. package/dist/search/orama-backend.d.ts +1 -1
  179. package/dist/search/port.d.ts +1 -1
  180. package/dist/search/remote-backend.d.ts +1 -1
  181. package/dist/{semantic-consolidation-DSjMDZ1u.d.ts → semantic-consolidation-B58v3S-a.d.ts} +1 -1
  182. package/dist/semantic-consolidation.d.ts +2 -2
  183. package/dist/semantic-consolidation.js +4 -4
  184. package/dist/semantic-rule-promotion.js +3 -3
  185. package/dist/semantic-rule-verifier.d.ts +1 -1
  186. package/dist/semantic-rule-verifier.js +3 -3
  187. package/dist/session-observer-bands.d.ts +1 -1
  188. package/dist/session-observer-state.d.ts +1 -1
  189. package/dist/shared-context/manager.d.ts +1 -1
  190. package/dist/signal.d.ts +1 -1
  191. package/dist/storage.d.ts +1 -1
  192. package/dist/storage.js +2 -2
  193. package/dist/summarizer.d.ts +1 -1
  194. package/dist/summary-snapshot.d.ts +1 -1
  195. package/dist/temporal-supersession.d.ts +1 -1
  196. package/dist/temporal-validity.d.ts +1 -1
  197. package/dist/threading.d.ts +1 -1
  198. package/dist/tier-migration.d.ts +1 -1
  199. package/dist/tier-routing.d.ts +1 -1
  200. package/dist/topics.d.ts +1 -1
  201. package/dist/transcript.d.ts +1 -1
  202. package/dist/transfer/types.d.ts +12 -12
  203. package/dist/{types-D9dwzU_x.d.ts → types-ACmDOScJ.d.ts} +36 -0
  204. package/dist/types.d.ts +1 -1
  205. package/dist/types.js +1 -1
  206. package/dist/utility-runtime.d.ts +1 -1
  207. package/dist/verified-recall.js +3 -3
  208. package/package.json +2 -2
  209. package/src/briefing.ts +32 -0
  210. package/src/config.ts +32 -0
  211. package/src/correction/index.ts +24 -0
  212. package/src/correction/passive-capture.test.ts +500 -0
  213. package/src/correction/passive-capture.ts +335 -0
  214. package/src/correction/passive-correction-detector.test.ts +263 -0
  215. package/src/correction/passive-correction-detector.ts +325 -0
  216. package/src/correction/passive-correction-notifications.ts +123 -0
  217. package/src/orchestrator.ts +196 -12
  218. package/src/types.ts +36 -0
  219. package/dist/chunk-2KF4HDG2.js +0 -24
  220. package/dist/chunk-2KF4HDG2.js.map +0 -1
  221. package/dist/chunk-CTUWGJRI.js.map +0 -1
  222. package/dist/chunk-JEIROMLX.js.map +0 -1
  223. package/dist/chunk-MSVSP5VO.js.map +0 -1
  224. package/dist/chunk-XCTDHF7U.js.map +0 -1
  225. package/dist/chunk-YAINYEM3.js.map +0 -1
  226. /package/dist/{chunk-XZTUMWKQ.js.map → chunk-2PTBLS2S.js.map} +0 -0
  227. /package/dist/{chunk-2Y6LMOQQ.js.map → chunk-2VHMVWPU.js.map} +0 -0
  228. /package/dist/{chunk-6UU43ZGD.js.map → chunk-4KZGLDJJ.js.map} +0 -0
  229. /package/dist/{chunk-K44MIN6D.js.map → chunk-5RHM43BJ.js.map} +0 -0
  230. /package/dist/{chunk-2B43BYYZ.js.map → chunk-72VDBALG.js.map} +0 -0
  231. /package/dist/{chunk-UY4JMEAK.js.map → chunk-7EEVXW7X.js.map} +0 -0
  232. /package/dist/{chunk-WI4QX7GH.js.map → chunk-A66YRSOS.js.map} +0 -0
  233. /package/dist/{chunk-VZQXJNBD.js.map → chunk-APUJIMDM.js.map} +0 -0
  234. /package/dist/{chunk-MPHDVHIB.js.map → chunk-DIFU33GU.js.map} +0 -0
  235. /package/dist/{chunk-DXGS5YOQ.js.map → chunk-EH27LHML.js.map} +0 -0
  236. /package/dist/{chunk-DZYARLJ7.js.map → chunk-HLIR3RJ7.js.map} +0 -0
  237. /package/dist/{chunk-6FUJ2VYY.js.map → chunk-JAKYZGV4.js.map} +0 -0
  238. /package/dist/{chunk-ZAVTEQFM.js.map → chunk-JIZGYQYH.js.map} +0 -0
  239. /package/dist/{chunk-QOIONSOI.js.map → chunk-KRGC4EDX.js.map} +0 -0
  240. /package/dist/{chunk-AJOIM6UR.js.map → chunk-L5ASZA2F.js.map} +0 -0
  241. /package/dist/{chunk-I56LKMF7.js.map → chunk-LXSG4L55.js.map} +0 -0
  242. /package/dist/{chunk-JVEMUX6L.js.map → chunk-OZ2KQ7HF.js.map} +0 -0
  243. /package/dist/{chunk-OUDAU3T5.js.map → chunk-P5IPFZVH.js.map} +0 -0
  244. /package/dist/{chunk-KHQ433G5.js.map → chunk-REWQAFSA.js.map} +0 -0
  245. /package/dist/{chunk-V7GDTQ6I.js.map → chunk-S35FX6MU.js.map} +0 -0
  246. /package/dist/{chunk-MTHNJCOJ.js.map → chunk-S3OPGGZB.js.map} +0 -0
  247. /package/dist/{chunk-BZAK3RBN.js.map → chunk-UCJ5CDTK.js.map} +0 -0
  248. /package/dist/{chunk-BF2426TN.js.map → chunk-URM3HW2R.js.map} +0 -0
  249. /package/dist/{chunk-VO4UICII.js.map → chunk-YTZLBBEB.js.map} +0 -0
  250. /package/dist/{chunk-OC56UWV5.js.map → chunk-YYOK4GVF.js.map} +0 -0
@@ -0,0 +1,500 @@
1
+ /**
2
+ * passive-capture.test.ts — pipeline tests for passive correction capture
3
+ * (issue #1581 PR 2–3).
4
+ *
5
+ * Tests: queue mode, auto mode + all guards, dedup, notifications, telemetry.
6
+ * Uses mock deps — no real CorrectionService or storage.
7
+ */
8
+
9
+ import { strict as assert } from "node:assert";
10
+ import { test } from "node:test";
11
+ import { rm, mkdir } from "node:fs/promises";
12
+ import path from "node:path";
13
+ import os from "node:os";
14
+ import {
15
+ capturePassiveCorrections,
16
+ evaluateAutoApplyGuards,
17
+ emptyTelemetry,
18
+ type PassiveCaptureConfig,
19
+ type PassiveCaptureContext,
20
+ type PassiveCaptureDeps,
21
+ } from "./passive-capture.js";
22
+ import type { PassiveCorrection } from "./passive-correction-detector.js";
23
+ import type {
24
+ CorrectionAction,
25
+ CorrectionClassification,
26
+ CorrectionOutcome,
27
+ CorrectionPlan,
28
+ } from "./correction-contract.js";
29
+ import {
30
+ enqueuePassiveCorrectionNotification,
31
+ drainPassiveCorrectionNotifications,
32
+ } from "./passive-correction-notifications.js";
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Helpers
36
+ // ---------------------------------------------------------------------------
37
+
38
+ function makeCorrection(overrides: Partial<PassiveCorrection> = {}): PassiveCorrection {
39
+ return {
40
+ targetHint: "Redis usage",
41
+ correctedAssertion: "we don't use Redis anymore",
42
+ polarity: "retract",
43
+ handles: [],
44
+ confidence: 0.9,
45
+ sourceExcerpt: "we don't use Redis anymore",
46
+ turnIndex: 0,
47
+ ...overrides,
48
+ };
49
+ }
50
+
51
+ function makePlan(overrides: Partial<CorrectionPlan> = {}): CorrectionPlan {
52
+ return {
53
+ planId: "corr-test-001",
54
+ request: { text: "we don't use Redis anymore", namespace: "default" },
55
+ namespace: "default",
56
+ affected: [{ memoryId: "mem-001", path: "facts/redis.md", excerpt: "Uses Redis for caching", why: "matches correction" }],
57
+ classification: "outdated",
58
+ actions: [{ kind: "retract", memoryId: "mem-001" }],
59
+ diff: "- Uses Redis for caching",
60
+ confidence: 0.9,
61
+ warnings: [],
62
+ createdAt: new Date().toISOString(),
63
+ expiresAt: new Date(Date.now() + 86400000).toISOString(),
64
+ status: "pending",
65
+ ...overrides,
66
+ };
67
+ }
68
+
69
+ function mockDeps(
70
+ plan: CorrectionPlan,
71
+ opts: { applyOutcome?: CorrectionOutcome; appliedPlans: string[] } = { appliedPlans: [] },
72
+ ): PassiveCaptureDeps {
73
+ return {
74
+ planCorrection: async () => plan,
75
+ applyCorrection: async (planId) => {
76
+ opts.appliedPlans.push(planId);
77
+ return (
78
+ opts.applyOutcome ?? {
79
+ planId,
80
+ status: "applied" as const,
81
+ results: [{ action: plan.actions[0], status: "applied" as const }],
82
+ auditMemoryId: "audit-001",
83
+ appliedAt: new Date().toISOString(),
84
+ }
85
+ );
86
+ },
87
+ storageDir: async () => "/tmp/test-passive-capture",
88
+ };
89
+ }
90
+
91
+ const QUEUE_CONFIG: PassiveCaptureConfig = {
92
+ mode: "queue",
93
+ confidenceFloor: 0.85,
94
+ autoApplyMaxAffected: 2,
95
+ };
96
+
97
+ const AUTO_CONFIG: PassiveCaptureConfig = {
98
+ mode: "auto",
99
+ confidenceFloor: 0.85,
100
+ autoApplyMaxAffected: 2,
101
+ };
102
+
103
+ const LIVE_CTX: PassiveCaptureContext = {
104
+ correctionEnabled: true,
105
+ isLiveSession: true,
106
+ bufferKey: "session-1",
107
+ namespace: "default",
108
+ };
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // Queue mode
112
+ // ---------------------------------------------------------------------------
113
+
114
+ test("queue mode: plans corrections and leaves them pending", async () => {
115
+ const plan = makePlan();
116
+ const deps = mockDeps(plan);
117
+ const dedup = new Set<string>();
118
+
119
+ const result = await capturePassiveCorrections([makeCorrection()], LIVE_CTX, QUEUE_CONFIG, deps, dedup);
120
+
121
+ assert.strictEqual(result.telemetry.detected, 1);
122
+ assert.strictEqual(result.telemetry.queued, 1);
123
+ assert.strictEqual(result.telemetry.autoApplied, 0);
124
+ assert.strictEqual(result.plans.length, 1);
125
+ });
126
+
127
+ test("queue mode: does not auto-apply", async () => {
128
+ const appliedPlans: string[] = [];
129
+ const plan = makePlan();
130
+ const deps = mockDeps(plan, { appliedPlans });
131
+ const dedup = new Set<string>();
132
+
133
+ await capturePassiveCorrections([makeCorrection()], LIVE_CTX, QUEUE_CONFIG, deps, dedup);
134
+
135
+ assert.strictEqual(appliedPlans.length, 0);
136
+ });
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // Dedup
140
+ // ---------------------------------------------------------------------------
141
+
142
+ test("dedup: same correction, two flushes → one plan", async () => {
143
+ const plan = makePlan();
144
+ const deps = mockDeps(plan);
145
+ const dedup = new Set<string>();
146
+
147
+ const correction = makeCorrection();
148
+ const r1 = await capturePassiveCorrections([correction], LIVE_CTX, QUEUE_CONFIG, deps, dedup);
149
+ const r2 = await capturePassiveCorrections([correction], LIVE_CTX, QUEUE_CONFIG, deps, dedup);
150
+
151
+ assert.strictEqual(r1.telemetry.queued, 1);
152
+ assert.strictEqual(r2.telemetry.queued, 0);
153
+ assert.strictEqual(r2.telemetry.detected, 1);
154
+ });
155
+
156
+ test("dedup: different bufferKey → both processed", async () => {
157
+ const plan = makePlan();
158
+ const deps = mockDeps(plan);
159
+ const dedup = new Set<string>();
160
+
161
+ const correction = makeCorrection();
162
+ await capturePassiveCorrections([correction], { ...LIVE_CTX, bufferKey: "session-A" }, QUEUE_CONFIG, deps, dedup);
163
+ await capturePassiveCorrections([correction], { ...LIVE_CTX, bufferKey: "session-B" }, QUEUE_CONFIG, deps, dedup);
164
+
165
+ assert.strictEqual(dedup.size, 2);
166
+ });
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // Auto mode + guards
170
+ // ---------------------------------------------------------------------------
171
+
172
+ test("auto mode: applies when all guards pass", async () => {
173
+ const appliedPlans: string[] = [];
174
+ const plan = makePlan({ confidence: 0.9, classification: "outdated" });
175
+ const deps = mockDeps(plan, { appliedPlans });
176
+ const dedup = new Set<string>();
177
+
178
+ const result = await capturePassiveCorrections([makeCorrection()], LIVE_CTX, AUTO_CONFIG, deps, dedup);
179
+
180
+ assert.strictEqual(result.telemetry.autoApplied, 1);
181
+ assert.deepStrictEqual(appliedPlans, ["corr-test-001"]);
182
+ });
183
+
184
+ test("auto mode: suppressed — confidence below floor → queued", async () => {
185
+ const appliedPlans: string[] = [];
186
+ const plan = makePlan({ confidence: 0.5 });
187
+ const deps = mockDeps(plan, { appliedPlans });
188
+ const dedup = new Set<string>();
189
+
190
+ const result = await capturePassiveCorrections([makeCorrection()], LIVE_CTX, AUTO_CONFIG, deps, dedup);
191
+
192
+ assert.strictEqual(result.telemetry.autoApplied, 0);
193
+ assert.strictEqual(result.telemetry.queued, 1);
194
+ assert.strictEqual(result.telemetry.suppressedReasons.confidence_below_floor, 1);
195
+ assert.strictEqual(appliedPlans.length, 0);
196
+ });
197
+
198
+ test("auto mode: suppressed — affected too large → queued", async () => {
199
+ const appliedPlans: string[] = [];
200
+ const plan = makePlan({
201
+ affected: [
202
+ { memoryId: "m1", path: "a.md", excerpt: "x", why: "y" },
203
+ { memoryId: "m2", path: "b.md", excerpt: "x", why: "y" },
204
+ { memoryId: "m3", path: "c.md", excerpt: "x", why: "y" },
205
+ ],
206
+ });
207
+ const deps = mockDeps(plan, { appliedPlans });
208
+ const dedup = new Set<string>();
209
+
210
+ const result = await capturePassiveCorrections([makeCorrection()], LIVE_CTX, AUTO_CONFIG, deps, dedup);
211
+
212
+ assert.strictEqual(result.telemetry.autoApplied, 0);
213
+ assert.strictEqual(result.telemetry.suppressedReasons.affected_too_large, 1);
214
+ });
215
+
216
+ test("auto mode: suppressed — disallowed classification → queued", async () => {
217
+ const appliedPlans: string[] = [];
218
+ const plan = makePlan({ classification: "wrong_scope" as CorrectionClassification });
219
+ const deps = mockDeps(plan, { appliedPlans });
220
+ const dedup = new Set<string>();
221
+
222
+ const result = await capturePassiveCorrections([makeCorrection()], LIVE_CTX, AUTO_CONFIG, deps, dedup);
223
+
224
+ assert.strictEqual(result.telemetry.autoApplied, 0);
225
+ assert.strictEqual(result.telemetry.suppressedReasons.classification_not_allowed, 1);
226
+ });
227
+
228
+ test("auto mode: suppressed — disallowed action kind (rescope) → queued", async () => {
229
+ const appliedPlans: string[] = [];
230
+ const plan = makePlan({
231
+ actions: [{ kind: "rescope", memoryId: "m1", toNamespace: "other" } as CorrectionAction],
232
+ });
233
+ const deps = mockDeps(plan, { appliedPlans });
234
+ const dedup = new Set<string>();
235
+
236
+ const result = await capturePassiveCorrections([makeCorrection()], LIVE_CTX, AUTO_CONFIG, deps, dedup);
237
+
238
+ assert.strictEqual(result.telemetry.autoApplied, 0);
239
+ assert.strictEqual(result.telemetry.suppressedReasons.disallowed_action_kind, 1);
240
+ });
241
+
242
+ test("auto mode: suppressed — empty action list (no-op plan) → queued (review: empty-plan guard)", async () => {
243
+ const appliedPlans: string[] = [];
244
+ const plan = makePlan({ actions: [], confidence: 0.9 });
245
+ const deps = mockDeps(plan, { appliedPlans });
246
+ const dedup = new Set<string>();
247
+
248
+ const result = await capturePassiveCorrections([makeCorrection()], LIVE_CTX, AUTO_CONFIG, deps, dedup);
249
+
250
+ assert.strictEqual(result.telemetry.autoApplied, 0);
251
+ assert.strictEqual(result.telemetry.suppressedReasons.empty_plan, 1);
252
+ assert.strictEqual(appliedPlans.length, 0, "empty plan must not be auto-applied");
253
+ });
254
+
255
+ test("auto mode: suppressed — non-live session (replay) → queued", async () => {
256
+ const appliedPlans: string[] = [];
257
+ const plan = makePlan();
258
+ const deps = mockDeps(plan, { appliedPlans });
259
+ const dedup = new Set<string>();
260
+
261
+ const result = await capturePassiveCorrections(
262
+ [makeCorrection()],
263
+ { ...LIVE_CTX, isLiveSession: false },
264
+ AUTO_CONFIG,
265
+ deps,
266
+ dedup,
267
+ );
268
+
269
+ assert.strictEqual(result.telemetry.autoApplied, 0);
270
+ assert.strictEqual(result.telemetry.suppressedReasons.non_live_session, 1);
271
+ });
272
+
273
+ test("auto mode: auto-applied correction enqueues notification", async () => {
274
+ const plan = makePlan({ confidence: 0.9 });
275
+ const tmpDir = path.join(os.tmpdir(), `test-passive-notify-${Date.now()}`);
276
+ const deps: PassiveCaptureDeps = {
277
+ planCorrection: async () => plan,
278
+ applyCorrection: async (planId) => ({
279
+ planId,
280
+ status: "applied",
281
+ results: [],
282
+ auditMemoryId: "audit",
283
+ appliedAt: new Date().toISOString(),
284
+ }),
285
+ storageDir: async () => tmpDir,
286
+ };
287
+ const dedup = new Set<string>();
288
+
289
+ await capturePassiveCorrections([makeCorrection()], LIVE_CTX, AUTO_CONFIG, deps, dedup);
290
+
291
+ const notifications = await drainPassiveCorrectionNotifications(tmpDir);
292
+ assert.strictEqual(notifications.length, 1);
293
+ assert.strictEqual(notifications[0].planId, "corr-test-001");
294
+ assert.ok(notifications[0].undoCommand.includes("auto-applied"));
295
+ assert.ok(notifications[0].undoCommand.includes("corr-test-001"));
296
+
297
+ await rm(tmpDir, { recursive: true, force: true });
298
+ });
299
+
300
+ test("auto mode: partial outcome queues instead of counting as applied (review: partial outcomes)", async () => {
301
+ const plan = makePlan({ confidence: 0.9 });
302
+ const deps: PassiveCaptureDeps = {
303
+ planCorrection: async () => plan,
304
+ applyCorrection: async (planId) => ({
305
+ planId,
306
+ status: "partial" as const,
307
+ results: [{ action: plan.actions[0], status: "failed" as const }],
308
+ auditMemoryId: "audit",
309
+ appliedAt: new Date().toISOString(),
310
+ }),
311
+ storageDir: async () => "/tmp/test-passive-capture",
312
+ };
313
+ const dedup = new Set<string>();
314
+
315
+ const result = await capturePassiveCorrections([makeCorrection()], LIVE_CTX, AUTO_CONFIG, deps, dedup);
316
+
317
+ assert.strictEqual(result.telemetry.autoApplied, 0, "partial outcome must not count as auto-applied");
318
+ assert.strictEqual(result.telemetry.queued, 0, "partial outcome does not re-queue (plan is consumed)");
319
+ });
320
+
321
+ // ---------------------------------------------------------------------------
322
+ // evaluateAutoApplyGuards — direct unit tests
323
+ // ---------------------------------------------------------------------------
324
+
325
+ test("guards: returns null when all pass", () => {
326
+ const config: PassiveCaptureConfig = { mode: "auto", confidenceFloor: 0.85, autoApplyMaxAffected: 2 };
327
+ const plan = makePlan({ confidence: 0.9 });
328
+ assert.strictEqual(evaluateAutoApplyGuards(plan, config, true), null);
329
+ });
330
+
331
+ test("guards: non_live_session for replay", () => {
332
+ const config: PassiveCaptureConfig = { mode: "auto", confidenceFloor: 0.85, autoApplyMaxAffected: 2 };
333
+ assert.strictEqual(evaluateAutoApplyGuards(makePlan(), config, false), "non_live_session");
334
+ });
335
+
336
+ test("guards: confidence_below_floor", () => {
337
+ const config: PassiveCaptureConfig = { mode: "auto", confidenceFloor: 0.85, autoApplyMaxAffected: 2 };
338
+ assert.strictEqual(evaluateAutoApplyGuards(makePlan({ confidence: 0.5 }), config, true), "confidence_below_floor");
339
+ });
340
+
341
+ test("guards: classification_not_allowed for wrong_scope", () => {
342
+ const config: PassiveCaptureConfig = { mode: "auto", confidenceFloor: 0.85, autoApplyMaxAffected: 2 };
343
+ assert.strictEqual(evaluateAutoApplyGuards(makePlan({ classification: "wrong_scope" }), config, true), "classification_not_allowed");
344
+ });
345
+
346
+ test("guards: affected_too_large", () => {
347
+ const config: PassiveCaptureConfig = { mode: "auto", confidenceFloor: 0.85, autoApplyMaxAffected: 2 };
348
+ const plan = makePlan({
349
+ affected: [
350
+ { memoryId: "m1", path: "a", excerpt: "x", why: "y" },
351
+ { memoryId: "m2", path: "b", excerpt: "x", why: "y" },
352
+ { memoryId: "m3", path: "c", excerpt: "x", why: "y" },
353
+ ],
354
+ });
355
+ assert.strictEqual(evaluateAutoApplyGuards(plan, config, true), "affected_too_large");
356
+ });
357
+
358
+ test("guards: disallowed_action_kind for redaction_rule", () => {
359
+ const config: PassiveCaptureConfig = { mode: "auto", confidenceFloor: 0.85, autoApplyMaxAffected: 2 };
360
+ const plan = makePlan({ actions: [{ kind: "redaction_rule", pattern: "secret" }] });
361
+ assert.strictEqual(evaluateAutoApplyGuards(plan, config, true), "disallowed_action_kind");
362
+ });
363
+
364
+ // ---------------------------------------------------------------------------
365
+ // Notifications — drain-once semantics
366
+ // ---------------------------------------------------------------------------
367
+
368
+ test("notifications: drains once then returns empty", async () => {
369
+ const tmpDir = path.join(os.tmpdir(), `test-passive-notify-${Date.now()}-${Math.random().toString(36).slice(2)}`);
370
+ await mkdir(tmpDir, { recursive: true });
371
+
372
+ await enqueuePassiveCorrectionNotification(tmpDir, {
373
+ planId: "p1", summary: "test", undoCommand: "auto-applied (plan p1)", createdAt: new Date().toISOString(),
374
+ });
375
+
376
+ const first = await drainPassiveCorrectionNotifications(tmpDir);
377
+ const second = await drainPassiveCorrectionNotifications(tmpDir);
378
+
379
+ assert.strictEqual(first.length, 1);
380
+ assert.strictEqual(second.length, 0);
381
+
382
+ await rm(tmpDir, { recursive: true, force: true });
383
+ });
384
+
385
+ test("notifications: accumulates multiple", async () => {
386
+ const tmpDir = path.join(os.tmpdir(), `test-passive-notify-${Date.now()}-${Math.random().toString(36).slice(2)}`);
387
+ await mkdir(tmpDir, { recursive: true });
388
+
389
+ await enqueuePassiveCorrectionNotification(tmpDir, {
390
+ planId: "p1", summary: "first", undoCommand: "auto-applied (plan p1)", createdAt: new Date().toISOString(),
391
+ });
392
+ await enqueuePassiveCorrectionNotification(tmpDir, {
393
+ planId: "p2", summary: "second", undoCommand: "auto-applied (plan p2)", createdAt: new Date().toISOString(),
394
+ });
395
+
396
+ const drained = await drainPassiveCorrectionNotifications(tmpDir);
397
+ assert.strictEqual(drained.length, 2);
398
+ assert.deepStrictEqual(drained.map((n) => n.planId), ["p1", "p2"]);
399
+
400
+ await rm(tmpDir, { recursive: true, force: true });
401
+ });
402
+
403
+ test("notifications: returns empty when no file exists", async () => {
404
+ const tmpDir = path.join(os.tmpdir(), `test-passive-notify-${Date.now()}-${Math.random().toString(36).slice(2)}`);
405
+ const drained = await drainPassiveCorrectionNotifications(tmpDir);
406
+ assert.deepStrictEqual(drained, []);
407
+ });
408
+
409
+ // ---------------------------------------------------------------------------
410
+ // Telemetry
411
+ // ---------------------------------------------------------------------------
412
+
413
+ test("telemetry: emptyTelemetry returns zeroed counters", () => {
414
+ const t = emptyTelemetry();
415
+ assert.strictEqual(t.detected, 0);
416
+ assert.strictEqual(t.queued, 0);
417
+ assert.strictEqual(t.autoApplied, 0);
418
+ assert.strictEqual(Object.keys(t.suppressedReasons).length, 0);
419
+ });
420
+
421
+ // ---------------------------------------------------------------------------
422
+ // Review fixes (cursor threads)
423
+ // ---------------------------------------------------------------------------
424
+
425
+ test("handle resolution: resolveHandle dep converts [m:xxxx] to memory ids (review)", async () => {
426
+ // The planner resolveTargetMemories expects concrete memory ids, not raw
427
+ // [m:xxxx] tokens. The capture loop must resolve handles via the resolveHandle
428
+ // dep before building the CorrectionRequest; unresolvable handles are dropped
429
+ // so the planner falls back to text search.
430
+ let capturedTargetIds: string[] | undefined;
431
+ const deps: PassiveCaptureDeps = {
432
+ ...mockDeps(makePlan()),
433
+ resolveHandle: (ref) => (ref === "[m:4f2a]" ? "fact-1700000000-abcd" : null),
434
+ planCorrection: async (req) => {
435
+ capturedTargetIds = req.targetIds as string[] | undefined;
436
+ return makePlan();
437
+ },
438
+ };
439
+ const correction = makeCorrection({
440
+ handles: ["[m:4f2a]"],
441
+ correctedAssertion: "[m:4f2a] is wrong",
442
+ polarity: "retract",
443
+ });
444
+ await capturePassiveCorrections(
445
+ [correction],
446
+ { ...LIVE_CTX, sessionKey: "sess-1" },
447
+ QUEUE_CONFIG,
448
+ deps,
449
+ new Set(),
450
+ );
451
+ assert.deepStrictEqual(
452
+ capturedTargetIds,
453
+ ["fact-1700000000-abcd"],
454
+ "handle must be resolved to a concrete memory id before planning",
455
+ );
456
+ });
457
+
458
+ test("handle resolution: unresolvable handle is dropped (planner falls back to text)", async () => {
459
+ let capturedTargetIds: string[] | undefined;
460
+ const deps: PassiveCaptureDeps = {
461
+ ...mockDeps(makePlan()),
462
+ resolveHandle: () => null,
463
+ planCorrection: async (req) => {
464
+ capturedTargetIds = req.targetIds as string[] | undefined;
465
+ return makePlan();
466
+ },
467
+ };
468
+ const correction = makeCorrection({ handles: ["[m:dead]"] });
469
+ await capturePassiveCorrections(
470
+ [correction],
471
+ { ...LIVE_CTX, sessionKey: "sess-1" },
472
+ QUEUE_CONFIG,
473
+ deps,
474
+ new Set(),
475
+ );
476
+ assert.strictEqual(capturedTargetIds, undefined, "unresolvable handle must not reach the planner");
477
+ });
478
+
479
+ test("dedup: failed plan can be retried on a later flush (review)", async () => {
480
+ // The fingerprint is recorded only AFTER a successful plan, so a transient
481
+ // planning failure does not permanently block the correction.
482
+ let attempts = 0;
483
+ const deps: PassiveCaptureDeps = {
484
+ ...mockDeps(makePlan()),
485
+ planCorrection: async () => {
486
+ attempts += 1;
487
+ if (attempts === 1) throw new Error("transient planning failure");
488
+ return makePlan();
489
+ },
490
+ };
491
+ const dedup = new Set<string>();
492
+ const correction = makeCorrection();
493
+ // First flush: planning throws — fingerprint NOT recorded.
494
+ const r1 = await capturePassiveCorrections([correction], LIVE_CTX, QUEUE_CONFIG, deps, dedup);
495
+ assert.strictEqual(r1.telemetry.queued, 0, "failed plan must not count as queued");
496
+ // Second flush: same correction, retry succeeds.
497
+ const r2 = await capturePassiveCorrections([correction], LIVE_CTX, QUEUE_CONFIG, deps, dedup);
498
+ assert.strictEqual(r2.telemetry.queued, 1, "failed plan must be retriable");
499
+ assert.strictEqual(attempts, 2);
500
+ });