@remnic/core 9.69.2 → 9.69.4

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 (254) hide show
  1. package/dist/access-admin-ops-surface.d.ts +1 -1
  2. package/dist/access-admin-ops-surface.js +25 -25
  3. package/dist/access-audit.js +2 -2
  4. package/dist/access-authorization-probe.d.ts +1 -1
  5. package/dist/access-authorization-probe.js +26 -26
  6. package/dist/access-boundary.d.ts +1 -1
  7. package/dist/access-boundary.js +25 -25
  8. package/dist/access-cli.js +75 -75
  9. package/dist/access-extraction-force-flush.d.ts +1 -1
  10. package/dist/access-extraction-force-flush.js +25 -25
  11. package/dist/access-http-lcm-compaction.d.ts +1 -1
  12. package/dist/access-http-lifecycle-flush.d.ts +1 -1
  13. package/dist/access-http-offline-stream.d.ts +1 -1
  14. package/dist/access-http-query.js +27 -27
  15. package/dist/access-http.d.ts +2 -2
  16. package/dist/access-http.js +40 -40
  17. package/dist/access-identity-continuity-surface.js +25 -25
  18. package/dist/access-lcm-surface.d.ts +1 -1
  19. package/dist/access-lcm-surface.js +25 -25
  20. package/dist/access-mcp.d.ts +1 -1
  21. package/dist/access-mcp.js +34 -34
  22. package/dist/access-namespace-preflight.js +25 -25
  23. package/dist/access-observe-write-surface.d.ts +1 -1
  24. package/dist/access-observe-write-surface.js +25 -25
  25. package/dist/access-operations-batch.js +28 -28
  26. package/dist/access-operations.d.ts +6 -6
  27. package/dist/access-operations.js +33 -33
  28. package/dist/access-recall-concurrency.d.ts +1 -1
  29. package/dist/access-recall-concurrency.js +25 -25
  30. package/dist/access-recall-response.d.ts +1 -1
  31. package/dist/access-recall-response.js +25 -25
  32. package/dist/access-recall-surface.d.ts +1 -1
  33. package/dist/access-recall-surface.js +25 -25
  34. package/dist/access-schema.d.ts +89 -89
  35. package/dist/access-schema.js +3 -3
  36. package/dist/access-service-helpers.d.ts +1 -1
  37. package/dist/access-service.d.ts +1 -1
  38. package/dist/access-service.js +25 -25
  39. package/dist/access-surface-catalog.d.ts +1 -1
  40. package/dist/access-surface-catalog.js +27 -27
  41. package/dist/active-recall.js +3 -3
  42. package/dist/briefing.js +7 -7
  43. package/dist/causal-behavior.js +2 -2
  44. package/dist/causal-chain.js +2 -2
  45. package/dist/causal-consolidation.js +10 -10
  46. package/dist/causal-retrieval.js +2 -2
  47. package/dist/causal-trajectory-graph.js +2 -2
  48. package/dist/causal-trajectory.js +1 -1
  49. package/dist/{chunk-LVFI5LG7.js → chunk-2L5FSNPC.js} +5 -5
  50. package/dist/{chunk-5H5TKPBA.js → chunk-35J5ABJC.js} +2 -2
  51. package/dist/{chunk-AI6TT4Y3.js → chunk-3HEYRDNF.js} +2 -2
  52. package/dist/{chunk-CCKIHXU5.js → chunk-3KURGLO7.js} +2 -2
  53. package/dist/{chunk-WKZSKFZC.js → chunk-5FBA6UXN.js} +7 -7
  54. package/dist/{chunk-THWUR7JO.js → chunk-6AYPXHOF.js} +2 -2
  55. package/dist/{chunk-JU5SBSV7.js → chunk-6CT4HSVJ.js} +2 -2
  56. package/dist/{chunk-DMR6GVTW.js → chunk-76X3F4WV.js} +6 -6
  57. package/dist/{chunk-6FLNUDEO.js → chunk-7DQQYJKU.js} +2 -2
  58. package/dist/{chunk-DKTIW44X.js → chunk-7SL6EJLT.js} +2 -2
  59. package/dist/{chunk-JC5GCR3K.js → chunk-ASDK4GEV.js} +2 -2
  60. package/dist/{chunk-YMOPYG3K.js → chunk-BIJAWZYW.js} +6 -6
  61. package/dist/{chunk-AWGXR2PV.js → chunk-BX6GW4KC.js} +2 -2
  62. package/dist/{chunk-ZOMXROJ6.js → chunk-D65XGENZ.js} +3 -3
  63. package/dist/{chunk-SB7PMYWV.js → chunk-DA2XDNS5.js} +5 -5
  64. package/dist/{chunk-LSGQRUTX.js → chunk-DT7LKLET.js} +2 -2
  65. package/dist/{chunk-RSPIDEY3.js → chunk-DZPX5WSG.js} +3 -3
  66. package/dist/{chunk-UXEAJ765.js → chunk-EA2C5Z7V.js} +31 -31
  67. package/dist/{chunk-ZBGODUSY.js → chunk-EB3V7PJC.js} +10 -10
  68. package/dist/{chunk-GPTZFSMQ.js → chunk-EJCKJS4X.js} +2 -2
  69. package/dist/{chunk-RGCF4XNH.js → chunk-EZPBQVGZ.js} +4 -4
  70. package/dist/{chunk-WJPSCTZY.js → chunk-FGORNFTZ.js} +2 -2
  71. package/dist/{chunk-JAQXUGLN.js → chunk-FNNWK7G7.js} +2 -2
  72. package/dist/{chunk-XCVGQKTR.js → chunk-GH477IGR.js} +3 -3
  73. package/dist/{chunk-TV52QFXK.js → chunk-HBOQO4GB.js} +10 -10
  74. package/dist/{chunk-D6Q5EPOV.js → chunk-HCERYDRI.js} +2 -2
  75. package/dist/{chunk-HCM7ZYJD.js → chunk-IEZ6CGTW.js} +2 -2
  76. package/dist/{chunk-KQAFEZQX.js → chunk-IPLYGWQF.js} +5 -5
  77. package/dist/{chunk-MB6GLPRY.js → chunk-IVKIODHO.js} +4 -4
  78. package/dist/{chunk-WFHBC4BQ.js → chunk-J6SWAE74.js} +2 -2
  79. package/dist/{chunk-PUTLM67J.js → chunk-JHTF22NQ.js} +2 -2
  80. package/dist/{chunk-M73TXJJW.js → chunk-LXBVTRBG.js} +2 -2
  81. package/dist/{chunk-LW5DFCGW.js → chunk-LXMYIJOO.js} +1 -1
  82. package/dist/{chunk-YJLGCUKZ.js → chunk-MU4FAKGV.js} +2 -2
  83. package/dist/{chunk-7ATQD2GP.js → chunk-N3VPKMU3.js} +2 -2
  84. package/dist/{chunk-LTBKKSCX.js → chunk-OLBZ2WGG.js} +2 -2
  85. package/dist/{chunk-4EQBBIFH.js → chunk-OM4VYQPH.js} +2 -2
  86. package/dist/{chunk-HACVBEAP.js → chunk-OMA2B7GY.js} +2 -2
  87. package/dist/{chunk-PD66CVSD.js → chunk-OUKBMQ2O.js} +2 -2
  88. package/dist/{chunk-PSFQ4HDM.js → chunk-OZFHE6FY.js} +3 -3
  89. package/dist/{chunk-TR76RJNY.js → chunk-P6GKRT2H.js} +1 -1
  90. package/dist/{chunk-42LQMCEE.js → chunk-PBSZQBFM.js} +11 -11
  91. package/dist/{chunk-B6D3O5X2.js → chunk-PRCJDCDD.js} +4 -4
  92. package/dist/{chunk-4FRP3R44.js → chunk-RFYKLES2.js} +2 -2
  93. package/dist/{chunk-AEZCLP3C.js → chunk-S7363OIV.js} +2 -2
  94. package/dist/{chunk-S7WPER23.js → chunk-TFN3PPYO.js} +2 -2
  95. package/dist/{chunk-AF67CHFU.js → chunk-TOUZM2RQ.js} +2 -2
  96. package/dist/{chunk-7K5Q6COX.js → chunk-TVVEYCNW.js} +4 -4
  97. package/dist/{chunk-J7RZ4NJO.js → chunk-UFY4G6EC.js} +13 -13
  98. package/dist/{chunk-52I6AHO6.js → chunk-V3KLIYTW.js} +2 -2
  99. package/dist/{chunk-RJ7NOBKA.js → chunk-V63BBOFS.js} +2 -2
  100. package/dist/{chunk-ZMVMYZMX.js → chunk-VJERIMIX.js} +3 -3
  101. package/dist/{chunk-YQPVXUJG.js → chunk-VVE5SLO5.js} +2 -2
  102. package/dist/{chunk-IJCC5TSX.js → chunk-WJTB4SF5.js} +2 -2
  103. package/dist/{chunk-TL2THUFH.js → chunk-WOA7DS7O.js} +114 -38
  104. package/dist/chunk-WOA7DS7O.js.map +1 -0
  105. package/dist/{chunk-42JAVCED.js → chunk-WRPRUMUI.js} +2 -2
  106. package/dist/{chunk-C7KYMSL5.js → chunk-WZBDOI2C.js} +2 -2
  107. package/dist/{chunk-6LDZNG3T.js → chunk-XVI2TR26.js} +2 -2
  108. package/dist/{chunk-KXJ3IDD5.js → chunk-YHSZTMM3.js} +2 -2
  109. package/dist/{chunk-KIJCXLW7.js → chunk-YNF4ZT3X.js} +2 -2
  110. package/dist/{chunk-F6B3RUR4.js → chunk-YSD4MK5Q.js} +2 -2
  111. package/dist/{cli-CFpMhqz5.d.ts → cli-Bvy4jdBu.d.ts} +1 -1
  112. package/dist/cli.d.ts +2 -2
  113. package/dist/cli.js +59 -59
  114. package/dist/coding/export-okf-codegraph.js +7 -7
  115. package/dist/coding/pre-action-gate.js +2 -2
  116. package/dist/compounding/engine.js +7 -7
  117. package/dist/config.js +3 -3
  118. package/dist/connectors/codex-materialize-runner.js +7 -7
  119. package/dist/connectors/index.js +7 -7
  120. package/dist/convergence-refresh.js +9 -9
  121. package/dist/corpus-watermark.js +9 -9
  122. package/dist/entity-retrieval.js +7 -7
  123. package/dist/external-wiki-access.d.ts +1 -1
  124. package/dist/external-wiki-access.js +26 -26
  125. package/dist/external-wiki-mcp-tools.d.ts +1 -1
  126. package/dist/extraction-span-fallback.d.ts +35 -0
  127. package/dist/extraction-span-fallback.js +33 -0
  128. package/dist/extraction-span-fallback.js.map +1 -0
  129. package/dist/extraction.js +10 -10
  130. package/dist/{graph-edge-decay-A4SDFMMS.js → graph-edge-decay-FXGELBFA.js} +4 -4
  131. package/dist/graph-snapshot.js +3 -3
  132. package/dist/graph.js +3 -3
  133. package/dist/harmonic-retrieval.js +1 -1
  134. package/dist/index.d.ts +732 -681
  135. package/dist/index.js +109 -101
  136. package/dist/lcm/engine.js +2 -2
  137. package/dist/lcm/index.js +5 -5
  138. package/dist/local-llm.js +2 -2
  139. package/dist/location/index.js +9 -9
  140. package/dist/maintenance/memory-governance.js +7 -7
  141. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
  142. package/dist/maintenance/rebuild-memory-projection.js +8 -8
  143. package/dist/mcp-memory-inspector-app.d.ts +1 -1
  144. package/dist/memory-subject-cli.d.ts +1 -1
  145. package/dist/memory-subject.d.ts +1 -1
  146. package/dist/namespaces/migrate.js +8 -8
  147. package/dist/namespaces/search.js +3 -3
  148. package/dist/namespaces/storage.js +7 -7
  149. package/dist/operator-doctor-corpus.js +10 -10
  150. package/dist/operator-toolkit.js +20 -20
  151. package/dist/orchestration/maintenance.js +15 -15
  152. package/dist/orchestrator.js +76 -76
  153. package/dist/{public-http-C9-CHc40.d.ts → public-http-2MyshWxl.d.ts} +1 -1
  154. package/dist/resume-bundles.js +4 -4
  155. package/dist/schemas.d.ts +176 -176
  156. package/dist/search/factory.js +2 -2
  157. package/dist/search/index.js +4 -4
  158. package/dist/semantic-consolidation.js +8 -8
  159. package/dist/semantic-rule-promotion.js +7 -7
  160. package/dist/semantic-rule-verifier.js +7 -7
  161. package/dist/shared-context/manager.d.ts +6 -6
  162. package/dist/source-agent-qualifier.js +7 -7
  163. package/dist/standup/build.js +7 -7
  164. package/dist/storage.js +6 -6
  165. package/dist/summarizer.js +3 -3
  166. package/dist/support-passport/index.d.ts +4 -4
  167. package/dist/support-passport/index.js +28 -28
  168. package/dist/transfer/autodetect.js +1 -1
  169. package/dist/transfer/backup.js +1 -1
  170. package/dist/transfer/capsule-export.js +2 -2
  171. package/dist/transfer/export-okf.js +8 -8
  172. package/dist/transfer/types.d.ts +78 -78
  173. package/dist/verified-recall.js +7 -7
  174. package/dist/who-knows-cli.d.ts +1 -1
  175. package/dist/who-knows-cli.js +8 -8
  176. package/dist/who-knows.d.ts +1 -1
  177. package/dist/who-knows.js +7 -7
  178. package/package.json +2 -2
  179. package/src/activity/index.ts +1 -0
  180. package/src/activity/journal-vault-prereq.test.ts +121 -0
  181. package/src/activity/journal-vault-prereq.ts +41 -0
  182. package/src/activity/timeline/analysis-metadata.test.ts +147 -0
  183. package/src/activity/timeline/analysis-metadata.ts +97 -0
  184. package/src/activity/timeline/index.ts +1 -0
  185. package/src/dedup/index.ts +6 -0
  186. package/src/dedup/merge-content.test.ts +116 -0
  187. package/src/dedup/merge-content.ts +40 -0
  188. package/src/extraction-span-fallback.test.ts +103 -0
  189. package/src/extraction-span-fallback.ts +62 -0
  190. package/src/shared-context/curation-lint.test.ts +155 -0
  191. package/src/shared-context/curation-lint.ts +68 -0
  192. package/dist/chunk-TL2THUFH.js.map +0 -1
  193. package/dist/{access-service-DcfM0Vf0.d.ts → access-service-LbwWl05g.d.ts} +64 -64
  194. /package/dist/{chunk-LVFI5LG7.js.map → chunk-2L5FSNPC.js.map} +0 -0
  195. /package/dist/{chunk-5H5TKPBA.js.map → chunk-35J5ABJC.js.map} +0 -0
  196. /package/dist/{chunk-AI6TT4Y3.js.map → chunk-3HEYRDNF.js.map} +0 -0
  197. /package/dist/{chunk-CCKIHXU5.js.map → chunk-3KURGLO7.js.map} +0 -0
  198. /package/dist/{chunk-WKZSKFZC.js.map → chunk-5FBA6UXN.js.map} +0 -0
  199. /package/dist/{chunk-THWUR7JO.js.map → chunk-6AYPXHOF.js.map} +0 -0
  200. /package/dist/{chunk-JU5SBSV7.js.map → chunk-6CT4HSVJ.js.map} +0 -0
  201. /package/dist/{chunk-DMR6GVTW.js.map → chunk-76X3F4WV.js.map} +0 -0
  202. /package/dist/{chunk-6FLNUDEO.js.map → chunk-7DQQYJKU.js.map} +0 -0
  203. /package/dist/{chunk-DKTIW44X.js.map → chunk-7SL6EJLT.js.map} +0 -0
  204. /package/dist/{chunk-JC5GCR3K.js.map → chunk-ASDK4GEV.js.map} +0 -0
  205. /package/dist/{chunk-YMOPYG3K.js.map → chunk-BIJAWZYW.js.map} +0 -0
  206. /package/dist/{chunk-AWGXR2PV.js.map → chunk-BX6GW4KC.js.map} +0 -0
  207. /package/dist/{chunk-ZOMXROJ6.js.map → chunk-D65XGENZ.js.map} +0 -0
  208. /package/dist/{chunk-SB7PMYWV.js.map → chunk-DA2XDNS5.js.map} +0 -0
  209. /package/dist/{chunk-LSGQRUTX.js.map → chunk-DT7LKLET.js.map} +0 -0
  210. /package/dist/{chunk-RSPIDEY3.js.map → chunk-DZPX5WSG.js.map} +0 -0
  211. /package/dist/{chunk-UXEAJ765.js.map → chunk-EA2C5Z7V.js.map} +0 -0
  212. /package/dist/{chunk-ZBGODUSY.js.map → chunk-EB3V7PJC.js.map} +0 -0
  213. /package/dist/{chunk-GPTZFSMQ.js.map → chunk-EJCKJS4X.js.map} +0 -0
  214. /package/dist/{chunk-RGCF4XNH.js.map → chunk-EZPBQVGZ.js.map} +0 -0
  215. /package/dist/{chunk-WJPSCTZY.js.map → chunk-FGORNFTZ.js.map} +0 -0
  216. /package/dist/{chunk-JAQXUGLN.js.map → chunk-FNNWK7G7.js.map} +0 -0
  217. /package/dist/{chunk-XCVGQKTR.js.map → chunk-GH477IGR.js.map} +0 -0
  218. /package/dist/{chunk-TV52QFXK.js.map → chunk-HBOQO4GB.js.map} +0 -0
  219. /package/dist/{chunk-D6Q5EPOV.js.map → chunk-HCERYDRI.js.map} +0 -0
  220. /package/dist/{chunk-HCM7ZYJD.js.map → chunk-IEZ6CGTW.js.map} +0 -0
  221. /package/dist/{chunk-KQAFEZQX.js.map → chunk-IPLYGWQF.js.map} +0 -0
  222. /package/dist/{chunk-MB6GLPRY.js.map → chunk-IVKIODHO.js.map} +0 -0
  223. /package/dist/{chunk-WFHBC4BQ.js.map → chunk-J6SWAE74.js.map} +0 -0
  224. /package/dist/{chunk-PUTLM67J.js.map → chunk-JHTF22NQ.js.map} +0 -0
  225. /package/dist/{chunk-M73TXJJW.js.map → chunk-LXBVTRBG.js.map} +0 -0
  226. /package/dist/{chunk-LW5DFCGW.js.map → chunk-LXMYIJOO.js.map} +0 -0
  227. /package/dist/{chunk-YJLGCUKZ.js.map → chunk-MU4FAKGV.js.map} +0 -0
  228. /package/dist/{chunk-7ATQD2GP.js.map → chunk-N3VPKMU3.js.map} +0 -0
  229. /package/dist/{chunk-LTBKKSCX.js.map → chunk-OLBZ2WGG.js.map} +0 -0
  230. /package/dist/{chunk-4EQBBIFH.js.map → chunk-OM4VYQPH.js.map} +0 -0
  231. /package/dist/{chunk-HACVBEAP.js.map → chunk-OMA2B7GY.js.map} +0 -0
  232. /package/dist/{chunk-PD66CVSD.js.map → chunk-OUKBMQ2O.js.map} +0 -0
  233. /package/dist/{chunk-PSFQ4HDM.js.map → chunk-OZFHE6FY.js.map} +0 -0
  234. /package/dist/{chunk-TR76RJNY.js.map → chunk-P6GKRT2H.js.map} +0 -0
  235. /package/dist/{chunk-42LQMCEE.js.map → chunk-PBSZQBFM.js.map} +0 -0
  236. /package/dist/{chunk-B6D3O5X2.js.map → chunk-PRCJDCDD.js.map} +0 -0
  237. /package/dist/{chunk-4FRP3R44.js.map → chunk-RFYKLES2.js.map} +0 -0
  238. /package/dist/{chunk-AEZCLP3C.js.map → chunk-S7363OIV.js.map} +0 -0
  239. /package/dist/{chunk-S7WPER23.js.map → chunk-TFN3PPYO.js.map} +0 -0
  240. /package/dist/{chunk-AF67CHFU.js.map → chunk-TOUZM2RQ.js.map} +0 -0
  241. /package/dist/{chunk-7K5Q6COX.js.map → chunk-TVVEYCNW.js.map} +0 -0
  242. /package/dist/{chunk-J7RZ4NJO.js.map → chunk-UFY4G6EC.js.map} +0 -0
  243. /package/dist/{chunk-52I6AHO6.js.map → chunk-V3KLIYTW.js.map} +0 -0
  244. /package/dist/{chunk-RJ7NOBKA.js.map → chunk-V63BBOFS.js.map} +0 -0
  245. /package/dist/{chunk-ZMVMYZMX.js.map → chunk-VJERIMIX.js.map} +0 -0
  246. /package/dist/{chunk-YQPVXUJG.js.map → chunk-VVE5SLO5.js.map} +0 -0
  247. /package/dist/{chunk-IJCC5TSX.js.map → chunk-WJTB4SF5.js.map} +0 -0
  248. /package/dist/{chunk-42JAVCED.js.map → chunk-WRPRUMUI.js.map} +0 -0
  249. /package/dist/{chunk-C7KYMSL5.js.map → chunk-WZBDOI2C.js.map} +0 -0
  250. /package/dist/{chunk-6LDZNG3T.js.map → chunk-XVI2TR26.js.map} +0 -0
  251. /package/dist/{chunk-KXJ3IDD5.js.map → chunk-YHSZTMM3.js.map} +0 -0
  252. /package/dist/{chunk-KIJCXLW7.js.map → chunk-YNF4ZT3X.js.map} +0 -0
  253. /package/dist/{chunk-F6B3RUR4.js.map → chunk-YSD4MK5Q.js.map} +0 -0
  254. /package/dist/{graph-edge-decay-A4SDFMMS.js.map → graph-edge-decay-FXGELBFA.js.map} +0 -0
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Span-validation fallback-rate tally (issue #2333 Phase A).
3
+ *
4
+ * Counts per-fact extraction outcomes — "span" means the span validated,
5
+ * "fallback" means validation fell back — and produces the `fallbackRatePct`
6
+ * input that `evaluateSpanPhaseGate` consumes (finite, [0, 100]; the gate
7
+ * refuses Phase B at >= SPAN_GATE_MAX_FALLBACK_RATE_PCT).
8
+ *
9
+ * An unrecognized outcome throws: bucketing it as a success would understate
10
+ * the fallback rate and could wave a failing gate through. A zero-attempt
11
+ * tally reports `fallbackRatePct: null`, which the gate's `number` input
12
+ * cannot accept, so an unmeasured run cannot be handed to it as 0%.
13
+ *
14
+ * Pure. No I/O, no randomness. The input array is not mutated.
15
+ */
16
+
17
+ export const SPAN_OUTCOMES = ["span", "fallback"] as const;
18
+ export type SpanOutcome = (typeof SPAN_OUTCOMES)[number];
19
+
20
+ export interface SpanFallbackTally {
21
+ attempts: number;
22
+ fallbacks: number;
23
+ /**
24
+ * Percentage in [0, 100] for the gate's `fallbackRatePct` input, or `null`
25
+ * when there were no attempts.
26
+ *
27
+ * `null` rather than 0 is the enforcement: a 0 satisfies the gate's
28
+ * `fallbackRatePct < 15` comparison, so an unmeasured run could have
29
+ * approved Phase B. `null` is not assignable to the gate's `number`
30
+ * parameter, so a caller must handle "no measurement" explicitly instead of
31
+ * relying on a comment to tell it not to.
32
+ */
33
+ fallbackRatePct: number | null;
34
+ }
35
+
36
+ export function tallySpanFallbacks(outcomes: readonly string[]): SpanFallbackTally {
37
+ if (!Array.isArray(outcomes)) {
38
+ throw new TypeError(
39
+ `tallySpanFallbacks expects an array of span outcomes, got ${typeof outcomes}`,
40
+ );
41
+ }
42
+ let fallbacks = 0;
43
+ for (const outcome of outcomes) {
44
+ if (!SPAN_OUTCOMES.includes(outcome as SpanOutcome)) {
45
+ throw new TypeError(
46
+ `unknown span outcome ${JSON.stringify(outcome)}; expected one of [${SPAN_OUTCOMES.join(", ")}]`,
47
+ );
48
+ }
49
+ if (outcome === "fallback") {
50
+ fallbacks += 1;
51
+ }
52
+ }
53
+ const attempts = outcomes.length;
54
+ if (attempts === 0) {
55
+ // No measurement. Never 0 and never NaN: 0 would pass the gate's
56
+ // `< 15` check on no data, and NaN would fail it for the same absence.
57
+ return { attempts: 0, fallbacks: 0, fallbackRatePct: null };
58
+ }
59
+ // Cap at 4 decimal places so repeated tallies are byte-identical.
60
+ const fallbackRatePct = Math.round((fallbacks / attempts) * 100 * 10_000) / 10_000;
61
+ return { attempts, fallbacks, fallbackRatePct };
62
+ }
@@ -0,0 +1,155 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+
4
+ import { lintCuratedClaims } from "./curation-lint.js";
5
+
6
+ const AVAILABLE = ["item-1", "item-2", "item-3"];
7
+
8
+ test("a fully-cited claim set returns no findings", () => {
9
+ const findings = lintCuratedClaims({
10
+ claims: [
11
+ { claimId: "c-1", citedItemIds: ["item-1", "item-2"] },
12
+ { claimId: "c-2", citedItemIds: ["item-3"] },
13
+ ],
14
+ availableItemIds: AVAILABLE,
15
+ });
16
+ assert.deepEqual(findings, []);
17
+ });
18
+
19
+ test("missing citedItemIds gives no_citations", () => {
20
+ const findings = lintCuratedClaims({
21
+ claims: [{ claimId: "c-1" }],
22
+ availableItemIds: AVAILABLE,
23
+ });
24
+ assert.deepEqual(findings, [{ claimId: "c-1", reason: "no_citations" }]);
25
+ });
26
+
27
+ test("an empty citedItemIds array gives no_citations", () => {
28
+ const findings = lintCuratedClaims({
29
+ claims: [{ claimId: "c-1", citedItemIds: [] }],
30
+ availableItemIds: AVAILABLE,
31
+ });
32
+ assert.deepEqual(findings, [{ claimId: "c-1", reason: "no_citations" }]);
33
+ });
34
+
35
+ test("an all-blank citedItemIds array gives no_citations", () => {
36
+ const findings = lintCuratedClaims({
37
+ claims: [{ claimId: "c-1", citedItemIds: ["", " ", "\t"] }],
38
+ availableItemIds: AVAILABLE,
39
+ });
40
+ assert.deepEqual(findings, [{ claimId: "c-1", reason: "no_citations" }]);
41
+ });
42
+
43
+ test("one valid plus one unknown citation gives unknown_citation listing only the unknown", () => {
44
+ const findings = lintCuratedClaims({
45
+ claims: [{ claimId: "c-1", citedItemIds: ["item-1", "item-404"] }],
46
+ availableItemIds: AVAILABLE,
47
+ });
48
+ assert.deepEqual(findings, [
49
+ { claimId: "c-1", reason: "unknown_citation", unknownIds: ["item-404"] },
50
+ ]);
51
+ });
52
+
53
+ test("all-unknown citations give no_citations, not unknown_citation (precedence)", () => {
54
+ const findings = lintCuratedClaims({
55
+ claims: [{ claimId: "c-1", citedItemIds: ["nope-1", "nope-2"] }],
56
+ availableItemIds: AVAILABLE,
57
+ });
58
+ assert.deepEqual(findings, [{ claimId: "c-1", reason: "no_citations" }]);
59
+ });
60
+
61
+ test("unknown ids are sorted ascending and deduplicated", () => {
62
+ const findings = lintCuratedClaims({
63
+ claims: [{ claimId: "c-1", citedItemIds: ["item-1", "zz", "aa", "zz", "mm"] }],
64
+ availableItemIds: AVAILABLE,
65
+ });
66
+ assert.deepEqual(findings, [
67
+ { claimId: "c-1", reason: "unknown_citation", unknownIds: ["aa", "mm", "zz"] },
68
+ ]);
69
+ });
70
+
71
+ test("a whitespace-padded citation is unknown (exact comparison, no trim)", () => {
72
+ const findings = lintCuratedClaims({
73
+ claims: [{ claimId: "c-1", citedItemIds: ["item-1", " item-2"] }],
74
+ availableItemIds: AVAILABLE,
75
+ });
76
+ assert.deepEqual(findings, [
77
+ { claimId: "c-1", reason: "unknown_citation", unknownIds: [" item-2"] },
78
+ ]);
79
+ });
80
+
81
+ test("a blank claimId throws RangeError naming claimId", () => {
82
+ assert.throws(
83
+ () =>
84
+ lintCuratedClaims({
85
+ claims: [{ claimId: " ", citedItemIds: ["item-1"] }],
86
+ availableItemIds: AVAILABLE,
87
+ }),
88
+ (err: unknown) => err instanceof RangeError && /claimId/.test(err.message),
89
+ );
90
+ });
91
+
92
+ test("duplicate claim ids are each linted independently", () => {
93
+ const findings = lintCuratedClaims({
94
+ claims: [
95
+ { claimId: "c-1", citedItemIds: ["item-404", "item-1"] },
96
+ { claimId: "c-1" },
97
+ ],
98
+ availableItemIds: AVAILABLE,
99
+ });
100
+ assert.deepEqual(findings, [
101
+ { claimId: "c-1", reason: "no_citations" },
102
+ { claimId: "c-1", reason: "unknown_citation", unknownIds: ["item-404"] },
103
+ ]);
104
+ });
105
+
106
+ test("findings are deterministic across two calls with shuffled input", () => {
107
+ const expected: Array<{ claimId: string; reason: string; unknownIds?: string[] }> = [
108
+ { claimId: "c-1", reason: "no_citations" },
109
+ { claimId: "c-2", reason: "no_citations" },
110
+ { claimId: "c-3", reason: "unknown_citation", unknownIds: ["item-404"] },
111
+ ];
112
+ const claims = [
113
+ { claimId: "c-3", citedItemIds: ["item-3", "item-404"] },
114
+ { claimId: "c-1" },
115
+ { claimId: "c-2", citedItemIds: [] },
116
+ ];
117
+ const first = lintCuratedClaims({ claims, availableItemIds: AVAILABLE });
118
+ const second = lintCuratedClaims({
119
+ claims: [claims[2], claims[0], claims[1]],
120
+ availableItemIds: AVAILABLE,
121
+ });
122
+ assert.deepEqual(first, expected);
123
+ assert.deepEqual(second, expected);
124
+ });
125
+
126
+ test("inputs are not mutated", () => {
127
+ const claims = [
128
+ { claimId: "c-1", citedItemIds: ["zz", "item-1", "zz"] as const },
129
+ ];
130
+ const availableItemIds = ["item-1", "item-2"];
131
+ const claimsSnapshot = structuredClone(claims);
132
+ const availableSnapshot = [...availableItemIds];
133
+ lintCuratedClaims({ claims, availableItemIds });
134
+ assert.deepEqual(claims, claimsSnapshot);
135
+ assert.deepEqual(availableItemIds, availableSnapshot);
136
+ });
137
+
138
+ // Review: two claims sharing a claimId AND a reason still differ by their
139
+ // unknownIds, so a comparator returning 0 there let input order decide the
140
+ // output — the contract says the result is deterministic.
141
+ test("duplicate claim ids with different unknown ids sort deterministically", () => {
142
+ const available = ["item-ok"];
143
+ const claims = [
144
+ { claimId: "claim-dup", citedItemIds: ["item-ok", "zeta-missing"] },
145
+ { claimId: "claim-dup", citedItemIds: ["item-ok", "alpha-missing"] },
146
+ ];
147
+ const forward = lintCuratedClaims({ claims, availableItemIds: available });
148
+ const reversed = lintCuratedClaims({ claims: [...claims].reverse(), availableItemIds: available });
149
+ assert.deepEqual(forward, reversed, "reversing the input must not reorder findings");
150
+ assert.deepEqual(
151
+ forward.map((f) => f.unknownIds),
152
+ [["alpha-missing"], ["zeta-missing"]],
153
+ "findings order by unknownIds once claimId and reason tie",
154
+ );
155
+ });
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Shared-context curation self-check (issue #1957 requirement 4).
3
+ *
4
+ * Pure. A synthesized claim with no traceable source is a lint error:
5
+ * report claims whose citations are missing, blank, or absent from the
6
+ * item ids the curation run actually had available.
7
+ */
8
+
9
+ export interface CuratedClaim {
10
+ claimId: string;
11
+ /** Shared-context item ids this claim was synthesized from. */
12
+ citedItemIds?: readonly string[];
13
+ }
14
+
15
+ export interface CurationLintFinding {
16
+ claimId: string;
17
+ reason: "no_citations" | "unknown_citation";
18
+ /** Cited ids that are not in the available set. Only for unknown_citation. */
19
+ unknownIds?: string[];
20
+ }
21
+
22
+ export function lintCuratedClaims(input: {
23
+ claims: readonly CuratedClaim[];
24
+ availableItemIds: readonly string[];
25
+ }): CurationLintFinding[] {
26
+ const available = new Set(input.availableItemIds);
27
+ const findings: CurationLintFinding[] = [];
28
+
29
+ for (const claim of input.claims) {
30
+ const claimId = claim.claimId;
31
+ if (typeof claimId !== "string" || claimId.trim().length === 0) {
32
+ throw new RangeError("claimId must be a non-blank string");
33
+ }
34
+
35
+ const unknown = new Set<string>();
36
+ let validCount = 0;
37
+ for (const id of claim.citedItemIds ?? []) {
38
+ if (id.trim().length === 0) continue;
39
+ if (available.has(id)) {
40
+ validCount += 1;
41
+ } else {
42
+ unknown.add(id);
43
+ }
44
+ }
45
+
46
+ if (validCount === 0) {
47
+ findings.push({ claimId, reason: "no_citations" });
48
+ continue;
49
+ }
50
+ if (unknown.size > 0) {
51
+ findings.push({ claimId, reason: "unknown_citation", unknownIds: [...unknown].sort() });
52
+ }
53
+ }
54
+
55
+ findings.sort((a, b) => {
56
+ if (a.claimId !== b.claimId) return a.claimId < b.claimId ? -1 : 1;
57
+ if (a.reason !== b.reason) return a.reason < b.reason ? -1 : 1;
58
+ // Duplicate claim ids are linted independently, so two findings can share
59
+ // both keys above and still differ. Without this tertiary key the result
60
+ // would depend on upstream iteration order, which the deterministic-output
61
+ // contract forbids.
62
+ const left = (a.unknownIds ?? []).join(",");
63
+ const right = (b.unknownIds ?? []).join(",");
64
+ if (left !== right) return left < right ? -1 : 1;
65
+ return 0;
66
+ });
67
+ return findings;
68
+ }