@remnic/core 9.66.11 → 9.67.0

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 (212) hide show
  1. package/dist/access-admin-ops-surface.js +23 -23
  2. package/dist/access-authorization-probe.js +24 -24
  3. package/dist/access-boundary.js +23 -23
  4. package/dist/access-cli.js +81 -81
  5. package/dist/access-extraction-force-flush.js +23 -23
  6. package/dist/access-http-query.js +25 -25
  7. package/dist/access-http.js +41 -41
  8. package/dist/access-identity-continuity-surface.js +23 -23
  9. package/dist/access-lcm-surface.js +23 -23
  10. package/dist/access-mcp.js +32 -32
  11. package/dist/access-namespace-preflight.js +23 -23
  12. package/dist/access-observe-write-surface.js +23 -23
  13. package/dist/access-operations-batch.js +26 -26
  14. package/dist/access-operations.js +31 -31
  15. package/dist/access-recall-concurrency.js +23 -23
  16. package/dist/access-recall-response.js +23 -23
  17. package/dist/access-recall-surface.js +23 -23
  18. package/dist/access-schema.js +4 -4
  19. package/dist/access-service.js +23 -23
  20. package/dist/access-surface-catalog.js +25 -25
  21. package/dist/adapters/index.js +4 -4
  22. package/dist/adapters/registry.js +2 -2
  23. package/dist/briefing.js +10 -10
  24. package/dist/buffer.js +4 -4
  25. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-GWVG7LGC.js} +2 -2
  26. package/dist/causal-consolidation.js +11 -11
  27. package/dist/{chunk-XN33JLRF.js → chunk-46XRYYOJ.js} +3 -3
  28. package/dist/{chunk-WD424KYT.js → chunk-4L7OKRGP.js} +2 -2
  29. package/dist/{chunk-XZ7QLOTJ.js → chunk-6KGBI54C.js} +2 -2
  30. package/dist/{chunk-245QROHZ.js → chunk-6SRUPUTY.js} +7 -7
  31. package/dist/{chunk-ZIGSNLYI.js → chunk-A6HVSRGM.js} +3 -3
  32. package/dist/{chunk-JBPKEARU.js → chunk-AU7Q3LSC.js} +4 -4
  33. package/dist/{chunk-7BS5UVXA.js → chunk-AWMS3IFB.js} +3 -3
  34. package/dist/{chunk-HJQZQUPC.js → chunk-BE4Y6GK6.js} +6 -6
  35. package/dist/{chunk-D3AUQXTJ.js → chunk-BT7AQZQX.js} +2 -2
  36. package/dist/{chunk-IPLYGWQF.js → chunk-BUKK5SWA.js} +6 -6
  37. package/dist/{chunk-CEH2AFXG.js → chunk-C24UZSFW.js} +6 -6
  38. package/dist/{chunk-HCERYDRI.js → chunk-CJTZO5MW.js} +2 -2
  39. package/dist/{chunk-4QKWBVEE.js → chunk-CR73YTWC.js} +13 -13
  40. package/dist/{chunk-LTWFU6VJ.js → chunk-DDAHG5ZD.js} +2 -2
  41. package/dist/{chunk-S47XNAWD.js → chunk-EU5PDHU2.js} +2 -2
  42. package/dist/{chunk-KT3ERTF6.js → chunk-F3H7VNM4.js} +2 -2
  43. package/dist/{chunk-EG4FHU5C.js → chunk-FJP6MVRK.js} +2 -2
  44. package/dist/{chunk-7FLINMUE.js → chunk-GEHEBLVY.js} +2 -2
  45. package/dist/{chunk-G3B3LP6A.js → chunk-GRZYF6KK.js} +14 -14
  46. package/dist/{chunk-E2PORO7O.js → chunk-H6VYVOVP.js} +2 -2
  47. package/dist/{chunk-QKTGCL6M.js → chunk-HGRRJDUH.js} +2 -2
  48. package/dist/{chunk-J2R275FN.js → chunk-HQJM3BKH.js} +2 -2
  49. package/dist/{chunk-ZRQPX2DW.js → chunk-I3X5NWDU.js} +2 -2
  50. package/dist/{chunk-TGKLVAML.js → chunk-I7FVQMAA.js} +2 -2
  51. package/dist/{chunk-UWOA5L6N.js → chunk-IDPFBRPJ.js} +35 -35
  52. package/dist/{chunk-CNZVEU4X.js → chunk-IE7N2YK6.js} +2 -2
  53. package/dist/{chunk-PVV42HM4.js → chunk-IYFL3534.js} +2 -2
  54. package/dist/{chunk-X5SDZMO7.js → chunk-JGXMA6HL.js} +2 -2
  55. package/dist/{chunk-SQEIPTDV.js → chunk-JKXPFIPC.js} +2 -2
  56. package/dist/{chunk-Y2ECPAJI.js → chunk-K5JFIA44.js} +7 -7
  57. package/dist/{chunk-JNZSNJ43.js → chunk-K7VTD5AZ.js} +2 -2
  58. package/dist/{chunk-E62M4H4M.js → chunk-KDCPIXIY.js} +2 -2
  59. package/dist/{chunk-TS37MXC7.js → chunk-KZAN4JXY.js} +2 -2
  60. package/dist/{chunk-227UFSEQ.js → chunk-LEPOP46W.js} +4 -4
  61. package/dist/{chunk-5E7KXID5.js → chunk-M3BFW3VX.js} +1 -1
  62. package/dist/{chunk-PWVRMD37.js → chunk-MUYJQ273.js} +2 -2
  63. package/dist/{chunk-5KVMT25I.js → chunk-MWLBRFLY.js} +2 -2
  64. package/dist/{chunk-2OXBS2LV.js → chunk-NROLO2HY.js} +2 -2
  65. package/dist/{chunk-6H7JWYTC.js → chunk-O2HDDAUM.js} +3 -3
  66. package/dist/{chunk-RLEJULGN.js → chunk-OMA2B7GY.js} +2 -2
  67. package/dist/{chunk-K6PWPC7O.js → chunk-OQT34EVH.js} +2 -2
  68. package/dist/{chunk-2NLLXCJG.js → chunk-OWHERGF2.js} +2 -2
  69. package/dist/{chunk-4PWANX5V.js → chunk-PKRYQ222.js} +6 -6
  70. package/dist/{chunk-BCM6ZAJ3.js → chunk-QJLTAH5X.js} +208 -61
  71. package/dist/chunk-QJLTAH5X.js.map +1 -0
  72. package/dist/{chunk-DJLH3XWM.js → chunk-QMIKXIAM.js} +2 -2
  73. package/dist/{chunk-Y6KDJQLK.js → chunk-QRHKJT7F.js} +7 -7
  74. package/dist/{chunk-7MOYAT4Y.js → chunk-QXDICB4J.js} +3 -3
  75. package/dist/{chunk-KWLDB64F.js → chunk-QZPSIQRB.js} +2 -2
  76. package/dist/{chunk-54BRW3BL.js → chunk-REJCULWA.js} +2 -2
  77. package/dist/{chunk-ISX7JODR.js → chunk-SBUQ4KIE.js} +2 -2
  78. package/dist/{chunk-HPGXB56F.js → chunk-STUPJFG7.js} +2 -2
  79. package/dist/{chunk-LPSTQR26.js → chunk-SV3MDTJI.js} +2 -2
  80. package/dist/{chunk-MK7EJLB4.js → chunk-UJICQ77Y.js} +2 -2
  81. package/dist/{chunk-4PHT7KE2.js → chunk-UUGU26P2.js} +2 -2
  82. package/dist/{chunk-X7Y7WX73.js → chunk-UVYI6VIX.js} +1 -1
  83. package/dist/{chunk-QB66LBXA.js → chunk-WGVAEVT7.js} +2 -2
  84. package/dist/{chunk-2L5FSNPC.js → chunk-X2NFFBTO.js} +7 -7
  85. package/dist/{chunk-EMAEDM2J.js → chunk-YWD5UIWY.js} +2 -2
  86. package/dist/{chunk-C6TMLF3A.js → chunk-ZFE7NXB7.js} +4 -4
  87. package/dist/cli.js +57 -57
  88. package/dist/coding/export-okf-codegraph.js +10 -10
  89. package/dist/compounding/engine.js +10 -10
  90. package/dist/connectors/codex-materialize-runner.js +10 -10
  91. package/dist/connectors/index.js +10 -10
  92. package/dist/convergence-refresh.js +12 -12
  93. package/dist/conversation-index/backend.js +2 -2
  94. package/dist/corpus-watermark.js +12 -12
  95. package/dist/entity-retrieval.js +10 -10
  96. package/dist/external-wiki-access.js +24 -24
  97. package/dist/extraction-span-provenance.d.ts +28 -0
  98. package/dist/extraction-span-provenance.js +24 -0
  99. package/dist/extraction-span-provenance.js.map +1 -0
  100. package/dist/extraction.js +13 -13
  101. package/dist/harmonic-retrieval.js +1 -1
  102. package/dist/index.d.ts +34 -6
  103. package/dist/index.js +115 -109
  104. package/dist/lcm/engine.js +3 -3
  105. package/dist/lcm/index.js +6 -6
  106. package/dist/location/index.js +12 -12
  107. package/dist/maintenance/memory-governance.js +10 -10
  108. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
  109. package/dist/maintenance/rebuild-memory-projection.js +11 -11
  110. package/dist/namespaces/migrate.js +11 -11
  111. package/dist/namespaces/search.js +8 -8
  112. package/dist/namespaces/storage.js +10 -10
  113. package/dist/operator-doctor-corpus.js +13 -13
  114. package/dist/operator-toolkit.js +18 -18
  115. package/dist/orchestration/maintenance.js +18 -18
  116. package/dist/orchestrator.js +82 -82
  117. package/dist/qmd.js +4 -4
  118. package/dist/recall-deep-rewrite.d.ts +23 -0
  119. package/dist/recall-deep-rewrite.js +29 -0
  120. package/dist/recall-deep-rewrite.js.map +1 -0
  121. package/dist/search/factory.js +7 -7
  122. package/dist/search/index.js +11 -11
  123. package/dist/semantic-consolidation.js +11 -11
  124. package/dist/semantic-rule-promotion.js +10 -10
  125. package/dist/semantic-rule-verifier.js +10 -10
  126. package/dist/source-agent-qualifier.js +12 -12
  127. package/dist/standup/build.js +10 -10
  128. package/dist/storage.js +9 -9
  129. package/dist/support-passport/index.js +26 -26
  130. package/dist/transfer/autodetect.js +1 -1
  131. package/dist/transfer/backup.js +2 -2
  132. package/dist/transfer/capsule-export.js +3 -3
  133. package/dist/transfer/capsule-import.js +2 -2
  134. package/dist/transfer/export-okf.js +11 -11
  135. package/dist/verified-recall.js +10 -10
  136. package/dist/who-knows-cli.js +11 -11
  137. package/dist/who-knows.js +10 -10
  138. package/package.json +2 -2
  139. package/src/activity/index.ts +1 -0
  140. package/src/activity/journal-section.ts +1 -1
  141. package/src/activity/journal-strip.test.ts +126 -0
  142. package/src/activity/journal-strip.ts +115 -0
  143. package/src/activity/timeline/analysis-failure.test.ts +104 -0
  144. package/src/activity/timeline/analysis-failure.ts +60 -0
  145. package/src/activity/timeline/index.ts +1 -0
  146. package/src/dedup/index.ts +7 -0
  147. package/src/dedup/merge-verdict.test.ts +74 -0
  148. package/src/dedup/merge-verdict.ts +31 -0
  149. package/src/extraction-span-provenance.test.ts +85 -0
  150. package/src/extraction-span-provenance.ts +41 -0
  151. package/src/recall-deep-rewrite.test.ts +199 -0
  152. package/src/recall-deep-rewrite.ts +50 -0
  153. package/dist/chunk-BCM6ZAJ3.js.map +0 -1
  154. /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
  155. /package/dist/{chunk-XN33JLRF.js.map → chunk-46XRYYOJ.js.map} +0 -0
  156. /package/dist/{chunk-WD424KYT.js.map → chunk-4L7OKRGP.js.map} +0 -0
  157. /package/dist/{chunk-XZ7QLOTJ.js.map → chunk-6KGBI54C.js.map} +0 -0
  158. /package/dist/{chunk-245QROHZ.js.map → chunk-6SRUPUTY.js.map} +0 -0
  159. /package/dist/{chunk-ZIGSNLYI.js.map → chunk-A6HVSRGM.js.map} +0 -0
  160. /package/dist/{chunk-JBPKEARU.js.map → chunk-AU7Q3LSC.js.map} +0 -0
  161. /package/dist/{chunk-7BS5UVXA.js.map → chunk-AWMS3IFB.js.map} +0 -0
  162. /package/dist/{chunk-HJQZQUPC.js.map → chunk-BE4Y6GK6.js.map} +0 -0
  163. /package/dist/{chunk-D3AUQXTJ.js.map → chunk-BT7AQZQX.js.map} +0 -0
  164. /package/dist/{chunk-IPLYGWQF.js.map → chunk-BUKK5SWA.js.map} +0 -0
  165. /package/dist/{chunk-CEH2AFXG.js.map → chunk-C24UZSFW.js.map} +0 -0
  166. /package/dist/{chunk-HCERYDRI.js.map → chunk-CJTZO5MW.js.map} +0 -0
  167. /package/dist/{chunk-4QKWBVEE.js.map → chunk-CR73YTWC.js.map} +0 -0
  168. /package/dist/{chunk-LTWFU6VJ.js.map → chunk-DDAHG5ZD.js.map} +0 -0
  169. /package/dist/{chunk-S47XNAWD.js.map → chunk-EU5PDHU2.js.map} +0 -0
  170. /package/dist/{chunk-KT3ERTF6.js.map → chunk-F3H7VNM4.js.map} +0 -0
  171. /package/dist/{chunk-EG4FHU5C.js.map → chunk-FJP6MVRK.js.map} +0 -0
  172. /package/dist/{chunk-7FLINMUE.js.map → chunk-GEHEBLVY.js.map} +0 -0
  173. /package/dist/{chunk-G3B3LP6A.js.map → chunk-GRZYF6KK.js.map} +0 -0
  174. /package/dist/{chunk-E2PORO7O.js.map → chunk-H6VYVOVP.js.map} +0 -0
  175. /package/dist/{chunk-QKTGCL6M.js.map → chunk-HGRRJDUH.js.map} +0 -0
  176. /package/dist/{chunk-J2R275FN.js.map → chunk-HQJM3BKH.js.map} +0 -0
  177. /package/dist/{chunk-ZRQPX2DW.js.map → chunk-I3X5NWDU.js.map} +0 -0
  178. /package/dist/{chunk-TGKLVAML.js.map → chunk-I7FVQMAA.js.map} +0 -0
  179. /package/dist/{chunk-UWOA5L6N.js.map → chunk-IDPFBRPJ.js.map} +0 -0
  180. /package/dist/{chunk-CNZVEU4X.js.map → chunk-IE7N2YK6.js.map} +0 -0
  181. /package/dist/{chunk-PVV42HM4.js.map → chunk-IYFL3534.js.map} +0 -0
  182. /package/dist/{chunk-X5SDZMO7.js.map → chunk-JGXMA6HL.js.map} +0 -0
  183. /package/dist/{chunk-SQEIPTDV.js.map → chunk-JKXPFIPC.js.map} +0 -0
  184. /package/dist/{chunk-Y2ECPAJI.js.map → chunk-K5JFIA44.js.map} +0 -0
  185. /package/dist/{chunk-JNZSNJ43.js.map → chunk-K7VTD5AZ.js.map} +0 -0
  186. /package/dist/{chunk-E62M4H4M.js.map → chunk-KDCPIXIY.js.map} +0 -0
  187. /package/dist/{chunk-TS37MXC7.js.map → chunk-KZAN4JXY.js.map} +0 -0
  188. /package/dist/{chunk-227UFSEQ.js.map → chunk-LEPOP46W.js.map} +0 -0
  189. /package/dist/{chunk-5E7KXID5.js.map → chunk-M3BFW3VX.js.map} +0 -0
  190. /package/dist/{chunk-PWVRMD37.js.map → chunk-MUYJQ273.js.map} +0 -0
  191. /package/dist/{chunk-5KVMT25I.js.map → chunk-MWLBRFLY.js.map} +0 -0
  192. /package/dist/{chunk-2OXBS2LV.js.map → chunk-NROLO2HY.js.map} +0 -0
  193. /package/dist/{chunk-6H7JWYTC.js.map → chunk-O2HDDAUM.js.map} +0 -0
  194. /package/dist/{chunk-RLEJULGN.js.map → chunk-OMA2B7GY.js.map} +0 -0
  195. /package/dist/{chunk-K6PWPC7O.js.map → chunk-OQT34EVH.js.map} +0 -0
  196. /package/dist/{chunk-2NLLXCJG.js.map → chunk-OWHERGF2.js.map} +0 -0
  197. /package/dist/{chunk-4PWANX5V.js.map → chunk-PKRYQ222.js.map} +0 -0
  198. /package/dist/{chunk-DJLH3XWM.js.map → chunk-QMIKXIAM.js.map} +0 -0
  199. /package/dist/{chunk-Y6KDJQLK.js.map → chunk-QRHKJT7F.js.map} +0 -0
  200. /package/dist/{chunk-7MOYAT4Y.js.map → chunk-QXDICB4J.js.map} +0 -0
  201. /package/dist/{chunk-KWLDB64F.js.map → chunk-QZPSIQRB.js.map} +0 -0
  202. /package/dist/{chunk-54BRW3BL.js.map → chunk-REJCULWA.js.map} +0 -0
  203. /package/dist/{chunk-ISX7JODR.js.map → chunk-SBUQ4KIE.js.map} +0 -0
  204. /package/dist/{chunk-HPGXB56F.js.map → chunk-STUPJFG7.js.map} +0 -0
  205. /package/dist/{chunk-LPSTQR26.js.map → chunk-SV3MDTJI.js.map} +0 -0
  206. /package/dist/{chunk-MK7EJLB4.js.map → chunk-UJICQ77Y.js.map} +0 -0
  207. /package/dist/{chunk-4PHT7KE2.js.map → chunk-UUGU26P2.js.map} +0 -0
  208. /package/dist/{chunk-X7Y7WX73.js.map → chunk-UVYI6VIX.js.map} +0 -0
  209. /package/dist/{chunk-QB66LBXA.js.map → chunk-WGVAEVT7.js.map} +0 -0
  210. /package/dist/{chunk-2L5FSNPC.js.map → chunk-X2NFFBTO.js.map} +0 -0
  211. /package/dist/{chunk-EMAEDM2J.js.map → chunk-YWD5UIWY.js.map} +0 -0
  212. /package/dist/{chunk-C6TMLF3A.js.map → chunk-ZFE7NXB7.js.map} +0 -0
@@ -0,0 +1,199 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import {
5
+ MAX_REFINES_PER_INVOCATION,
6
+ validateRefineRewrite,
7
+ } from "./recall-deep-rewrite.js";
8
+
9
+ const base = {
10
+ currentQuery: "who met at the cafe",
11
+ refinesUsed: 0,
12
+ };
13
+
14
+ test("genuine rewrite succeeds and is trimmed", () => {
15
+ assert.deepEqual(
16
+ validateRefineRewrite({
17
+ ...base,
18
+ refinedQuery: " which people met at the cafe on friday ",
19
+ }),
20
+ { ok: true, refinedQuery: "which people met at the cafe on friday" },
21
+ );
22
+ });
23
+
24
+ test("success keeps internal whitespace as written", () => {
25
+ assert.deepEqual(
26
+ validateRefineRewrite({
27
+ ...base,
28
+ refinedQuery: "which people met at the cafe",
29
+ }),
30
+ { ok: true, refinedQuery: "which people met at the cafe" },
31
+ );
32
+ });
33
+
34
+ test("refinesUsed 0 is allowed", () => {
35
+ assert.equal(
36
+ validateRefineRewrite({
37
+ ...base,
38
+ refinesUsed: 0,
39
+ refinedQuery: "which people met at the cafe",
40
+ }).ok,
41
+ true,
42
+ );
43
+ });
44
+
45
+ test("refinesUsed 1 is allowed", () => {
46
+ assert.equal(
47
+ validateRefineRewrite({
48
+ ...base,
49
+ refinesUsed: 1,
50
+ refinedQuery: "which people met at the cafe",
51
+ }).ok,
52
+ true,
53
+ );
54
+ });
55
+
56
+ test("refinesUsed 2 gives refine_budget_spent", () => {
57
+ assert.deepEqual(
58
+ validateRefineRewrite({
59
+ ...base,
60
+ refinesUsed: 2,
61
+ refinedQuery: "which people met at the cafe",
62
+ }),
63
+ { ok: false, stop: true, reason: "refine_budget_spent" },
64
+ );
65
+ });
66
+
67
+ test("refinesUsed 3 gives refine_budget_spent", () => {
68
+ assert.deepEqual(
69
+ validateRefineRewrite({
70
+ ...base,
71
+ refinesUsed: 3,
72
+ refinedQuery: "which people met at the cafe",
73
+ }),
74
+ { ok: false, stop: true, reason: "refine_budget_spent" },
75
+ );
76
+ });
77
+
78
+ test("budget check wins over a valid rewrite", () => {
79
+ assert.deepEqual(
80
+ validateRefineRewrite({
81
+ currentQuery: "who met at the cafe",
82
+ refinedQuery: "completely different and clearly better query",
83
+ refinesUsed: MAX_REFINES_PER_INVOCATION,
84
+ }),
85
+ { ok: false, stop: true, reason: "refine_budget_spent" },
86
+ );
87
+ });
88
+
89
+ test("negative refinesUsed throws", () => {
90
+ assert.throws(
91
+ () => validateRefineRewrite({ ...base, refinedQuery: "new query", refinesUsed: -1 }),
92
+ RangeError,
93
+ );
94
+ assert.throws(
95
+ () => validateRefineRewrite({ ...base, refinedQuery: "new query", refinesUsed: -1 }),
96
+ /refinesUsed/,
97
+ );
98
+ });
99
+
100
+ test("float refinesUsed throws", () => {
101
+ assert.throws(
102
+ () => validateRefineRewrite({ ...base, refinedQuery: "new query", refinesUsed: 1.5 }),
103
+ /refinesUsed/,
104
+ );
105
+ });
106
+
107
+ test("NaN refinesUsed throws", () => {
108
+ assert.throws(
109
+ () => validateRefineRewrite({ ...base, refinedQuery: "new query", refinesUsed: Number.NaN }),
110
+ /refinesUsed/,
111
+ );
112
+ });
113
+
114
+ test("empty string rewrite gives empty_rewrite", () => {
115
+ assert.deepEqual(validateRefineRewrite({ ...base, refinedQuery: "" }), {
116
+ ok: false,
117
+ stop: true,
118
+ reason: "empty_rewrite",
119
+ });
120
+ });
121
+
122
+ test("whitespace-only rewrite gives empty_rewrite", () => {
123
+ assert.deepEqual(validateRefineRewrite({ ...base, refinedQuery: " " }), {
124
+ ok: false,
125
+ stop: true,
126
+ reason: "empty_rewrite",
127
+ });
128
+ });
129
+
130
+ test("null rewrite gives empty_rewrite", () => {
131
+ assert.deepEqual(validateRefineRewrite({ ...base, refinedQuery: null }), {
132
+ ok: false,
133
+ stop: true,
134
+ reason: "empty_rewrite",
135
+ });
136
+ });
137
+
138
+ test("undefined rewrite gives empty_rewrite", () => {
139
+ assert.deepEqual(validateRefineRewrite({ ...base, refinedQuery: undefined }), {
140
+ ok: false,
141
+ stop: true,
142
+ reason: "empty_rewrite",
143
+ });
144
+ });
145
+
146
+ test("number rewrite gives empty_rewrite", () => {
147
+ assert.deepEqual(validateRefineRewrite({ ...base, refinedQuery: 42 }), {
148
+ ok: false,
149
+ stop: true,
150
+ reason: "empty_rewrite",
151
+ });
152
+ });
153
+
154
+ test("exact duplicate gives identical_rewrite", () => {
155
+ assert.deepEqual(
156
+ validateRefineRewrite({
157
+ ...base,
158
+ refinedQuery: "who met at the cafe",
159
+ }),
160
+ { ok: false, stop: true, reason: "identical_rewrite" },
161
+ );
162
+ });
163
+
164
+ test("case-different duplicate gives identical_rewrite", () => {
165
+ assert.deepEqual(
166
+ validateRefineRewrite({
167
+ ...base,
168
+ refinedQuery: " Who Met At The Cafe ",
169
+ }),
170
+ { ok: false, stop: true, reason: "identical_rewrite" },
171
+ );
172
+ });
173
+
174
+ test("extra internal whitespace duplicate gives identical_rewrite", () => {
175
+ assert.deepEqual(
176
+ validateRefineRewrite({
177
+ currentQuery: "who met",
178
+ refinedQuery: "who met",
179
+ refinesUsed: 0,
180
+ }),
181
+ { ok: false, stop: true, reason: "identical_rewrite" },
182
+ );
183
+ });
184
+
185
+ test("every failure carries stop true", () => {
186
+ const failures = [
187
+ validateRefineRewrite({
188
+ ...base,
189
+ refinedQuery: "fine",
190
+ refinesUsed: 2,
191
+ }),
192
+ validateRefineRewrite({ ...base, refinedQuery: " " }),
193
+ validateRefineRewrite({ ...base, refinedQuery: "who met at the cafe" }),
194
+ ];
195
+ for (const failure of failures) {
196
+ assert.equal(failure.ok, false);
197
+ assert.equal(failure.stop, true);
198
+ }
199
+ });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Validate a deep-recall REFINE rewrite (issue #2332).
3
+ *
4
+ * Pure. The budget check runs before the rewrite is inspected, so an
5
+ * exhausted budget cannot be bypassed by a valid-looking query. An empty
6
+ * or no-progress rewrite is rejected and treated as STOP.
7
+ */
8
+ export const MAX_REFINES_PER_INVOCATION = 2;
9
+
10
+ export type RefineRewriteResult =
11
+ | { ok: true; refinedQuery: string }
12
+ | {
13
+ ok: false;
14
+ stop: true;
15
+ reason: "empty_rewrite" | "identical_rewrite" | "refine_budget_spent";
16
+ };
17
+
18
+ export function validateRefineRewrite(input: {
19
+ currentQuery: string;
20
+ refinedQuery: unknown;
21
+ refinesUsed: number;
22
+ }): RefineRewriteResult {
23
+ if (!Number.isInteger(input.refinesUsed) || input.refinesUsed < 0) {
24
+ throw new RangeError(
25
+ `refinesUsed must be a non-negative integer; got ${JSON.stringify(input.refinesUsed)}`,
26
+ );
27
+ }
28
+ if (input.refinesUsed >= MAX_REFINES_PER_INVOCATION) {
29
+ return { ok: false, stop: true, reason: "refine_budget_spent" };
30
+ }
31
+ if (
32
+ typeof input.refinedQuery !== "string" ||
33
+ input.refinedQuery.trim() === ""
34
+ ) {
35
+ return { ok: false, stop: true, reason: "empty_rewrite" };
36
+ }
37
+ const trimmed = input.refinedQuery.trim();
38
+ // Comparison key: case-folded with internal whitespace runs collapsed, so
39
+ // pure case/spacing drift is not progress. The returned rewrite keeps
40
+ // internal whitespace as the model wrote it.
41
+ const currentKey = input.currentQuery
42
+ .trim()
43
+ .toLowerCase()
44
+ .replace(/\s+/g, " ");
45
+ const refinedKey = trimmed.toLowerCase().replace(/\s+/g, " ");
46
+ if (refinedKey === currentKey) {
47
+ return { ok: false, stop: true, reason: "identical_rewrite" };
48
+ }
49
+ return { ok: true, refinedQuery: trimmed };
50
+ }