@harperfast/harper 5.2.9 → 5.2.12

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 (177) hide show
  1. package/dist/resources/DatabaseTransaction.d.ts +3 -0
  2. package/dist/resources/DatabaseTransaction.js +90 -2
  3. package/dist/resources/DatabaseTransaction.js.map +1 -1
  4. package/dist/resources/PrimaryRocksDatabase.js +2 -1
  5. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  6. package/dist/resources/RocksIndexStore.js +2 -1
  7. package/dist/resources/RocksIndexStore.js.map +1 -1
  8. package/dist/resources/Table.js +13 -5
  9. package/dist/resources/Table.js.map +1 -1
  10. package/dist/resources/auditStore.js +142 -53
  11. package/dist/resources/auditStore.js.map +1 -1
  12. package/dist/resources/databases.d.ts +10 -0
  13. package/dist/resources/databases.js +369 -97
  14. package/dist/resources/databases.js.map +1 -1
  15. package/dist/resources/search.js +33 -7
  16. package/dist/resources/search.js.map +1 -1
  17. package/dist/server/storageReclamation.d.ts +7 -0
  18. package/dist/server/storageReclamation.js +10 -0
  19. package/dist/server/storageReclamation.js.map +1 -1
  20. package/dist/server/threads/manageThreads.d.ts +1 -0
  21. package/dist/server/threads/manageThreads.js +7 -0
  22. package/dist/server/threads/manageThreads.js.map +1 -1
  23. package/npm-shrinkwrap.json +41 -41
  24. package/package.json +2 -2
  25. package/resources/DatabaseTransaction.ts +88 -2
  26. package/resources/PrimaryRocksDatabase.ts +2 -1
  27. package/resources/RocksIndexStore.ts +2 -1
  28. package/resources/Table.ts +13 -5
  29. package/resources/auditStore.ts +144 -52
  30. package/resources/databases.ts +351 -93
  31. package/resources/search.ts +32 -7
  32. package/server/storageReclamation.ts +10 -0
  33. package/server/threads/manageThreads.js +7 -0
  34. package/studio/web/assets/Chat-CokJOjyX.js +2267 -0
  35. package/studio/web/assets/FloatingChat-h9BJo6YG.js +23 -0
  36. package/studio/web/assets/{abnfDiagram-VCTEODGH-C0_BAZyO.js → abnfDiagram-VCTEODGH-B0BebmD2.js} +1 -1
  37. package/studio/web/assets/{alertDialog-DIHt7Z0r.js → alertDialog-CQyAJJhl.js} +1 -1
  38. package/studio/web/assets/{apiToken-c3Rd-w6g.js → apiToken-B_g12QrM.js} +1 -1
  39. package/studio/web/assets/applications-Dlq5kJ2O.js +296 -0
  40. package/studio/web/assets/architecture-7GRP2DOG-LB-MLAAb.js +1 -0
  41. package/studio/web/assets/{architectureDiagram-5GKGNRK7-BWzrASgm.js → architectureDiagram-5GKGNRK7-7SW3GD-K.js} +1 -1
  42. package/studio/web/assets/authStore-C3Nfubqr.js +3 -0
  43. package/studio/web/assets/{blockDiagram-NRAW4CY4-BdJX9Khj.js → blockDiagram-I7D4REHJ-BqguiadH.js} +2 -2
  44. package/studio/web/assets/{button-DhiX-njv.js → button-BIsUKRZq.js} +2 -2
  45. package/studio/web/assets/{c4Diagram-UCG6FXSJ-CI6MzGmQ.js → c4Diagram-7LVT6UL2-LBNf8t_X.js} +1 -1
  46. package/studio/web/assets/channel-yictG-U-.js +1 -0
  47. package/studio/web/assets/{chevron-up-Df2c8uoU.js → chevron-up-DtKGqDn3.js} +1 -1
  48. package/studio/web/assets/{chunk-TEH6E4GO-P87k5mNi.js → chunk-4HAMMTFA-DWtTut21.js} +1 -1
  49. package/studio/web/assets/{chunk-75Z2AOVW-BT8tVmks.js → chunk-75Z2AOVW-QGQD6th2.js} +1 -1
  50. package/studio/web/assets/{chunk-DU6HZSFF-9kAOOmI6.js → chunk-DU6HZSFF-Chq20Ba5.js} +1 -1
  51. package/studio/web/assets/{chunk-F27PBJKO-BW7ao8AY.js → chunk-F27PBJKO-BVA5EPhV.js} +1 -1
  52. package/studio/web/assets/{chunk-GMAD6QVW-BNyXpoQO.js → chunk-GMAD6QVW-BeS7S07A.js} +1 -1
  53. package/studio/web/assets/{chunk-OBVCFTLP-D4wWOqDQ.js → chunk-GVQU2GXP-sbwVIQ8i.js} +1 -1
  54. package/studio/web/assets/{chunk-G27WJ6UU-COyLMcgK.js → chunk-IMKFNOWR-Bnh3tAVd.js} +1 -1
  55. package/studio/web/assets/{chunk-JQ64N6SF-Cyz1IeLf.js → chunk-L3NEJ4N5-COfUyKII.js} +1 -1
  56. package/studio/web/assets/chunk-OSK3NFVY-ByciRftO.js +10 -0
  57. package/studio/web/assets/{chunk-P2QGCYS3-DmIFY4d7.js → chunk-P2QGCYS3-CP1VhG_c.js} +1 -1
  58. package/studio/web/assets/{chunk-POPQ4Y6H-BPrvMyKz.js → chunk-POPQ4Y6H-ClWhhkwW.js} +1 -1
  59. package/studio/web/assets/{chunk-PWAF6VOD-2zB6IW9i.js → chunk-PWAF6VOD-1z1THyS5.js} +1 -1
  60. package/studio/web/assets/{chunk-RHFEMEQ7-2FgyI8YU.js → chunk-SHT3W25Y-LpQkMsah.js} +2 -2
  61. package/studio/web/assets/{chunk-SVP7TREG-FwtbH2QC.js → chunk-SVP7TREG-jtdAHw0S.js} +1 -1
  62. package/studio/web/assets/{chunk-LCL6LL3I-HOzK_ppE.js → chunk-TICWLB2K-VOwzetX-.js} +1 -1
  63. package/studio/web/assets/classDiagram-ZZMXUADV-VaEwSy_g.js +1 -0
  64. package/studio/web/assets/classDiagram-v2-VYDZK3BY-VaEwSy_g.js +1 -0
  65. package/studio/web/assets/{createLucideIcon-BKGPfjm2.js → createLucideIcon-CzW9508A.js} +1 -1
  66. package/studio/web/assets/{cssMode-CEN2mzSA.js → cssMode-DAjzPoBf.js} +1 -1
  67. package/studio/web/assets/{cynefin-OW5HDTMX-BRkpLFQV.js → cynefin-OW5HDTMX-BbdbCvub.js} +1 -1
  68. package/studio/web/assets/{cynefinDiagram-5FMLGOSQ-CHT1DaX6.js → cynefinDiagram-5FMLGOSQ-TP-aIqbt.js} +1 -1
  69. package/studio/web/assets/{dagre-3AP2YEHR-DpUXBh63.js → dagre-GXQ25YYZ-DShnGpGo.js} +1 -1
  70. package/studio/web/assets/{diagram-S7CK7UJ4-BuymVFZT.js → diagram-S7CK7UJ4-aoCVTtcy.js} +1 -1
  71. package/studio/web/assets/{diagram-UQ7AKVKN-CyP148RM.js → diagram-UQ7AKVKN-DglXtQ6x.js} +1 -1
  72. package/studio/web/assets/{diagram-VSXAHHWV-CoCAg3M9.js → diagram-VSXAHHWV-fhEdmkwM.js} +1 -1
  73. package/studio/web/assets/{diagram-VX7I27RA-BpOqCFca.js → diagram-VX7I27RA-DccVJet6.js} +1 -1
  74. package/studio/web/assets/{diagram-Z3DM3KII-Bfpw7Vbj.js → diagram-Z3DM3KII-D-RyJJb7.js} +1 -1
  75. package/studio/web/assets/{dialog-CBf0Mr1d.js → dialog-Cn2uWgD4.js} +1 -1
  76. package/studio/web/assets/{dist-lkA3O3eM.js → dist-DP8UjMB_.js} +1 -1
  77. package/studio/web/assets/{download-BtTOBem-.js → download-B5T5r7ss.js} +1 -1
  78. package/studio/web/assets/{ebnfDiagram-PWID7BFC-DS_6aWqL.js → ebnfDiagram-PWID7BFC-DJGpIpz_.js} +1 -1
  79. package/studio/web/assets/{editor-D8oDeCTL.js → editor-VUNQ-5Ye.js} +1 -1
  80. package/studio/web/assets/{erDiagram-SSCWMZ5O-DJNk6Fgw.js → erDiagram-RLTQ6QDP-CIfNlgkC.js} +1 -1
  81. package/studio/web/assets/eventmodeling-NTZA5JFV-CLxnp2CR.js +1 -0
  82. package/studio/web/assets/flowDiagram-HODETNUW-BIbhmz9f.js +1 -0
  83. package/studio/web/assets/{ganttDiagram-EL5Y4UJY-2pOExxMY.js → ganttDiagram-EL5Y4UJY-BxToTzzD.js} +1 -1
  84. package/studio/web/assets/{getAnalytics-D4LKGeVy.js → getAnalytics-GHK8ORfM.js} +1 -1
  85. package/studio/web/assets/{gitGraph-4MIJSDKK-CH5ZxwzF.js → gitGraph-4MIJSDKK-D2s2w8lE.js} +1 -1
  86. package/studio/web/assets/{gitGraphDiagram-WWUBYQGX-DVIsIhbO.js → gitGraphDiagram-WWUBYQGX-Dwntd4-x.js} +1 -1
  87. package/studio/web/assets/{html-u3vOg7LJ.js → html-CFqhtdfa.js} +1 -1
  88. package/studio/web/assets/{htmlMode-DyO31v-P.js → htmlMode-CpgOTfjz.js} +1 -1
  89. package/studio/web/assets/{index-Cxj2_wsl.css → index-7RMEgVG1.css} +1 -1
  90. package/studio/web/assets/index-ZQ1upnh7.js +824 -0
  91. package/studio/web/assets/index.lazy-HDafSRxx.js +2 -0
  92. package/studio/web/assets/{info-A6RAGUB7-CPQfTnaG.js → info-A6RAGUB7-DYjkvb0C.js} +1 -1
  93. package/studio/web/assets/{infoDiagram-RXCK75RN-DlwLYlwm.js → infoDiagram-27XIBGKW-Bnp1FJE5.js} +1 -1
  94. package/studio/web/assets/{ishikawaDiagram-5VMMS53U-BRXRp29U.js → ishikawaDiagram-5VMMS53U-D9Xh2r6X.js} +1 -1
  95. package/studio/web/assets/{javascript-CUvxOyTC.js → javascript-9l5JXJwc.js} +1 -1
  96. package/studio/web/assets/{journeyDiagram-EYS64GPL-B0ou8k0n.js → journeyDiagram-3NMN7TZE-CokIi6ll.js} +2 -2
  97. package/studio/web/assets/{jsonMode-f_IwbF3D.js → jsonMode-BJBH-iR0.js} +1 -1
  98. package/studio/web/assets/{kanban-definition-3QL26DDD-uYg7iYzp.js → kanban-definition-UXKFOSKX-CukSFJfX.js} +1 -1
  99. package/studio/web/assets/{languageServices-DXtZ6rEF.js → languageServices-0ZvgO63b.js} +1 -1
  100. package/studio/web/assets/{lspLanguageFeatures-B4pCF1zO.js → lspLanguageFeatures-sNVDopx3.js} +1 -1
  101. package/studio/web/assets/{mermaid-parser.core-Ck-fC8b7.js → mermaid-parser.core-BlEsOWNO.js} +3 -3
  102. package/studio/web/assets/{mermaid.core-CP8aNNYm.js → mermaid.core-BlkGaMIH.js} +5 -5
  103. package/studio/web/assets/{mindmap-definition-FBJOCRG2-CgTZ-rit.js → mindmap-definition-YA3MSWOX-IprMc_0j.js} +1 -1
  104. package/studio/web/assets/notifications-BukjVuVe.js +1 -0
  105. package/studio/web/assets/{notifications-D3tIQ4sg.js → notifications-DvBJIIO0.js} +1 -1
  106. package/studio/web/assets/{packet-AYTQ26CC-DEyoPtPb.js → packet-AYTQ26CC-Bi3V04Zi.js} +1 -1
  107. package/studio/web/assets/{pegDiagram-XKGWAZYB-DrD-7sD9.js → pegDiagram-XKGWAZYB-BNuPDLZY.js} +1 -1
  108. package/studio/web/assets/{pie-WAS4IAKB-wjj-EI1d.js → pie-WAS4IAKB-_6DoDbng.js} +1 -1
  109. package/studio/web/assets/{pieDiagram-E7YTZNPT-GntqDCzv.js → pieDiagram-E7YTZNPT-DqNb6Ht2.js} +1 -1
  110. package/studio/web/assets/{profile-DZWU7MgT.js → profile-B-jyYdzV.js} +1 -1
  111. package/studio/web/assets/{quadrantDiagram-AXDQQJYC-0UeqXQGd.js → quadrantDiagram-AXDQQJYC-BGH9E2YR.js} +1 -1
  112. package/studio/web/assets/{radar-RG4KPBEZ-DFSA5h7k.js → radar-RG4KPBEZ-DDdVczcL.js} +1 -1
  113. package/studio/web/assets/{railroad-74A4TZTK-CaOUG9wR.js → railroad-74A4TZTK-BJUP4Jds.js} +1 -1
  114. package/studio/web/assets/railroad-abnf-HS5TGJTU-Bm3L1L0h.js +1 -0
  115. package/studio/web/assets/railroad-ebnf-LZEXJU2U-CKzLGlkw.js +1 -0
  116. package/studio/web/assets/railroad-peg-WCYAUIDC-S8xLjslx.js +1 -0
  117. package/studio/web/assets/{railroadDiagram-O6MQD6OU-yHUELZaV.js → railroadDiagram-O6MQD6OU-DGTPh2KZ.js} +1 -1
  118. package/studio/web/assets/{regions-CkyurXzE.js → regions-GN6Mu5aH.js} +1 -1
  119. package/studio/web/assets/{register-BUyhWjBO.js → register-CU-gCfLP.js} +3 -3
  120. package/studio/web/assets/{requirementDiagram-EFPCY7ZU-DNEGFjuW.js → requirementDiagram-BXWQKSXE-BJnO6uLz.js} +1 -1
  121. package/studio/web/assets/{sankeyDiagram-P5KCCOFB-DpyAmSVR.js → sankeyDiagram-P5KCCOFB-0vSOdymH.js} +1 -1
  122. package/studio/web/assets/{sequenceDiagram-WJ2MYXX4-TyaT7xNk.js → sequenceDiagram-WJ2MYXX4-hETizDWE.js} +1 -1
  123. package/studio/web/assets/{setComponentFile-CeyKSZAa.js → setComponentFile-D7Fww0lC.js} +1 -1
  124. package/studio/web/assets/{setup-D2kn7cAA.js → setup-DG3Ws0Gg.js} +2 -2
  125. package/studio/web/assets/{stateDiagram-HBIQ2CUA-CeEdTArZ.js → stateDiagram-D77RDMKH-CdYQ_KtC.js} +1 -1
  126. package/studio/web/assets/stateDiagram-v2-MP3YSRHH-CdKuzQMT.js +1 -0
  127. package/studio/web/assets/status-koJd66XF.js +61 -0
  128. package/studio/web/assets/{swimlanes-XN3QIQJK-B54FmF46.js → swimlanes-42K2YHIH-B8cHIpU4.js} +1 -1
  129. package/studio/web/assets/swimlanesDiagram-VR7AAH4N-DmOSJwaH.js +8 -0
  130. package/studio/web/assets/{tabs-B_G5zscN.js → tabs-BrHu7gJi.js} +1 -1
  131. package/studio/web/assets/{timeline-definition-24CTP7MA-D-a9ujbo.js → timeline-definition-24CTP7MA-BJWYSXqF.js} +1 -1
  132. package/studio/web/assets/{toggleHighContrast-C0UW6rI2.js → toggleHighContrast-CGksgPgv.js} +1 -1
  133. package/studio/web/assets/{treeView-Q6P3EWNA-CrW_6JnS.js → treeView-Q6P3EWNA-qxe_v6CQ.js} +1 -1
  134. package/studio/web/assets/{treemap-WGGIJYW6-BxyYLdP_.js → treemap-WGGIJYW6-dDo97XXF.js} +1 -1
  135. package/studio/web/assets/{tsMode-DBC0zmDx.js → tsMode-D3T_5QzW.js} +1 -1
  136. package/studio/web/assets/{typescript-DApRQir3.js → typescript-BegjUvtB.js} +1 -1
  137. package/studio/web/assets/{useEntityRestURL-DB6JStU1.js → useEntityRestURL-BicFpx_N.js} +1 -1
  138. package/studio/web/assets/{useLocalStorage-Dtj1QS8_.js → useLocalStorage-BqMR3D8_.js} +1 -1
  139. package/studio/web/assets/vendor-core-c2JRRJpV.js +58 -0
  140. package/studio/web/assets/vendor-datadog-CLUcJXOo.js +6 -0
  141. package/studio/web/assets/{vendor-react-Dyj4O3HE.js → vendor-react-CJV_K1u4.js} +1 -1
  142. package/studio/web/assets/vendor-tanstack-DxzraizX.js +1 -0
  143. package/studio/web/assets/{vendor-ui-vhu-UHhF.js → vendor-ui-BUjK0h8a.js} +2 -2
  144. package/studio/web/assets/{vennDiagram-4TSXK5OY-Cy7s7Mpy.js → vennDiagram-4TSXK5OY-A3i-lCdl.js} +1 -1
  145. package/studio/web/assets/{wardley-WFR3VGLG-BeBL35g2.js → wardley-WFR3VGLG-B0ik-_6g.js} +1 -1
  146. package/studio/web/assets/{wardleyDiagram-VM6X3IG4-BylmIGSg.js → wardleyDiagram-VM6X3IG4-CjrkKWUR.js} +1 -1
  147. package/studio/web/assets/{workers-C0bFIedw.js → workers-Bmx4l_9B.js} +1 -1
  148. package/studio/web/assets/x-DIzaLEdK.js +1 -0
  149. package/studio/web/assets/{xml-HWd01lU-.js → xml-Bg40Rcra.js} +1 -1
  150. package/studio/web/assets/{xychartDiagram-S5SC5T6Z-CoKALMXr.js → xychartDiagram-S5SC5T6Z-Biok4GYV.js} +1 -1
  151. package/studio/web/assets/{yaml-CIH0Nt-h.js → yaml-DolDvfXE.js} +1 -1
  152. package/studio/web/index.html +14 -14
  153. package/studio/web/assets/Chat-JpO8EtUu.js +0 -2067
  154. package/studio/web/assets/FloatingChat-Bcj3xSZu.js +0 -23
  155. package/studio/web/assets/applications-ByqLRKyZ.js +0 -296
  156. package/studio/web/assets/architecture-7GRP2DOG-DNdx5tEU.js +0 -1
  157. package/studio/web/assets/authStore-qKmCZcaf.js +0 -3
  158. package/studio/web/assets/channel-DtCV8PTL.js +0 -1
  159. package/studio/web/assets/chunk-R7TYR2AO-Irip67yr.js +0 -10
  160. package/studio/web/assets/classDiagram-DTDB5LWJ-DbO_dCNE.js +0 -1
  161. package/studio/web/assets/classDiagram-v2-JRS7N3AN-DbO_dCNE.js +0 -1
  162. package/studio/web/assets/eventmodeling-NTZA5JFV-5jbe4A5P.js +0 -1
  163. package/studio/web/assets/flowDiagram-A5DVABFB-Dp9Ezlow.js +0 -1
  164. package/studio/web/assets/index-aSt5tY-L.js +0 -824
  165. package/studio/web/assets/index.lazy-B9jiPwT8.js +0 -2
  166. package/studio/web/assets/notifications-CUoYgU98.js +0 -1
  167. package/studio/web/assets/railroad-abnf-HS5TGJTU-Bc0Qi0WH.js +0 -1
  168. package/studio/web/assets/railroad-ebnf-LZEXJU2U-G8rVVZ2C.js +0 -1
  169. package/studio/web/assets/railroad-peg-WCYAUIDC-CrehKBhC.js +0 -1
  170. package/studio/web/assets/stateDiagram-v2-4QOOHH4V-D4tuw9Su.js +0 -1
  171. package/studio/web/assets/status-D7Xn5ePA.js +0 -61
  172. package/studio/web/assets/swimlanesDiagram-VK2B7HYN-XOhmNEvq.js +0 -8
  173. package/studio/web/assets/vendor-core-RCcadM3e.js +0 -73
  174. package/studio/web/assets/vendor-datadog-BRv-mOv1.js +0 -6
  175. package/studio/web/assets/vendor-tanstack-BiFWSB3W.js +0 -1
  176. package/studio/web/assets/x-B9o9hsep.js +0 -1
  177. /package/studio/web/assets/{sizeCapture-X5ZJPWSS-B0uUizjq.js → sizeCapture-INFHLROL-B0uUizjq.js} +0 -0
@@ -118,6 +118,13 @@ const warnedBodylessMints = new Set<string>();
118
118
  const warnedBodylessTables = new Set<number>();
119
119
  const FLOAT_TARGET = new Float64Array(1);
120
120
  const FLOAT_BUFFER = new Uint8Array(FLOAT_TARGET.buffer);
121
+
122
+ /** Last resort on a detached path: a failing log sink must not itself become an unhandled rejection. */
123
+ function warnContained(message: string, error: unknown) {
124
+ try {
125
+ harperLogger.warn(message, error);
126
+ } catch {}
127
+ }
121
128
  let DEFAULT_AUDIT_CLEANUP_DELAY = 10000; // default delay of 10 seconds
122
129
  let timestampErrored = false;
123
130
  export function openAuditStore(rootStore) {
@@ -133,7 +140,10 @@ export function openAuditStore(rootStore) {
133
140
  if (!auditStore) {
134
141
  // this means we are creating a new audit store. Initialize with the last removed timestamp (we don't want to put this in legacy audit logs since we don't know if they have had deletions or not).
135
142
  auditStore = rootStore.openDB(AUDIT_STORE_NAME, AUDIT_STORE_OPTIONS);
136
- updateLastRemoved(auditStore, 1);
143
+ // this open path is synchronous, so nothing downstream can own the write's rejection
144
+ updateLastRemoved(auditStore, 1)?.catch?.((error) =>
145
+ warnContained('Error initializing the audit log last-removed marker', error)
146
+ );
137
147
  }
138
148
  const superGetRange = auditStore.getRange.bind(auditStore);
139
149
  auditStore.getRange = function (options) {
@@ -164,7 +174,15 @@ export function openAuditStore(rootStore) {
164
174
  let pendingCleanupResolve: (() => void) | null = null;
165
175
  let lastCleanupResolution: Promise<void>;
166
176
  let cleanupPriority = 0;
167
- let auditCleanupDelay = DEFAULT_AUDIT_CLEANUP_DELAY;
177
+ auditStore.auditCleanupDelay = DEFAULT_AUDIT_CLEANUP_DELAY;
178
+ let cleanupStopped = false;
179
+ // a last-removed marker whose write failed, retried on later passes: dropping it would leave
180
+ // getLastRemoved() reporting a boundary the entries behind it have already been deleted past
181
+ let pendingLastRemoved: number | undefined;
182
+ const isRocksAuditStore = auditStore instanceof RocksTransactionLogStore;
183
+ // A pass yields, so the store can be closed underneath it. Every touch of the environment after a
184
+ // resume — cursor advance, cursor release, marker write, re-arm — has to re-check.
185
+ const storeClosing = () => auditStore.rootStore.status === 'closed' || auditStore.rootStore.status === 'closing';
168
186
  onStorageReclamation(rootStore.path, (priority) => {
169
187
  cleanupPriority = priority; // update the priority
170
188
  if (priority) {
@@ -175,28 +193,23 @@ export function openAuditStore(rootStore) {
175
193
  /**
176
194
  * Schedules a pass of the audit cleanup loop. The returned promise fulfills once the pass that
177
195
  * serves this call has finished, so callers (notably tests) can await completion instead of
178
- * guessing a delay. Two things it does not promise: a pass removes at most
196
+ * guessing a delay. Two things it does not promise: an LMDB pass removes at most
179
197
  * MAX_DELETES_PER_CLEANUP entries before rescheduling itself, so fulfillment means "that pass
180
198
  * finished", not "the audit log is fully pruned"; and a pass that failed logs and fulfills rather
181
199
  * than rejecting, since this promise doubles as the loop's serialization barrier.
182
200
  */
183
201
  function scheduleAuditCleanup(newCleanupDelay?: number): Promise<void> {
184
202
  // Skip audit cleanup/purge in read-only mode
185
- if (isReadOnlyMode()) return Promise.resolve();
186
- if (auditStore instanceof RocksTransactionLogStore) {
187
- auditStore.rootStore.purgeLogs({
188
- before: Date.now() - auditRetention / (1 + cleanupPriority * cleanupPriority),
189
- });
190
- return Promise.resolve();
191
- }
203
+ if (cleanupStopped || isReadOnlyMode()) return Promise.resolve();
192
204
 
193
- if (newCleanupDelay) auditCleanupDelay = newCleanupDelay;
205
+ if (newCleanupDelay) auditStore.auditCleanupDelay = newCleanupDelay;
194
206
  // the pass we are about to cancel has not started, so its callers are handed over to this one
195
207
  const supersededResolve = pendingCleanupResolve;
196
208
  clearTimeout(pendingCleanup);
197
209
  const resolution = new Promise<void>((resolve) => {
198
210
  pendingCleanupResolve = resolve;
199
- pendingCleanup = setTimeout(async () => {
211
+ const runCleanupPass = async () => {
212
+ pendingCleanup = null;
200
213
  pendingCleanupResolve = null; // started, so a later schedule can no longer cancel this pass
201
214
  // claim the serialization slot before yielding: assigning it after the await lets every
202
215
  // pass released by the same resolution run concurrently over the same range
@@ -204,67 +217,146 @@ export function openAuditStore(rootStore) {
204
217
  lastCleanupResolution = resolution;
205
218
  await previousCleanup;
206
219
  // query for audit entries that are old
207
- if (auditStore.rootStore.status === 'closed' || auditStore.rootStore.status === 'closing') {
220
+ if (cleanupStopped || storeClosing()) {
208
221
  // nothing to clean up and nothing to reschedule, but leaving `resolution` pending would
209
222
  // wedge the loop permanently: it is now the resolution every later pass awaits
210
223
  resolve();
211
224
  return;
212
225
  }
226
+ const passCleanupPriority = cleanupPriority;
213
227
  let deleted = 0;
214
228
  let lastKey: any;
215
229
  try {
216
- for (const auditRecord of auditStore.getRange({
217
- start: 1, // must not be zero or it will be interpreted as null and overlap with symbols in search
218
- snapshot: false,
219
- end: Date.now() - auditRetention / (1 + cleanupPriority * cleanupPriority), // remove up until the audit retention time, reducing audit retention time if cleanup is higher priority
220
- })) {
230
+ if (isRocksAuditStore) {
231
+ auditStore.rootStore.purgeLogs({
232
+ before: Date.now() - auditRetention / (1 + passCleanupPriority * passCleanupPriority),
233
+ });
234
+ } else {
235
+ // Driven explicitly rather than with for-of: this loop suspends on the awaits below, and a
236
+ // close landing mid-pass closes the env under it. for-of calls next() before the body, so a
237
+ // check inside the body advances the cursor first — the guard has to precede every next().
238
+ const entries = auditStore
239
+ .getRange({
240
+ start: 1, // must not be zero or it will be interpreted as null and overlap with symbols in search
241
+ snapshot: false,
242
+ end: Date.now() - auditRetention / (1 + passCleanupPriority * passCleanupPriority), // remove up until the audit retention time, reducing audit retention time if cleanup is higher priority
243
+ })
244
+ [Symbol.iterator]();
221
245
  try {
222
- // awaited so a rejection (not just a synchronous throw) is caught here instead of
223
- // escaping as an unhandled rejection once a later iteration's promise replaces this one
224
- await removeAuditEntry(auditStore, auditRecord);
225
- } catch (error) {
226
- harperLogger.warn('Error removing audit entry', error);
227
- }
228
- lastKey = auditRecord.key;
229
- await new Promise(setImmediate);
230
- if (++deleted >= MAX_DELETES_PER_CLEANUP) {
231
- // limit the amount we cleanup per event turn so we don't use too much memory/CPU
232
- auditCleanupDelay = 10; // and keep trying very soon
233
- break;
246
+ while (!cleanupStopped && !storeClosing()) {
247
+ const entry = entries.next();
248
+ if (entry.done) break;
249
+ const auditRecord = entry.value;
250
+ try {
251
+ // awaited so a rejection (not just a synchronous throw) is caught here instead of
252
+ // escaping as an unhandled rejection once a later iteration's promise replaces this one
253
+ await removeAuditEntry(auditStore, auditRecord);
254
+ } catch (error) {
255
+ harperLogger.warn('Error removing audit entry', error);
256
+ }
257
+ lastKey = auditRecord.key;
258
+ await new Promise(setImmediate);
259
+ if (++deleted >= MAX_DELETES_PER_CLEANUP) {
260
+ // limit the amount we cleanup per event turn so we don't use too much memory/CPU
261
+ auditStore.auditCleanupDelay = 10; // and keep trying very soon
262
+ break;
263
+ }
264
+ }
265
+ } finally {
266
+ // for-of released the underlying cursor on break; an explicit loop owes that itself.
267
+ // Skipped only once the root is closing: releasing a cursor into a closed env reaches
268
+ // native code, while a retirement that leaves the env open still owes the release.
269
+ if (!storeClosing()) entries.return?.();
234
270
  }
235
271
  }
236
272
  } catch (error) {
237
- // the timer callback is detached, so anything escaping here lands as an unhandled
238
- // rejection instead of a log line — and skips the reschedule below with it
273
+ // a failed scan is a log line, not a retired loop: the bookkeeping and reschedule below
274
+ // still run
239
275
  harperLogger.warn('Error during audit log cleanup', error);
240
276
  } finally {
241
- // resolve() first and unconditionally: updateLastRemoved() below can throw
242
- // synchronously if the store transitions to closing/closed mid-pass, and a throw
243
- // from the rest of this block must not skip settling `resolution` — that's the
244
- // serialization barrier every later pass awaits, and never settling it wedges the
245
- // cleanup loop for the life of the store.
246
- resolve();
247
- if (deleted === 0) {
248
- // if we didn't delete anything, we can increase the delay (double until we get to one tenth of
249
- // the retention time). Plain arithmetic, not `<<`/`>>`: those coerce to int32, so a
250
- // sub-millisecond retention collapsed the delay to 0 permanently (0 << 1 is 0), and a
251
- // retention over ~248 days grows it past 2^31 where halving it wraps negative.
252
- auditCleanupDelay = Math.max(1, Math.min(auditCleanupDelay * 2, auditRetention / 10, MAX_CLEANUP_DELAY));
253
- } else {
254
- // if we did delete something, update our updates since timestamp
255
- updateLastRemoved(auditStore, lastKey);
256
- // and do updates faster
257
- if (auditCleanupDelay > 100) auditCleanupDelay = auditCleanupDelay / 2;
277
+ try {
278
+ if (isRocksAuditStore) {
279
+ // eligibility only changes on rotation/flush, so the LMDB backoff — keyed on a per-entry
280
+ // delete count — would only rescan the same segments
281
+ auditStore.auditCleanupDelay = Math.max(
282
+ DEFAULT_AUDIT_CLEANUP_DELAY,
283
+ Math.min(auditRetention / (1 + cleanupPriority * cleanupPriority) / 10, MAX_CLEANUP_DELAY)
284
+ );
285
+ } else {
286
+ if (deleted === 0) {
287
+ // if we didn't delete anything, we can increase the delay (double until we get to one tenth of
288
+ // the retention time). Plain arithmetic, not `<<`/`>>`: those coerce to int32, so a
289
+ // sub-millisecond retention collapsed the delay to 0 permanently (0 << 1 is 0), and a
290
+ // retention over ~248 days grows it past 2^31 where halving it wraps negative.
291
+ auditStore.auditCleanupDelay = Math.max(
292
+ 1,
293
+ Math.min(auditStore.auditCleanupDelay * 2, auditRetention / 10, MAX_CLEANUP_DELAY)
294
+ );
295
+ } else {
296
+ pendingLastRemoved = lastKey;
297
+ // and do updates faster
298
+ if (auditStore.auditCleanupDelay > 100) auditStore.auditCleanupDelay = auditStore.auditCleanupDelay / 2;
299
+ }
300
+ // skipped when the store was retired or closed mid-pass — this writes to the audit
301
+ // store — and carried to the next pass instead, so a failed write is not lost
302
+ if (pendingLastRemoved !== undefined && !cleanupStopped && !storeClosing()) {
303
+ const marker = pendingLastRemoved;
304
+ try {
305
+ // awaited so the barrier below covers the write, and so a rejection is logged
306
+ // here rather than escaping the detached timer callback
307
+ await updateLastRemoved(auditStore, marker);
308
+ if (pendingLastRemoved === marker) pendingLastRemoved = undefined;
309
+ } catch (error) {
310
+ harperLogger.warn('Error recording the last removed audit entry', error);
311
+ }
312
+ }
313
+ }
314
+ } finally {
315
+ // settled and re-armed whatever the bookkeeping above threw: this promise is both the
316
+ // serialization barrier every later pass awaits and the drain barrier
317
+ // stopAuditCleanup() hands its callers, so never settling it wedges both
318
+ resolve();
319
+ // both conjuncts are backstops, not the ownership rule — see DESIGN.md: the arming
320
+ // sites already restrict Rocks to the last worker
321
+ if (
322
+ !cleanupStopped &&
323
+ !storeClosing() &&
324
+ (!isRocksAuditStore || (getWorkerIndex() === getWorkerCount() - 1 && !pendingCleanupResolve))
325
+ ) {
326
+ scheduleAuditCleanup();
327
+ }
258
328
  }
259
- scheduleAuditCleanup();
260
329
  }
261
330
  // we can run this pretty frequently since there is very little overhead to these queries
262
- }, auditCleanupDelay).unref();
331
+ };
332
+ pendingCleanup = setTimeout(() => {
333
+ // nothing owns the timer callback's promise, so anything the pass lets escape — including a
334
+ // throw from the logging inside its own containment — would land as an unhandled rejection
335
+ runCleanupPass().catch((error) => {
336
+ warnContained('Error during audit log cleanup', error);
337
+ resolve();
338
+ });
339
+ }, auditStore.auditCleanupDelay).unref();
263
340
  });
264
341
  if (supersededResolve) resolution.then(supersededResolve);
265
342
  return resolution;
266
343
  }
267
344
  auditStore.scheduleAuditCleanup = scheduleAuditCleanup;
345
+ /**
346
+ * Retires the cleanup loop for good, and returns a drain barrier: the promise settles once the pass
347
+ * that was already running has finished. Retirement alone only stops the loop admitting more work —
348
+ * a pass suspended inside `await removeAuditEntry()` still has a write queued whose DBI the native
349
+ * writer consumes later, so a caller that closes or unlinks stores must await this first. The
350
+ * synchronous teardown paths cannot; the in-pass status checks are what covers them.
351
+ */
352
+ auditStore.stopAuditCleanup = function (): Promise<void> {
353
+ cleanupStopped = true;
354
+ clearTimeout(pendingCleanup);
355
+ pendingCleanup = null;
356
+ pendingCleanupResolve?.();
357
+ pendingCleanupResolve = null;
358
+ return lastCleanupResolution ?? Promise.resolve();
359
+ };
268
360
  if (getWorkerIndex() === getWorkerCount() - 1) {
269
361
  scheduleAuditCleanup();
270
362
  }
@@ -296,7 +388,7 @@ export function removeAuditEntry(auditStore: any, auditRecord: AuditRecord): Pro
296
388
 
297
389
  function updateLastRemoved(auditStore, lastKey) {
298
390
  FLOAT_TARGET[0] = lastKey;
299
- auditStore.put(Symbol.for('last-removed'), FLOAT_BUFFER);
391
+ return auditStore.put(Symbol.for('last-removed'), FLOAT_BUFFER);
300
392
  }
301
393
 
302
394
  export function getLastRemoved(auditStore) {