@remnic/core 10.0.49 → 10.0.51

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 (415) hide show
  1. package/dist/access-admin-ops-surface.js +49 -48
  2. package/dist/access-authorization-probe.js +50 -49
  3. package/dist/access-boundary.d.ts +6 -1
  4. package/dist/access-boundary.d.ts.map +1 -1
  5. package/dist/access-boundary.js +49 -48
  6. package/dist/access-cli.js +120 -119
  7. package/dist/access-cli.js.map +1 -1
  8. package/dist/access-errors.d.ts.map +1 -1
  9. package/dist/access-errors.js +1 -1
  10. package/dist/access-extraction-force-flush.js +49 -48
  11. package/dist/access-http-briefing.d.ts +2 -0
  12. package/dist/access-http-briefing.d.ts.map +1 -1
  13. package/dist/access-http-briefing.js +50 -49
  14. package/dist/access-http-query.js +51 -50
  15. package/dist/access-http.d.ts.map +1 -1
  16. package/dist/access-http.js +70 -69
  17. package/dist/access-identity-continuity-surface.js +49 -48
  18. package/dist/access-lcm-surface.js +49 -48
  19. package/dist/access-mcp.js +60 -59
  20. package/dist/access-memory-search-fanout.js +2 -2
  21. package/dist/access-namespace-preflight.js +49 -48
  22. package/dist/access-observe-write-category.d.ts +1 -1
  23. package/dist/access-observe-write-category.d.ts.map +1 -1
  24. package/dist/access-observe-write-category.js +10 -10
  25. package/dist/access-observe-write-surface.d.ts.map +1 -1
  26. package/dist/access-observe-write-surface.js +49 -48
  27. package/dist/access-offline-manifest.js +6 -6
  28. package/dist/access-operations-batch.js +50 -49
  29. package/dist/access-operations.js +57 -56
  30. package/dist/access-recall-concurrency.js +49 -48
  31. package/dist/access-recall-diagnostics.js +2 -2
  32. package/dist/access-recall-response.js +49 -48
  33. package/dist/access-recall-surface.js +49 -48
  34. package/dist/access-schema.d.ts +245 -773
  35. package/dist/access-schema.d.ts.map +1 -1
  36. package/dist/access-schema.js +6 -6
  37. package/dist/access-service.d.ts +5 -0
  38. package/dist/access-service.d.ts.map +1 -1
  39. package/dist/access-service.js +49 -48
  40. package/dist/access-surface-catalog.js +51 -50
  41. package/dist/access-surface-catalog.js.map +1 -1
  42. package/dist/access-token-capabilities.js +2 -2
  43. package/dist/active-recall.js +16 -16
  44. package/dist/{auto-sync-BNCBMU6H.js → auto-sync-UQQ6YJSB.js} +3 -3
  45. package/dist/briefing-deadline.d.ts +7 -0
  46. package/dist/briefing-deadline.d.ts.map +1 -0
  47. package/dist/briefing-deadline.js +13 -0
  48. package/dist/briefing-window.d.ts +12 -4
  49. package/dist/briefing-window.d.ts.map +1 -1
  50. package/dist/briefing-window.js +3 -1
  51. package/dist/briefing.d.ts +2 -0
  52. package/dist/briefing.d.ts.map +1 -1
  53. package/dist/briefing.js +12 -11
  54. package/dist/buffer.js +3 -3
  55. package/dist/calibration.js +2 -2
  56. package/dist/{capsule-merge-RNQUP2VL.js → capsule-merge-D4BTIJGK.js} +3 -3
  57. package/dist/causal-consolidation.js +11 -11
  58. package/dist/{chunk-ESDOQEJM.js → chunk-2AKIVSHP.js} +2 -2
  59. package/dist/{chunk-BWF3KNSQ.js → chunk-2EXF6IIB.js} +2 -2
  60. package/dist/{chunk-AGCEJDG2.js → chunk-2ONKXEC7.js} +3 -3
  61. package/dist/{chunk-3NYIXVCT.js → chunk-2S6JADBB.js} +2 -2
  62. package/dist/chunk-2S6JADBB.js.map +1 -0
  63. package/dist/{chunk-ZSEFGDCI.js → chunk-2TGIKMMZ.js} +2 -2
  64. package/dist/{chunk-MCS4LIXP.js → chunk-43TUWQNR.js} +2 -2
  65. package/dist/{chunk-Q6PYCPHO.js → chunk-44EUCSG4.js} +2 -2
  66. package/dist/{chunk-2J6BIOVN.js → chunk-4FM35YIA.js} +3 -3
  67. package/dist/{chunk-2NLCDNGA.js → chunk-564YP3DW.js} +2 -2
  68. package/dist/{chunk-YY6PWFUC.js → chunk-6ZAU7LAR.js} +3 -3
  69. package/dist/{chunk-HCPVL6SF.js → chunk-72CBJ5VP.js} +3 -3
  70. package/dist/{chunk-AF4UIHON.js → chunk-74AY3S7U.js} +9 -9
  71. package/dist/{chunk-N6EM7ESC.js → chunk-75BK4XGV.js} +27 -19
  72. package/dist/chunk-75BK4XGV.js.map +1 -0
  73. package/dist/{chunk-5IDQVE4U.js → chunk-A3UFV4K3.js} +6 -6
  74. package/dist/{chunk-LWRIGKFM.js → chunk-A5L5KGDI.js} +19 -1
  75. package/dist/chunk-A5L5KGDI.js.map +1 -0
  76. package/dist/{chunk-RZDDNH63.js → chunk-A6JDT6QF.js} +27 -15
  77. package/dist/chunk-A6JDT6QF.js.map +1 -0
  78. package/dist/{chunk-XFND63OB.js → chunk-AHGOOZUC.js} +2 -2
  79. package/dist/{chunk-A7AISUJI.js → chunk-AJG2TAMR.js} +2 -2
  80. package/dist/{chunk-2BY56HE6.js → chunk-AZLD4CKG.js} +36 -36
  81. package/dist/{chunk-E47UQBYW.js → chunk-B35EQQ7L.js} +3 -3
  82. package/dist/{chunk-E6UNM67F.js → chunk-B6SPPYGJ.js} +2 -2
  83. package/dist/{chunk-CMOYD5CA.js → chunk-CCL2OXFO.js} +2 -2
  84. package/dist/{chunk-SVJ5JVZO.js → chunk-CK6GCGMX.js} +4 -4
  85. package/dist/{chunk-QZK6HG4J.js → chunk-DF6ZF2BN.js} +3 -3
  86. package/dist/{chunk-CPBYKOTC.js → chunk-DJFULNXK.js} +4 -4
  87. package/dist/{chunk-VGRBKEBH.js → chunk-DQOGZZJA.js} +3 -3
  88. package/dist/{chunk-YB3WN6XA.js → chunk-EC2KVA27.js} +12 -12
  89. package/dist/{chunk-FRXS6SC7.js → chunk-EKUI7I2X.js} +20 -4
  90. package/dist/chunk-EKUI7I2X.js.map +1 -0
  91. package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
  92. package/dist/{chunk-WIBWONL4.js → chunk-EZS6QJNC.js} +2 -2
  93. package/dist/{chunk-C7WDWD35.js → chunk-FNNGWN3O.js} +5 -5
  94. package/dist/{chunk-PBWJVRKE.js → chunk-FT75AQ74.js} +3 -3
  95. package/dist/{chunk-VY6BD5KB.js → chunk-G6A3SGCE.js} +14 -12
  96. package/dist/chunk-G6A3SGCE.js.map +1 -0
  97. package/dist/{chunk-O4CA53UY.js → chunk-GBGU5OZP.js} +111 -104
  98. package/dist/chunk-GBGU5OZP.js.map +1 -0
  99. package/dist/{chunk-WJCAAVZX.js → chunk-GE3ZX6YK.js} +2 -2
  100. package/dist/{chunk-3PVKV3XL.js → chunk-HCIRR3BA.js} +3 -3
  101. package/dist/{chunk-BJTFLP3J.js → chunk-HEF4BRMT.js} +3 -3
  102. package/dist/{chunk-BMG3SAXA.js → chunk-HQUYENKK.js} +9 -12
  103. package/dist/chunk-HQUYENKK.js.map +1 -0
  104. package/dist/{chunk-6UDLN4IA.js → chunk-I6ORDT54.js} +2 -2
  105. package/dist/{chunk-N5PISU26.js → chunk-IN6U2BS5.js} +2 -2
  106. package/dist/{chunk-3INLTKTW.js → chunk-IUQXRRJB.js} +4 -4
  107. package/dist/{chunk-6W3QH3QW.js → chunk-JIJAWTRK.js} +388 -292
  108. package/dist/chunk-JIJAWTRK.js.map +1 -0
  109. package/dist/{chunk-4KS4IHKH.js → chunk-JX3E4XCW.js} +2 -2
  110. package/dist/{chunk-SW3WHVKN.js → chunk-KAJYSO3U.js} +2 -2
  111. package/dist/{chunk-D76AHITY.js → chunk-KETS5A62.js} +2 -2
  112. package/dist/{chunk-LCHFINL4.js → chunk-KIWO4327.js} +2 -2
  113. package/dist/{chunk-FC3ZFEXF.js → chunk-KJ3MDX4V.js} +2 -2
  114. package/dist/{chunk-7LCGFUMB.js → chunk-KQAFEZQX.js} +4 -4
  115. package/dist/{chunk-6TIVVOVU.js → chunk-L3EFYRYK.js} +9 -9
  116. package/dist/{chunk-3MUKQQ7A.js → chunk-LGEH473S.js} +24 -29
  117. package/dist/chunk-LGEH473S.js.map +1 -0
  118. package/dist/{chunk-X52SXKVC.js → chunk-LMB47UM6.js} +2 -2
  119. package/dist/{chunk-A3WXV7YU.js → chunk-LWMLBVGB.js} +2 -2
  120. package/dist/{chunk-KMTMMBEB.js → chunk-MC7PXJDP.js} +7 -7
  121. package/dist/{chunk-NMOEOQV4.js → chunk-MUNYUSBU.js} +2 -2
  122. package/dist/{chunk-VKUMKVEU.js → chunk-OCRLJJOV.js} +5 -5
  123. package/dist/{chunk-XSJL62LE.js → chunk-ON77FQXH.js} +2 -2
  124. package/dist/{chunk-C3HRYVGD.js → chunk-POJRFSJM.js} +3 -3
  125. package/dist/{chunk-LKRXGOQW.js → chunk-QA3Y2O6H.js} +2 -2
  126. package/dist/{chunk-23ZLN5X6.js → chunk-QT5MGENX.js} +14 -6
  127. package/dist/chunk-QT5MGENX.js.map +1 -0
  128. package/dist/{chunk-QFTNWDJ6.js → chunk-RA7YRL6H.js} +2 -2
  129. package/dist/{chunk-WQFHHJWK.js → chunk-RJDYDF5K.js} +4 -4
  130. package/dist/{chunk-E3DL3CAL.js → chunk-ROAKLYOL.js} +2 -2
  131. package/dist/{chunk-YFXRLRKP.js → chunk-S4OZKPBS.js} +7 -11
  132. package/dist/{chunk-YFXRLRKP.js.map → chunk-S4OZKPBS.js.map} +1 -1
  133. package/dist/{chunk-NWIT3U2N.js → chunk-S5CQI2KF.js} +2 -2
  134. package/dist/{chunk-P6P35FY4.js → chunk-SDESKFKT.js} +2 -2
  135. package/dist/{chunk-OD7ZXCUF.js → chunk-SFRBUBUZ.js} +10 -10
  136. package/dist/{chunk-7XFQSQYS.js → chunk-SHNM7RIT.js} +5 -4
  137. package/dist/chunk-SHNM7RIT.js.map +1 -0
  138. package/dist/{chunk-NH37SP6V.js → chunk-SJD334AG.js} +81 -78
  139. package/dist/chunk-SJD334AG.js.map +1 -0
  140. package/dist/{chunk-O7GNSHFF.js → chunk-SPTPBKZX.js} +2 -2
  141. package/dist/{chunk-HI53EK3Y.js → chunk-TC55M5PO.js} +2 -2
  142. package/dist/{chunk-SA6P2KS3.js → chunk-TEH5MDX5.js} +33 -17
  143. package/dist/chunk-TEH5MDX5.js.map +1 -0
  144. package/dist/{chunk-FOKTW6GY.js → chunk-UCLJTGP3.js} +2 -2
  145. package/dist/{chunk-FYZXZUGU.js → chunk-UQBOPCDK.js} +2 -2
  146. package/dist/{chunk-SUKPM6DV.js → chunk-UWMZT2TY.js} +47 -4
  147. package/dist/chunk-UWMZT2TY.js.map +1 -0
  148. package/dist/{chunk-754XN6LX.js → chunk-V27FK6D6.js} +2 -2
  149. package/dist/{chunk-DM6SOROF.js → chunk-V6Z4NAJZ.js} +2 -2
  150. package/dist/{chunk-5FNZ3G6X.js → chunk-VKQI47DJ.js} +4 -4
  151. package/dist/{chunk-NUK5XU3P.js → chunk-VUMWVYMR.js} +28 -13
  152. package/dist/chunk-VUMWVYMR.js.map +1 -0
  153. package/dist/{chunk-MPJDQC4G.js → chunk-WJCXUE2J.js} +2 -2
  154. package/dist/{chunk-VCBS5GRQ.js → chunk-WY66J4PI.js} +2 -2
  155. package/dist/chunk-X2KO4FK5.js +40 -0
  156. package/dist/chunk-X2KO4FK5.js.map +1 -0
  157. package/dist/{chunk-OOOX5C4X.js → chunk-XL2EE5W3.js} +4 -4
  158. package/dist/{chunk-V4GPFCJ7.js → chunk-XWJUFER3.js} +3 -3
  159. package/dist/{chunk-CZVNOTLR.js → chunk-XXR4A33N.js} +2 -2
  160. package/dist/{chunk-CRJX4OMN.js → chunk-Y3T5KQCR.js} +2 -2
  161. package/dist/{chunk-KNCR7APE.js → chunk-Y77FXULH.js} +8 -8
  162. package/dist/{chunk-2AV2U2SZ.js → chunk-YDIOMDBW.js} +2 -2
  163. package/dist/{chunk-IG747LRN.js → chunk-YO2JEX2Z.js} +2 -2
  164. package/dist/{chunk-HDT3DMUF.js → chunk-ZKK3VMWU.js} +2 -2
  165. package/dist/cli.js +92 -91
  166. package/dist/coding/export-okf-codegraph.js +9 -9
  167. package/dist/compounding/engine.js +11 -11
  168. package/dist/config.js +16 -16
  169. package/dist/connectors/codex-materialize-runner.js +10 -10
  170. package/dist/connectors/index.js +14 -14
  171. package/dist/consolidation-provenance-check.js +4 -4
  172. package/dist/consolidation-undo.js +2 -2
  173. package/dist/convergence-refresh.js +12 -12
  174. package/dist/corpus-watermark.js +14 -14
  175. package/dist/correction/passive-correction-notifications.d.ts +1 -1
  176. package/dist/correction/passive-correction-notifications.d.ts.map +1 -1
  177. package/dist/dashboard-runtime.js +2 -2
  178. package/dist/deep-recall-config.js +2 -2
  179. package/dist/deep-recall-http-glue.js +9 -9
  180. package/dist/{entity-canonical-id-migration-2YOLMOFC.js → entity-canonical-id-migration-F3JVG6K6.js} +3 -3
  181. package/dist/entity-canonical-id-migration-F3JVG6K6.js.map +1 -0
  182. package/dist/entity-retrieval.js +11 -11
  183. package/dist/explicit-capture.js +2 -2
  184. package/dist/external-wiki-access.js +50 -49
  185. package/dist/extraction-span-wire.d.ts.map +1 -1
  186. package/dist/extraction-span-wire.js +1 -1
  187. package/dist/extraction.js +22 -22
  188. package/dist/{first-start-migration-QVD22WWS.js → first-start-migration-A6PHIBUQ.js} +4 -4
  189. package/dist/harmonic-retrieval.js +10 -10
  190. package/dist/index.d.ts +1 -0
  191. package/dist/index.d.ts.map +1 -1
  192. package/dist/index.js +143 -135
  193. package/dist/lcm/engine.js +2 -2
  194. package/dist/lcm/index.js +5 -5
  195. package/dist/local-llm.js +2 -2
  196. package/dist/location/index.js +16 -16
  197. package/dist/location/tagging.d.ts +7 -5
  198. package/dist/location/tagging.d.ts.map +1 -1
  199. package/dist/maintenance/memory-governance.js +10 -10
  200. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
  201. package/dist/maintenance/rebuild-memory-projection-runner.js +12 -12
  202. package/dist/maintenance/rebuild-memory-projection.js +11 -11
  203. package/dist/memory-browse-http-glue.js +2 -2
  204. package/dist/memory-browse-operations.js +50 -49
  205. package/dist/namespaces/migrate.js +11 -11
  206. package/dist/namespaces/storage.js +10 -10
  207. package/dist/offline-sync-file-io.d.ts +15 -0
  208. package/dist/offline-sync-file-io.d.ts.map +1 -1
  209. package/dist/offline-sync-file-io.js +9 -1
  210. package/dist/offline-sync-impression-drain.js +2 -2
  211. package/dist/offline-sync-snapshot-stream.js +4 -4
  212. package/dist/offline-sync-upload-staging.js +2 -2
  213. package/dist/offline-sync.d.ts.map +1 -1
  214. package/dist/offline-sync.js +3 -3
  215. package/dist/operator-doctor-corpus.js +17 -17
  216. package/dist/operator-doctor-replica.js +4 -4
  217. package/dist/operator-toolkit.js +29 -29
  218. package/dist/orchestration/maintenance.js +25 -25
  219. package/dist/orchestrator.d.ts +1 -0
  220. package/dist/orchestrator.d.ts.map +1 -1
  221. package/dist/orchestrator.js +122 -121
  222. package/dist/page-versioning.js +2 -2
  223. package/dist/recall-navigation-http-glue.js +2 -2
  224. package/dist/recall-navigation-operations.js +50 -49
  225. package/dist/recall-navigation.js +49 -48
  226. package/dist/relay/mission.d.ts +731 -3403
  227. package/dist/relay/mission.d.ts.map +1 -1
  228. package/dist/replica-divergence.js +3 -3
  229. package/dist/resume-bundles.js +17 -17
  230. package/dist/schemas.d.ts +346 -1771
  231. package/dist/schemas.d.ts.map +1 -1
  232. package/dist/semantic-consolidation.js +15 -15
  233. package/dist/semantic-rule-promotion.js +10 -10
  234. package/dist/semantic-rule-verifier.js +10 -10
  235. package/dist/shared-context/manager.d.ts +13 -33
  236. package/dist/shared-context/manager.d.ts.map +1 -1
  237. package/dist/source-agent-qualifier.js +18 -18
  238. package/dist/standup/build.js +11 -11
  239. package/dist/storage/entity-canonical-id-migration-adapter.d.ts +1 -0
  240. package/dist/storage/entity-canonical-id-migration-adapter.d.ts.map +1 -1
  241. package/dist/storage/entity-canonical-id-migration.d.ts +7 -0
  242. package/dist/storage/entity-canonical-id-migration.d.ts.map +1 -1
  243. package/dist/storage/entity-canonical-id-references.d.ts +2 -0
  244. package/dist/storage/entity-canonical-id-references.d.ts.map +1 -1
  245. package/dist/storage/entity-migration-completion-cache.d.ts +46 -0
  246. package/dist/storage/entity-migration-completion-cache.d.ts.map +1 -0
  247. package/dist/storage/entity-store.d.ts +1 -8
  248. package/dist/storage/entity-store.d.ts.map +1 -1
  249. package/dist/storage/memory-read-store.d.ts +3 -2
  250. package/dist/storage/memory-read-store.d.ts.map +1 -1
  251. package/dist/storage/windowed-memory-read.d.ts +6 -0
  252. package/dist/storage/windowed-memory-read.d.ts.map +1 -1
  253. package/dist/storage.d.ts +2 -0
  254. package/dist/storage.d.ts.map +1 -1
  255. package/dist/storage.js +9 -9
  256. package/dist/summarizer.js +3 -3
  257. package/dist/support-passport/access-operations.d.ts +51 -51
  258. package/dist/support-passport/contracts.d.ts +85 -126
  259. package/dist/support-passport/contracts.d.ts.map +1 -1
  260. package/dist/support-passport/generated-batch.d.ts +3 -17
  261. package/dist/support-passport/generated-batch.d.ts.map +1 -1
  262. package/dist/support-passport/grant-contracts.d.ts +75 -381
  263. package/dist/support-passport/grant-contracts.d.ts.map +1 -1
  264. package/dist/support-passport/index.js +52 -51
  265. package/dist/support-passport/model-audit.d.ts +17 -82
  266. package/dist/support-passport/model-audit.d.ts.map +1 -1
  267. package/dist/support-passport/model-bridge.d.ts +12 -51
  268. package/dist/support-passport/model-bridge.d.ts.map +1 -1
  269. package/dist/support-passport/model-contracts.d.ts +53 -192
  270. package/dist/support-passport/model-contracts.d.ts.map +1 -1
  271. package/dist/tokens.js +3 -3
  272. package/dist/transfer/capsule-export.js +2 -2
  273. package/dist/transfer/capsule-import.js +4 -4
  274. package/dist/transfer/export-okf.js +10 -10
  275. package/dist/transfer/types.d.ts +32 -737
  276. package/dist/transfer/types.d.ts.map +1 -1
  277. package/dist/verified-recall.js +10 -10
  278. package/dist/who-knows-cli.js +12 -12
  279. package/dist/who-knows.js +11 -11
  280. package/package.json +3 -4
  281. package/src/access-boundary.test.ts +14 -0
  282. package/src/access-boundary.ts +48 -42
  283. package/src/access-category-alias.test.ts +9 -7
  284. package/src/access-errors.test.ts +21 -9
  285. package/src/access-errors.ts +45 -31
  286. package/src/access-http-briefing.ts +4 -0
  287. package/src/access-http.test.ts +41 -12
  288. package/src/access-http.ts +6 -0
  289. package/src/access-observe-write-category.ts +1 -1
  290. package/src/access-observe-write-surface.ts +24 -19
  291. package/src/access-operations-batch.ts +18 -2
  292. package/src/access-schema.ts +13 -17
  293. package/src/access-service.ts +5 -0
  294. package/src/briefing-abort.test.ts +173 -0
  295. package/src/briefing-deadline.test.ts +84 -0
  296. package/src/briefing-deadline.ts +33 -0
  297. package/src/briefing-window-abort.test.ts +47 -0
  298. package/src/briefing-window.ts +33 -4
  299. package/src/briefing.ts +23 -16
  300. package/src/correction/passive-correction-notifications-abort.test.ts +42 -0
  301. package/src/correction/passive-correction-notifications.ts +4 -0
  302. package/src/entity-lazy-compatibility.test.ts +229 -0
  303. package/src/entity-migration-completion-notification.test.ts +148 -0
  304. package/src/extraction-scope-source-agent.test.ts +6 -6
  305. package/src/extraction-span-wire.test.ts +28 -0
  306. package/src/extraction-span-wire.ts +34 -6
  307. package/src/index.ts +1 -1
  308. package/src/location/tagging.ts +14 -12
  309. package/src/offline-sync-file-io.ts +61 -2
  310. package/src/offline-sync.test.ts +313 -20
  311. package/src/offline-sync.ts +43 -21
  312. package/src/orchestrator-init.test.ts +49 -1
  313. package/src/orchestrator.ts +9 -8
  314. package/src/storage/entity-canonical-id-migration-adapter.ts +16 -1
  315. package/src/storage/entity-canonical-id-migration.ts +19 -10
  316. package/src/storage/entity-canonical-id-references.ts +24 -0
  317. package/src/storage/entity-migration-completion-cache.nofollow.test.ts +223 -0
  318. package/src/storage/entity-migration-completion-cache.ts +153 -0
  319. package/src/storage/entity-store.ts +58 -86
  320. package/src/storage/memory-read-store.ts +9 -3
  321. package/src/storage/windowed-memory-read.ts +8 -0
  322. package/src/storage-startup-nonblocking-migration.test.ts +183 -0
  323. package/src/storage.ts +7 -2
  324. package/src/testing/subjects/entity-migration-completion-cache.test.ts +90 -0
  325. package/dist/chunk-23ZLN5X6.js.map +0 -1
  326. package/dist/chunk-3MUKQQ7A.js.map +0 -1
  327. package/dist/chunk-3NYIXVCT.js.map +0 -1
  328. package/dist/chunk-6W3QH3QW.js.map +0 -1
  329. package/dist/chunk-7XFQSQYS.js.map +0 -1
  330. package/dist/chunk-BMG3SAXA.js.map +0 -1
  331. package/dist/chunk-FRXS6SC7.js.map +0 -1
  332. package/dist/chunk-LWRIGKFM.js.map +0 -1
  333. package/dist/chunk-N6EM7ESC.js.map +0 -1
  334. package/dist/chunk-NH37SP6V.js.map +0 -1
  335. package/dist/chunk-NUK5XU3P.js.map +0 -1
  336. package/dist/chunk-O4CA53UY.js.map +0 -1
  337. package/dist/chunk-RZDDNH63.js.map +0 -1
  338. package/dist/chunk-SA6P2KS3.js.map +0 -1
  339. package/dist/chunk-SUKPM6DV.js.map +0 -1
  340. package/dist/chunk-VY6BD5KB.js.map +0 -1
  341. /package/dist/{auto-sync-BNCBMU6H.js.map → auto-sync-UQQ6YJSB.js.map} +0 -0
  342. /package/dist/{entity-canonical-id-migration-2YOLMOFC.js.map → briefing-deadline.js.map} +0 -0
  343. /package/dist/{capsule-merge-RNQUP2VL.js.map → capsule-merge-D4BTIJGK.js.map} +0 -0
  344. /package/dist/{chunk-ESDOQEJM.js.map → chunk-2AKIVSHP.js.map} +0 -0
  345. /package/dist/{chunk-BWF3KNSQ.js.map → chunk-2EXF6IIB.js.map} +0 -0
  346. /package/dist/{chunk-AGCEJDG2.js.map → chunk-2ONKXEC7.js.map} +0 -0
  347. /package/dist/{chunk-ZSEFGDCI.js.map → chunk-2TGIKMMZ.js.map} +0 -0
  348. /package/dist/{chunk-MCS4LIXP.js.map → chunk-43TUWQNR.js.map} +0 -0
  349. /package/dist/{chunk-Q6PYCPHO.js.map → chunk-44EUCSG4.js.map} +0 -0
  350. /package/dist/{chunk-2J6BIOVN.js.map → chunk-4FM35YIA.js.map} +0 -0
  351. /package/dist/{chunk-2NLCDNGA.js.map → chunk-564YP3DW.js.map} +0 -0
  352. /package/dist/{chunk-YY6PWFUC.js.map → chunk-6ZAU7LAR.js.map} +0 -0
  353. /package/dist/{chunk-HCPVL6SF.js.map → chunk-72CBJ5VP.js.map} +0 -0
  354. /package/dist/{chunk-AF4UIHON.js.map → chunk-74AY3S7U.js.map} +0 -0
  355. /package/dist/{chunk-5IDQVE4U.js.map → chunk-A3UFV4K3.js.map} +0 -0
  356. /package/dist/{chunk-XFND63OB.js.map → chunk-AHGOOZUC.js.map} +0 -0
  357. /package/dist/{chunk-A7AISUJI.js.map → chunk-AJG2TAMR.js.map} +0 -0
  358. /package/dist/{chunk-2BY56HE6.js.map → chunk-AZLD4CKG.js.map} +0 -0
  359. /package/dist/{chunk-E47UQBYW.js.map → chunk-B35EQQ7L.js.map} +0 -0
  360. /package/dist/{chunk-E6UNM67F.js.map → chunk-B6SPPYGJ.js.map} +0 -0
  361. /package/dist/{chunk-CMOYD5CA.js.map → chunk-CCL2OXFO.js.map} +0 -0
  362. /package/dist/{chunk-SVJ5JVZO.js.map → chunk-CK6GCGMX.js.map} +0 -0
  363. /package/dist/{chunk-QZK6HG4J.js.map → chunk-DF6ZF2BN.js.map} +0 -0
  364. /package/dist/{chunk-CPBYKOTC.js.map → chunk-DJFULNXK.js.map} +0 -0
  365. /package/dist/{chunk-VGRBKEBH.js.map → chunk-DQOGZZJA.js.map} +0 -0
  366. /package/dist/{chunk-YB3WN6XA.js.map → chunk-EC2KVA27.js.map} +0 -0
  367. /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
  368. /package/dist/{chunk-WIBWONL4.js.map → chunk-EZS6QJNC.js.map} +0 -0
  369. /package/dist/{chunk-C7WDWD35.js.map → chunk-FNNGWN3O.js.map} +0 -0
  370. /package/dist/{chunk-PBWJVRKE.js.map → chunk-FT75AQ74.js.map} +0 -0
  371. /package/dist/{chunk-WJCAAVZX.js.map → chunk-GE3ZX6YK.js.map} +0 -0
  372. /package/dist/{chunk-3PVKV3XL.js.map → chunk-HCIRR3BA.js.map} +0 -0
  373. /package/dist/{chunk-BJTFLP3J.js.map → chunk-HEF4BRMT.js.map} +0 -0
  374. /package/dist/{chunk-6UDLN4IA.js.map → chunk-I6ORDT54.js.map} +0 -0
  375. /package/dist/{chunk-N5PISU26.js.map → chunk-IN6U2BS5.js.map} +0 -0
  376. /package/dist/{chunk-3INLTKTW.js.map → chunk-IUQXRRJB.js.map} +0 -0
  377. /package/dist/{chunk-4KS4IHKH.js.map → chunk-JX3E4XCW.js.map} +0 -0
  378. /package/dist/{chunk-SW3WHVKN.js.map → chunk-KAJYSO3U.js.map} +0 -0
  379. /package/dist/{chunk-D76AHITY.js.map → chunk-KETS5A62.js.map} +0 -0
  380. /package/dist/{chunk-LCHFINL4.js.map → chunk-KIWO4327.js.map} +0 -0
  381. /package/dist/{chunk-FC3ZFEXF.js.map → chunk-KJ3MDX4V.js.map} +0 -0
  382. /package/dist/{chunk-7LCGFUMB.js.map → chunk-KQAFEZQX.js.map} +0 -0
  383. /package/dist/{chunk-6TIVVOVU.js.map → chunk-L3EFYRYK.js.map} +0 -0
  384. /package/dist/{chunk-X52SXKVC.js.map → chunk-LMB47UM6.js.map} +0 -0
  385. /package/dist/{chunk-A3WXV7YU.js.map → chunk-LWMLBVGB.js.map} +0 -0
  386. /package/dist/{chunk-KMTMMBEB.js.map → chunk-MC7PXJDP.js.map} +0 -0
  387. /package/dist/{chunk-NMOEOQV4.js.map → chunk-MUNYUSBU.js.map} +0 -0
  388. /package/dist/{chunk-VKUMKVEU.js.map → chunk-OCRLJJOV.js.map} +0 -0
  389. /package/dist/{chunk-XSJL62LE.js.map → chunk-ON77FQXH.js.map} +0 -0
  390. /package/dist/{chunk-C3HRYVGD.js.map → chunk-POJRFSJM.js.map} +0 -0
  391. /package/dist/{chunk-LKRXGOQW.js.map → chunk-QA3Y2O6H.js.map} +0 -0
  392. /package/dist/{chunk-QFTNWDJ6.js.map → chunk-RA7YRL6H.js.map} +0 -0
  393. /package/dist/{chunk-WQFHHJWK.js.map → chunk-RJDYDF5K.js.map} +0 -0
  394. /package/dist/{chunk-E3DL3CAL.js.map → chunk-ROAKLYOL.js.map} +0 -0
  395. /package/dist/{chunk-NWIT3U2N.js.map → chunk-S5CQI2KF.js.map} +0 -0
  396. /package/dist/{chunk-P6P35FY4.js.map → chunk-SDESKFKT.js.map} +0 -0
  397. /package/dist/{chunk-OD7ZXCUF.js.map → chunk-SFRBUBUZ.js.map} +0 -0
  398. /package/dist/{chunk-O7GNSHFF.js.map → chunk-SPTPBKZX.js.map} +0 -0
  399. /package/dist/{chunk-HI53EK3Y.js.map → chunk-TC55M5PO.js.map} +0 -0
  400. /package/dist/{chunk-FOKTW6GY.js.map → chunk-UCLJTGP3.js.map} +0 -0
  401. /package/dist/{chunk-FYZXZUGU.js.map → chunk-UQBOPCDK.js.map} +0 -0
  402. /package/dist/{chunk-754XN6LX.js.map → chunk-V27FK6D6.js.map} +0 -0
  403. /package/dist/{chunk-DM6SOROF.js.map → chunk-V6Z4NAJZ.js.map} +0 -0
  404. /package/dist/{chunk-5FNZ3G6X.js.map → chunk-VKQI47DJ.js.map} +0 -0
  405. /package/dist/{chunk-MPJDQC4G.js.map → chunk-WJCXUE2J.js.map} +0 -0
  406. /package/dist/{chunk-VCBS5GRQ.js.map → chunk-WY66J4PI.js.map} +0 -0
  407. /package/dist/{chunk-OOOX5C4X.js.map → chunk-XL2EE5W3.js.map} +0 -0
  408. /package/dist/{chunk-V4GPFCJ7.js.map → chunk-XWJUFER3.js.map} +0 -0
  409. /package/dist/{chunk-CZVNOTLR.js.map → chunk-XXR4A33N.js.map} +0 -0
  410. /package/dist/{chunk-CRJX4OMN.js.map → chunk-Y3T5KQCR.js.map} +0 -0
  411. /package/dist/{chunk-KNCR7APE.js.map → chunk-Y77FXULH.js.map} +0 -0
  412. /package/dist/{chunk-2AV2U2SZ.js.map → chunk-YDIOMDBW.js.map} +0 -0
  413. /package/dist/{chunk-IG747LRN.js.map → chunk-YO2JEX2Z.js.map} +0 -0
  414. /package/dist/{chunk-HDT3DMUF.js.map → chunk-ZKK3VMWU.js.map} +0 -0
  415. /package/dist/{first-start-migration-QVD22WWS.js.map → first-start-migration-A6PHIBUQ.js.map} +0 -0
@@ -34,3 +34,31 @@ test("provider schema omits span in off mode and keeps it in on/shadow (issue #2
34
34
  assert.deepEqual(extractionProviderJsonSchema("on"), extractionProviderJsonSchema("shadow"));
35
35
  assert.notEqual(extractionProviderJsonSchema("off"), extractionProviderJsonSchema("on"));
36
36
  });
37
+
38
+ // Providers receive this as the extraction response_format. zod 3 advertised
39
+ // additionalProperties:false on every object; the zod 4 port must too.
40
+ type SchemaNode = {
41
+ type?: string;
42
+ properties?: Record<string, SchemaNode>;
43
+ items?: SchemaNode;
44
+ anyOf?: SchemaNode[];
45
+ additionalProperties?: boolean | SchemaNode;
46
+ };
47
+
48
+ function openObjectSchemas(node: SchemaNode, path: string, found: string[]): void {
49
+ if (node.type === "object" && node.properties && node.additionalProperties !== false) found.push(path);
50
+ for (const [name, child] of Object.entries(node.properties ?? {})) {
51
+ openObjectSchemas(child, `${path}.${name}`, found);
52
+ }
53
+ if (node.items) openObjectSchemas(node.items, `${path}[]`, found);
54
+ node.anyOf?.forEach((child, index) => openObjectSchemas(child, `${path}|${index}`, found));
55
+ }
56
+
57
+ test("provider schema closes every object schema with additionalProperties false", () => {
58
+ for (const mode of ["off", "on"] as const) {
59
+ const open: string[] = [];
60
+ // SAFETY: extractionProviderJsonSchema returns a JSON Schema document; SchemaNode names the keys walked here.
61
+ openObjectSchemas(extractionProviderJsonSchema(mode) as SchemaNode, "$", open);
62
+ assert.deepEqual(open, [], `spanMode=${mode}: objects that let a provider emit extra keys`);
63
+ }
64
+ });
@@ -5,7 +5,7 @@
5
5
  * schema is mode-aware so off-mode structured output cannot advertise `span`.
6
6
  */
7
7
 
8
- import { zodToJsonSchema } from "zod-to-json-schema";
8
+ import { z } from "zod";
9
9
  import type { ExtractionSpanConfig } from "./extraction-span-config.js";
10
10
  import { parseSpanOffsets, type ParsedSpan } from "./extraction-span.js";
11
11
  import { ExtractionProviderOutputSchema } from "./schemas.js";
@@ -19,13 +19,41 @@ export function applySpanMode(opts: {
19
19
  return parseSpanOffsets(opts.text, { start: opts.start, end: opts.end }, opts.enabled);
20
20
  }
21
21
 
22
- const JSON_SCHEMA_OPTS = { $refStrategy: "none" as const, effectStrategy: "input" as const };
22
+ /** The parts of a JSON Schema node the extraction schema uses. */
23
+ type SchemaNode = {
24
+ type?: string;
25
+ properties?: Record<string, SchemaNode>;
26
+ items?: SchemaNode;
27
+ anyOf?: SchemaNode[];
28
+ additionalProperties?: boolean | SchemaNode;
29
+ };
23
30
 
24
- const EXTRACTION_PROVIDER_JSON_SCHEMA_WITH_SPAN = zodToJsonSchema(
25
- ExtractionProviderOutputSchema,
26
- JSON_SCHEMA_OPTS,
27
- ) as Record<string, unknown>;
31
+ /**
32
+ * `io: "input"` leaves objects open (zod accepts extra input keys), but providers
33
+ * must be told to emit no extra keys: zod-to-json-schema advertised
34
+ * additionalProperties:false on every object. Close any object schema that
35
+ * does not already state its own.
36
+ */
37
+ function closeObjectSchemas(node: SchemaNode): SchemaNode {
38
+ if (node.type === "object" && node.properties && node.additionalProperties === undefined) {
39
+ node.additionalProperties = false;
40
+ }
41
+ Object.values(node.properties ?? {}).forEach(closeObjectSchemas);
42
+ if (node.items) closeObjectSchemas(node.items);
43
+ node.anyOf?.forEach(closeObjectSchemas);
44
+ return node;
45
+ }
28
46
 
47
+ // zod 4 ships toJSONSchema natively; zod-to-json-schema emits an empty
48
+ // document for v4 schemas, so the dependency is dropped here. `io: "input"`
49
+ // matches the old effectStrategy: "input" (advertise the input shape of
50
+ // transforms/preprocess to providers).
51
+ // SAFETY: z.toJSONSchema returns a JSON-Schema document — always an object at
52
+ // the root; the record shape is what omitFactSpan walks.
53
+ const EXTRACTION_PROVIDER_JSON_SCHEMA_WITH_SPAN: SchemaNode = closeObjectSchemas(
54
+ // SAFETY: z.toJSONSchema returns a JSON Schema document; SchemaNode names the keys this file reads.
55
+ z.toJSONSchema(ExtractionProviderOutputSchema, { target: "draft-7", io: "input" }) as SchemaNode,
56
+ );
29
57
  type FactItems = {
30
58
  properties?: Record<string, unknown>;
31
59
  required?: string[];
package/src/index.ts CHANGED
@@ -657,7 +657,7 @@ export {
657
657
  type ParsedBriefingWindow,
658
658
  type BriefingFormatValue,
659
659
  } from "./briefing.js";
660
-
660
+ export { withBriefingDeadline, BriefingTimeoutError, BRIEFING_DEADLINE_MS } from "./briefing-deadline.js";
661
661
  // ---------------------------------------------------------------------------
662
662
  // Binary lifecycle management (#367)
663
663
  // ---------------------------------------------------------------------------
@@ -21,7 +21,7 @@
21
21
 
22
22
  import type { StorageManager } from "../index.js";
23
23
  import { log } from "../logger.js";
24
- import type { MemoryFile } from "../types.js";
24
+ import type { BriefingResult, MemoryFile } from "../types.js";
25
25
  import type { WearableConversation } from "../wearables/types.js";
26
26
  import { localDayKey, placeDurations } from "./intervals.js";
27
27
  import {
@@ -294,23 +294,25 @@ export async function briefingLocationSection(
294
294
  );
295
295
  }
296
296
 
297
- export async function applyBriefingLocationContext<
298
- T extends { markdown: string; json: Record<string, unknown> },
299
- >(
300
- result: T,
297
+ /**
298
+ * Append the opt-in location section to a briefing. The section is read BEFORE
299
+ * `build` runs: buildBriefing drains correction notifications at most once, so
300
+ * a deadline during this read must not lose them.
301
+ */
302
+ export async function buildWithBriefingLocation<T extends Pick<BriefingResult, "markdown" | "json">>(
301
303
  includeLocation: boolean | undefined,
302
304
  memoryDir: string,
303
305
  windowStartUtc: string,
304
306
  location: LocationConfig,
307
+ build: () => Promise<T>,
305
308
  ): Promise<T> {
306
- if (includeLocation !== true || !locationTaggingEnabled(location)) return result;
307
- const section = await briefingLocationSection(memoryDir, windowStartUtc, location);
309
+ const section =
310
+ includeLocation === true && locationTaggingEnabled(location)
311
+ ? await briefingLocationSection(memoryDir, windowStartUtc, location)
312
+ : null;
313
+ const result = await build();
308
314
  if (!section) return result;
309
- return {
310
- ...result,
311
- markdown: `${result.markdown}\n\n${section}\n`,
312
- json: { ...result.json, locationContext: section },
313
- };
315
+ return { ...result, markdown: `${result.markdown}\n\n${section}\n`, json: { ...result.json, locationContext: section } };
314
316
  }
315
317
 
316
318
  /**
@@ -77,9 +77,16 @@ export async function plainFileDigest(filePath: string): Promise<string> {
77
77
  return hash.digest("hex");
78
78
  }
79
79
 
80
- import { lstat, utimes } from "node:fs/promises";
80
+ import { lstat, realpath, utimes } from "node:fs/promises";
81
81
  import { isCensusMtimeMs } from "./census-validation.js";
82
- import { resolveSafeArchiveTarget, type SafeArchiveRoot } from "./transfer/fs-utils.js";
82
+ import path from "node:path";
83
+ import {
84
+ fromPosixRelPath,
85
+ isPathInsideRoot,
86
+ resolveSafeArchiveTarget,
87
+ type SafeArchiveRoot,
88
+ validateArchiveRelativePath,
89
+ } from "./transfer/fs-utils.js";
83
90
 
84
91
  /** Sync-internal directory spooling partial uploads and coordination state. */
85
92
  export const SYNC_INTERNAL_DIR = ".offline-sync";
@@ -124,3 +131,55 @@ export async function setSafeFileMtime(
124
131
  await utimes(target, mtime, mtime);
125
132
  return true;
126
133
  }
134
+
135
+ /**
136
+ * True when a regular (non-symlink) file exists at the archive-relative path.
137
+ * A symlink is not a regular local file and reads as absent, so callers treat
138
+ * it like any other missing path instead of failing the whole push.
139
+ */
140
+ export async function offlineSyncRegularFilePresent(
141
+ root: SafeArchiveRoot,
142
+ relPath: string,
143
+ ): Promise<boolean> {
144
+ const target = path.resolve(root.abs, fromPosixRelPath(validateArchiveRelativePath(relPath, root.errorPrefix)));
145
+ if (!isPathInsideRoot(root.abs, target)) {
146
+ throw new Error(`${root.errorPrefix}: record path escapes target root: ${relPath}`);
147
+ }
148
+ const st = await lstat(target).catch((error: NodeJS.ErrnoException) => {
149
+ // SAFETY: Node fs errors carry `code`; only ENOENT means absent, all other
150
+ // errno codes must propagate so census uncertainty fails closed.
151
+ if (error.code === "ENOENT") return null;
152
+ throw error;
153
+ });
154
+ if (!st || st.isSymbolicLink() || !st.isFile()) return false;
155
+ // A regular file still has to resolve inside the root (a symlinked parent directory escapes it).
156
+ await resolveSafeArchiveTarget(root, relPath);
157
+ // The census skips a symlinked directory, so a file reached only through one is absent here too.
158
+ return (await realpath(target)) === path.join(root.real, path.relative(root.abs, target));
159
+ }
160
+
161
+ /** A path the census observed vanished before its digest was read: a retryable race, never a confirmed delete. */
162
+ export class OfflineSyncCensusRaceError extends Error {
163
+ constructor(readonly relPath: string, cause?: unknown) {
164
+ super(
165
+ `offline sync census race: ${relPath} vanished during digest; retry the snapshot`,
166
+ cause === undefined ? undefined : { cause },
167
+ );
168
+ this.name = "OfflineSyncCensusRaceError";
169
+ }
170
+ }
171
+
172
+ /** The message the CLI push retry loop matches: it defers the path and rebuilds the census. */
173
+ export function changedWhileBuildingChangesetError(relPath: string, cause?: unknown): Error {
174
+ return new Error(
175
+ `offline sync file changed while building changeset: ${relPath}`,
176
+ cause === undefined ? undefined : { cause },
177
+ );
178
+ }
179
+
180
+ /** A `.catch` handler: a census race while building a changeset upsert stays retryable. */
181
+ export function censusRaceAsChangedWhileBuilding(relPath: string): (error: Error) => never {
182
+ return (error) => {
183
+ throw error instanceof OfflineSyncCensusRaceError ? changedWhileBuildingChangesetError(relPath, error) : error;
184
+ };
185
+ }
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { createHash } from "node:crypto";
3
- import { chmod, mkdir, mkdtemp, readdir, readFile, rm, stat, utimes, writeFile } from "node:fs/promises";
3
+ import { chmod, mkdir, mkdtemp, readdir, readFile, rm, stat, symlink, utimes, writeFile } from "node:fs/promises";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import test from "node:test";
@@ -3714,33 +3714,37 @@ test("applyOfflineSyncSnapshot consumes readIncomingFile for records without inl
3714
3714
  }
3715
3715
  });
3716
3716
 
3717
- test("buildOfflineSyncSnapshot skips a file that vanishes during the walk", async () => {
3717
+ test("buildOfflineSyncSnapshot fails closed when a file vanishes during digest", async () => {
3718
3718
  const root = await tempDir("remnic-offline-vanish");
3719
3719
  try {
3720
3720
  await write(root, "facts/keep.md", "keep");
3721
3721
  await write(root, "facts/gone.md", "gone");
3722
- const snapshot = await buildOfflineSyncSnapshot({
3723
- root,
3724
- sourceId: "local",
3725
- includeContent: false,
3726
- readFileDigest: async ({ path: relPath, filePath }) => {
3727
- if (relPath.endsWith("gone.md")) {
3728
- await rm(filePath);
3729
- throw Object.assign(new Error("ENOENT: vanished"), { code: "ENOENT" });
3730
- }
3731
- const content = await readFile(filePath);
3732
- return {
3733
- sha256: createHash("sha256").update(content).digest("hex"),
3734
- bytes: content.length,
3735
- };
3736
- },
3737
- });
3738
- assert.deepEqual(snapshot.files.map((file) => file.path), ["facts/keep.md"]);
3722
+ await assert.rejects(
3723
+ () => buildOfflineSyncSnapshot({
3724
+ root,
3725
+ sourceId: "local",
3726
+ includeContent: false,
3727
+ readFileDigest: async ({ path: relPath, filePath }) => {
3728
+ if (relPath.endsWith("gone.md")) {
3729
+ await rm(filePath);
3730
+ throw Object.assign(new Error("ENOENT: vanished"), { code: "ENOENT" });
3731
+ }
3732
+ const content = await readFile(filePath);
3733
+ return {
3734
+ sha256: createHash("sha256").update(content).digest("hex"),
3735
+ bytes: content.length,
3736
+ };
3737
+ },
3738
+ }),
3739
+ /census race|vanished during digest|retry the snapshot/i,
3740
+ );
3741
+ assert.equal(await readUtf8(root, "facts/keep.md"), "keep");
3739
3742
  } finally {
3740
3743
  await rm(root, { recursive: true, force: true });
3741
3744
  }
3742
3745
  });
3743
3746
 
3747
+
3744
3748
  test("base snapshot fails when a base-known file vanishes before the fast-base stat", async () => {
3745
3749
  const root = await tempDir("remnic-offline-base-vanish");
3746
3750
  try {
@@ -3770,7 +3774,7 @@ test("base snapshot fails when a base-known file vanishes before the fast-base s
3770
3774
  }
3771
3775
  });
3772
3776
 
3773
- test("base snapshot omits a brand-new file that vanishes during the walk", async () => {
3777
+ test("base snapshot omits a brand-new file that vanishes before the fast-base stat", async () => {
3774
3778
  const root = await tempDir("remnic-offline-new-vanish");
3775
3779
  try {
3776
3780
  await write(root, "facts/keep.md", "keep");
@@ -3798,6 +3802,7 @@ test("base snapshot omits a brand-new file that vanishes during the walk", async
3798
3802
  }
3799
3803
  });
3800
3804
 
3805
+
3801
3806
  test("snapshot fails when an embedding generation member vanishes during the walk", async () => {
3802
3807
  const root = await tempDir("remnic-offline-shard-vanish");
3803
3808
  try {
@@ -3822,3 +3827,291 @@ test("snapshot fails when an embedding generation member vanishes during the wal
3822
3827
  await rm(root, { recursive: true, force: true });
3823
3828
  }
3824
3829
  });
3830
+
3831
+ test("a transient snapshot omission must not emit a delete for a shared-base file", async () => {
3832
+ const root = await tempDir("remnic-offline-uncertain-delete");
3833
+ try {
3834
+ await write(root, "facts/keep.md", "keep");
3835
+ await write(root, "facts/race.md", "race-v1");
3836
+ const base = await buildOfflineSyncSnapshot({
3837
+ root,
3838
+ sourceId: "local",
3839
+ includeContent: false,
3840
+ });
3841
+ // Live census fail-closes on digest ENOENT. A stale/incomplete currentFiles
3842
+ // list is still possible from an older client cache — that path must not
3843
+ // emit a confirmed delete when the file is back on disk.
3844
+ const incompleteFiles = base.files.filter((file) => file.path !== "facts/race.md");
3845
+ assert.deepEqual(
3846
+ incompleteFiles.map((file) => file.path),
3847
+ ["facts/keep.md"],
3848
+ "fixture: the race path is absent from the incomplete currentFiles view",
3849
+ );
3850
+ await write(root, "facts/race.md", "race-v1");
3851
+ const changeset = await buildOfflineSyncChangesetFromSnapshot({
3852
+ root,
3853
+ sourceId: "local",
3854
+ currentFiles: incompleteFiles,
3855
+ baseFiles: base.files,
3856
+ });
3857
+ assert.equal(
3858
+ changeset.changes.filter((change) => change.path === "facts/race.md" && change.type === "delete").length,
3859
+ 0,
3860
+ "an unreadable/recreated path must not become a confirmed delete against the shared base",
3861
+ );
3862
+ } finally {
3863
+ await rm(root, { recursive: true, force: true });
3864
+ }
3865
+ });
3866
+
3867
+ test("apply must not overwrite a recreated local file omitted from the local census", async () => {
3868
+ const root = await tempDir("remnic-offline-uncertain-overwrite");
3869
+ try {
3870
+ const recreated = "recreated local bytes";
3871
+ const incoming = "incoming remote bytes";
3872
+ await write(root, "facts/race.md", recreated);
3873
+ const incomingSha = createHash("sha256").update(incoming).digest("hex");
3874
+ // Local census omitted the recreated path (stale/racy currentFiles).
3875
+ // Incoming is base-less, so the apply path would treat it as a pure create.
3876
+ const result = await applyOfflineSyncSnapshot({
3877
+ root,
3878
+ currentFiles: [],
3879
+ baseFiles: [],
3880
+ snapshot: {
3881
+ format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
3882
+ schemaVersion: 1,
3883
+ createdAt: new Date().toISOString(),
3884
+ sourceId: "remote",
3885
+ includeTranscripts: false,
3886
+ files: [{
3887
+ path: "facts/race.md",
3888
+ sha256: incomingSha,
3889
+ bytes: Buffer.byteLength(incoming),
3890
+ mtimeMs: 2,
3891
+ contentBase64: Buffer.from(incoming).toString("base64"),
3892
+ }],
3893
+ },
3894
+ });
3895
+ assert.equal(
3896
+ await readUtf8(root, "facts/race.md"),
3897
+ recreated,
3898
+ "recreated local content must survive when the local census omitted it",
3899
+ );
3900
+ assert.equal(result.upserted, 0, "must not publish over an uncertain local path");
3901
+ } finally {
3902
+ await rm(root, { recursive: true, force: true });
3903
+ }
3904
+ });
3905
+
3906
+ test("changeset apply must not overwrite a recreated local file omitted by controlled digest census", async () => {
3907
+ // Public applyOfflineSyncChangeset builds local currentFiles via
3908
+ // buildOfflineSyncSnapshotForPaths when currentFiles is omitted. A controlled
3909
+ // digest that recreates local bytes then throws ENOENT must fail closed at
3910
+ // the shared census boundary — never export confirmed absence and overwrite.
3911
+ const root = await tempDir("remnic-offline-changeset-uncertain-overwrite");
3912
+ try {
3913
+ const recreated = "recreated local bytes";
3914
+ const incoming = "incoming remote bytes";
3915
+ const incomingSha = createHash("sha256").update(incoming).digest("hex");
3916
+ await write(root, "facts/race.md", recreated);
3917
+
3918
+ await assert.rejects(
3919
+ () => applyOfflineSyncChangeset({
3920
+ root,
3921
+ // No currentFiles: exercise the public snapshotForPaths census path.
3922
+ returnCurrentFiles: false,
3923
+ writeConflictCopies: false,
3924
+ readFileDigest: async ({ path: relPath, filePath }) => {
3925
+ if (relPath === "facts/race.md") {
3926
+ await writeFile(filePath, recreated);
3927
+ throw Object.assign(new Error("ENOENT: vanished during census"), { code: "ENOENT" });
3928
+ }
3929
+ const content = await readFile(filePath);
3930
+ return {
3931
+ sha256: createHash("sha256").update(content).digest("hex"),
3932
+ bytes: content.length,
3933
+ };
3934
+ },
3935
+ changeset: {
3936
+ format: "remnic.offline-sync.changeset.v1",
3937
+ schemaVersion: 1,
3938
+ createdAt: new Date().toISOString(),
3939
+ sourceId: "remote",
3940
+ includeTranscripts: false,
3941
+ changes: [{
3942
+ type: "upsert",
3943
+ path: "facts/race.md",
3944
+ file: {
3945
+ path: "facts/race.md",
3946
+ sha256: incomingSha,
3947
+ bytes: Buffer.byteLength(incoming),
3948
+ mtimeMs: 2,
3949
+ contentBase64: Buffer.from(incoming).toString("base64"),
3950
+ },
3951
+ }],
3952
+ },
3953
+ }),
3954
+ /census race|vanished during digest|retry the snapshot/i,
3955
+ );
3956
+
3957
+ assert.equal(
3958
+ await readUtf8(root, "facts/race.md"),
3959
+ recreated,
3960
+ "recreated local content must survive a fail-closed public changeset census race",
3961
+ );
3962
+ } finally {
3963
+ await rm(root, { recursive: true, force: true });
3964
+ }
3965
+ });
3966
+
3967
+ test("sender snapshot omission from digest race must not delete receiver shared-base file", async () => {
3968
+ // Root premise: sender digest race must not export an apparently-complete
3969
+ // snapshot that omits the path. Fail closed at the shared census boundary so
3970
+ // no consumer can assign confirmed absence. Sender and receiver bytes stay.
3971
+ const senderRoot = await tempDir("remnic-offline-sender-uncertain-omit");
3972
+ const receiverRoot = await tempDir("remnic-offline-receiver-shared-base");
3973
+ try {
3974
+ const good = "unchanged good fact bytes";
3975
+ const keep = "keep peer";
3976
+ await write(senderRoot, "facts/race.md", good);
3977
+ await write(senderRoot, "facts/keep.md", keep);
3978
+ await write(receiverRoot, "facts/race.md", good);
3979
+ await write(receiverRoot, "facts/keep.md", keep);
3980
+
3981
+ const sharedBase = await buildOfflineSyncSnapshot({
3982
+ root: receiverRoot,
3983
+ sourceId: "shared",
3984
+ includeContent: false,
3985
+ });
3986
+ assert.ok(
3987
+ sharedBase.files.some((file) => file.path === "facts/race.md"),
3988
+ "fixture: shared base includes the good race path",
3989
+ );
3990
+
3991
+ await assert.rejects(
3992
+ () => buildOfflineSyncSnapshot({
3993
+ root: senderRoot,
3994
+ sourceId: "sender",
3995
+ includeContent: false,
3996
+ readFileDigest: async ({ path: relPath, filePath }) => {
3997
+ if (relPath === "facts/race.md") {
3998
+ await writeFile(filePath, good);
3999
+ throw Object.assign(new Error("ENOENT: vanished during sender census"), { code: "ENOENT" });
4000
+ }
4001
+ const content = await readFile(filePath);
4002
+ return {
4003
+ sha256: createHash("sha256").update(content).digest("hex"),
4004
+ bytes: content.length,
4005
+ };
4006
+ },
4007
+ }),
4008
+ /census race|vanished during digest|retry the snapshot/i,
4009
+ );
4010
+
4011
+ assert.equal(await readUtf8(senderRoot, "facts/race.md"), good, "sender bytes preserved");
4012
+ assert.equal(await readUtf8(receiverRoot, "facts/race.md"), good, "receiver bytes preserved without a false snapshot");
4013
+ assert.equal(await readUtf8(receiverRoot, "facts/keep.md"), keep);
4014
+ } finally {
4015
+ await rm(senderRoot, { recursive: true, force: true });
4016
+ await rm(receiverRoot, { recursive: true, force: true });
4017
+ }
4018
+ });
4019
+
4020
+
4021
+ test("a file vanishing during the changeset upsert read keeps the retryable changed-while-building message", async () => {
4022
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-offline-changeset-vanish-"));
4023
+ try {
4024
+ const rel = "facts/volatile.md";
4025
+ const bytes = Buffer.from("volatile bytes\n");
4026
+ await mkdir(path.join(root, "facts"), { recursive: true });
4027
+ await writeFile(path.join(root, rel), bytes);
4028
+ const currentFile = {
4029
+ path: rel,
4030
+ sha256: createHash("sha256").update(bytes).digest("hex"),
4031
+ bytes: bytes.byteLength,
4032
+ mtimeMs: 1,
4033
+ };
4034
+ await assert.rejects(
4035
+ buildOfflineSyncChangesetFromSnapshot({
4036
+ root,
4037
+ sourceId: "laptop",
4038
+ baseFiles: [],
4039
+ currentFiles: [currentFile],
4040
+ readFile: async () => {
4041
+ await rm(path.join(root, rel), { force: true });
4042
+ throw Object.assign(new Error("ENOENT: file vanished"), { code: "ENOENT" });
4043
+ },
4044
+ }),
4045
+ (error: Error) => {
4046
+ // The CLI retry loop matches this exact prefix to defer the path and rebuild the census.
4047
+ assert.equal(error.message, `offline sync file changed while building changeset: ${rel}`);
4048
+ return true;
4049
+ },
4050
+ );
4051
+ } finally {
4052
+ await rm(root, { recursive: true, force: true });
4053
+ }
4054
+ });
4055
+
4056
+ test("a tracked file replaced by a symlink is a local delete, not a push-blocking error", async () => {
4057
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-offline-symlink-replaced-"));
4058
+ const outside = await mkdtemp(path.join(os.tmpdir(), "remnic-offline-symlink-target-"));
4059
+ try {
4060
+ const rel = "facts/tracked.md";
4061
+ const bytes = Buffer.from("tracked bytes\n");
4062
+ await mkdir(path.join(root, "facts"), { recursive: true });
4063
+ await writeFile(path.join(outside, "target.md"), "outside\n");
4064
+ await symlink(path.join(outside, "target.md"), path.join(root, rel));
4065
+ const baseFile = {
4066
+ path: rel,
4067
+ sha256: createHash("sha256").update(bytes).digest("hex"),
4068
+ bytes: bytes.byteLength,
4069
+ mtimeMs: 1,
4070
+ };
4071
+ const changeset = await buildOfflineSyncChangesetFromSnapshot({
4072
+ root,
4073
+ sourceId: "laptop",
4074
+ baseFiles: [baseFile],
4075
+ currentFiles: [],
4076
+ });
4077
+ assert.deepEqual(
4078
+ changeset.changes.map((change) => [change.type, change.path]),
4079
+ [["delete", rel]],
4080
+ "a symlink is not a regular local file: it reads as a delete, as before the census change",
4081
+ );
4082
+ } finally {
4083
+ await rm(root, { recursive: true, force: true });
4084
+ await rm(outside, { recursive: true, force: true });
4085
+ }
4086
+ });
4087
+
4088
+ test("a tracked file under a directory replaced by an in-root symlink is a local delete", async () => {
4089
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-offline-symlink-parent-"));
4090
+ try {
4091
+ const rel = "facts/tracked.md";
4092
+ const bytes = Buffer.from("tracked bytes\n");
4093
+ await mkdir(path.join(root, "elsewhere"), { recursive: true });
4094
+ await writeFile(path.join(root, "elsewhere", "tracked.md"), bytes);
4095
+ // The census skips a symlinked directory, so facts/tracked.md is not in it.
4096
+ await symlink(path.join(root, "elsewhere"), path.join(root, "facts"));
4097
+ const baseFile = {
4098
+ path: rel,
4099
+ sha256: createHash("sha256").update(bytes).digest("hex"),
4100
+ bytes: bytes.byteLength,
4101
+ mtimeMs: 1,
4102
+ };
4103
+ const changeset = await buildOfflineSyncChangesetFromSnapshot({
4104
+ root,
4105
+ sourceId: "laptop",
4106
+ baseFiles: [baseFile],
4107
+ currentFiles: [],
4108
+ });
4109
+ assert.deepEqual(
4110
+ changeset.changes.map((change) => [change.type, change.path]),
4111
+ [["delete", rel]],
4112
+ "a path reached only through a symlinked parent is absent, as the census sees it",
4113
+ );
4114
+ } finally {
4115
+ await rm(root, { recursive: true, force: true });
4116
+ }
4117
+ });