@remnic/core 9.63.6 → 9.63.7

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 (262) hide show
  1. package/dist/access-admin-ops-surface.d.ts +1 -1
  2. package/dist/access-admin-ops-surface.js +27 -25
  3. package/dist/access-authorization-probe.d.ts +1 -1
  4. package/dist/access-authorization-probe.js +28 -26
  5. package/dist/access-boundary.d.ts +1 -1
  6. package/dist/access-boundary.js +27 -25
  7. package/dist/access-cli.js +83 -81
  8. package/dist/access-cli.js.map +1 -1
  9. package/dist/access-extraction-force-flush.d.ts +1 -1
  10. package/dist/access-extraction-force-flush.js +27 -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 +29 -27
  15. package/dist/access-http.d.ts +2 -2
  16. package/dist/access-http.js +43 -41
  17. package/dist/access-identity-continuity-surface.js +27 -25
  18. package/dist/access-lcm-surface.d.ts +1 -1
  19. package/dist/access-lcm-surface.js +27 -25
  20. package/dist/access-mcp.d.ts +1 -1
  21. package/dist/access-mcp.js +34 -32
  22. package/dist/access-namespace-preflight.js +27 -25
  23. package/dist/access-observe-write-surface.d.ts +1 -1
  24. package/dist/access-observe-write-surface.js +27 -25
  25. package/dist/access-offline-manifest.js +6 -5
  26. package/dist/access-operations-batch.js +29 -27
  27. package/dist/access-operations.d.ts +4 -4
  28. package/dist/access-operations.js +33 -31
  29. package/dist/access-recall-concurrency.d.ts +1 -1
  30. package/dist/access-recall-concurrency.js +27 -25
  31. package/dist/access-recall-response.d.ts +1 -1
  32. package/dist/access-recall-response.js +27 -25
  33. package/dist/access-recall-surface.d.ts +1 -1
  34. package/dist/access-recall-surface.js +27 -25
  35. package/dist/access-schema.d.ts +88 -88
  36. package/dist/access-schema.js +6 -5
  37. package/dist/access-service-helpers.d.ts +1 -1
  38. package/dist/access-service.d.ts +1 -1
  39. package/dist/access-service.js +27 -25
  40. package/dist/access-surface-catalog.d.ts +1 -1
  41. package/dist/access-surface-catalog.js +29 -27
  42. package/dist/access-surface-catalog.js.map +1 -1
  43. package/dist/active-recall.js +5 -3
  44. package/dist/active-recall.js.map +1 -1
  45. package/dist/adapters/index.js +4 -4
  46. package/dist/adapters/registry.js +2 -2
  47. package/dist/{auto-sync-BKRJDMKX.js → auto-sync-3SHMZVQS.js} +5 -3
  48. package/dist/{auto-sync-BKRJDMKX.js.map → auto-sync-3SHMZVQS.js.map} +1 -1
  49. package/dist/briefing.js +10 -10
  50. package/dist/calibration.d.ts +2 -1
  51. package/dist/calibration.js +7 -0
  52. package/dist/calibration.js.map +1 -1
  53. package/dist/causal-consolidation.js +11 -11
  54. package/dist/census-validation.d.ts +19 -0
  55. package/dist/census-validation.js +14 -0
  56. package/dist/census-validation.js.map +1 -0
  57. package/dist/{chunk-LEPOP46W.js → chunk-227UFSEQ.js} +4 -4
  58. package/dist/{chunk-Z4N7SVVF.js → chunk-2L7DKNVE.js} +12 -7
  59. package/dist/chunk-2L7DKNVE.js.map +1 -0
  60. package/dist/{chunk-3Z5TGRFA.js → chunk-3NZAVWU5.js} +9 -9
  61. package/dist/{chunk-I5FLIB2J.js → chunk-3S3PNR5H.js} +2 -2
  62. package/dist/{chunk-R6UMNP5C.js → chunk-3T6HIIKA.js} +2 -2
  63. package/dist/{chunk-77QZW2D6.js → chunk-3TDAA7VA.js} +2 -2
  64. package/dist/{chunk-TWTUGCTY.js → chunk-3YTTAGPB.js} +6 -1
  65. package/dist/chunk-3YTTAGPB.js.map +1 -0
  66. package/dist/{chunk-DXDAQZ2K.js → chunk-42TIIO2M.js} +2 -2
  67. package/dist/{chunk-Y7COCXY2.js → chunk-4F67JJPG.js} +2 -2
  68. package/dist/{chunk-PB4WVYGC.js → chunk-5S5DUJFJ.js} +2 -2
  69. package/dist/{chunk-KYTRF64R.js → chunk-5SCFDQEJ.js} +2 -2
  70. package/dist/{chunk-GG4DN63W.js → chunk-62Y5AGCM.js} +2 -2
  71. package/dist/{chunk-KHCUVYCY.js → chunk-6UKSOXAO.js} +3 -3
  72. package/dist/{chunk-F6JYBVL6.js → chunk-7ZAC2C2U.js} +24 -24
  73. package/dist/{chunk-GF7RQWXC.js → chunk-AJRJDXYR.js} +2 -2
  74. package/dist/{chunk-YEQCMA5L.js → chunk-BOSIHHOH.js} +2 -2
  75. package/dist/{chunk-7LF2OHIJ.js → chunk-BRHKW7GY.js} +2 -2
  76. package/dist/{chunk-LX4TNSAE.js → chunk-CBY54QYW.js} +4 -4
  77. package/dist/chunk-FEHIOYML.js +168 -0
  78. package/dist/chunk-FEHIOYML.js.map +1 -0
  79. package/dist/{chunk-6YMEU6OQ.js → chunk-G72IPMR7.js} +2 -2
  80. package/dist/{chunk-7AF243WZ.js → chunk-GBVRVYF4.js} +1 -1
  81. package/dist/{chunk-GTD4Q62X.js → chunk-H4GQ2Y24.js} +3 -3
  82. package/dist/{chunk-QI5RDDEL.js → chunk-HXZNTZ3Y.js} +6 -6
  83. package/dist/{chunk-CIAI24TQ.js → chunk-IBZQQOM5.js} +3 -3
  84. package/dist/{chunk-RYST72N5.js → chunk-IINVXO3U.js} +4 -154
  85. package/dist/chunk-IINVXO3U.js.map +1 -0
  86. package/dist/{chunk-R7TZ2HCS.js → chunk-JG22O53B.js} +58 -3
  87. package/dist/chunk-JG22O53B.js.map +1 -0
  88. package/dist/{chunk-VPHSMJ2E.js → chunk-JPWSYHLB.js} +2 -2
  89. package/dist/{chunk-X7LEDA74.js → chunk-KFB4YNXM.js} +2 -2
  90. package/dist/{chunk-IPLYGWQF.js → chunk-KQAFEZQX.js} +5 -5
  91. package/dist/chunk-LQWOFU6L.js +17 -0
  92. package/dist/chunk-LQWOFU6L.js.map +1 -0
  93. package/dist/{chunk-ZUPKNWRZ.js → chunk-MIUL6AUZ.js} +3 -3
  94. package/dist/{chunk-EJY3HJMT.js → chunk-MYI3RKI6.js} +2 -2
  95. package/dist/{chunk-NA56NSKL.js → chunk-OOJCXJBN.js} +2 -2
  96. package/dist/{chunk-7KQB4EBS.js → chunk-OPPBTPUU.js} +4 -4
  97. package/dist/{chunk-ERPBRXVZ.js → chunk-PZKWPXRF.js} +2 -2
  98. package/dist/{chunk-FHKXVTM5.js → chunk-Q5XKKDFQ.js} +16 -16
  99. package/dist/{chunk-QRWMS6SI.js → chunk-Q6OL23WM.js} +2 -2
  100. package/dist/{chunk-OW7NVMGQ.js → chunk-QIZOU3FN.js} +2 -2
  101. package/dist/{chunk-V3KIRICY.js → chunk-QQYHYW2N.js} +3 -3
  102. package/dist/{chunk-YFCEBB5P.js → chunk-QTXNS3HB.js} +2 -2
  103. package/dist/{chunk-2QSZNTDO.js → chunk-RKNJBZ55.js} +4 -4
  104. package/dist/{chunk-E4KMY4KR.js → chunk-SNEUAZ6B.js} +2 -2
  105. package/dist/{chunk-TSTI6A3A.js → chunk-SYJW273H.js} +11 -11
  106. package/dist/{chunk-EFKZT6AY.js → chunk-T42L3S6A.js} +6 -11
  107. package/dist/chunk-T42L3S6A.js.map +1 -0
  108. package/dist/{chunk-YXMHUBN2.js → chunk-TOP3FSDW.js} +25 -102
  109. package/dist/chunk-TOP3FSDW.js.map +1 -0
  110. package/dist/{chunk-PBRCYYO7.js → chunk-ULZFZL7K.js} +2 -2
  111. package/dist/{chunk-UGHCZS6K.js → chunk-VFZQXYN5.js} +2 -2
  112. package/dist/{chunk-IVXVLDNT.js → chunk-WKJTMXME.js} +37 -54
  113. package/dist/chunk-WKJTMXME.js.map +1 -0
  114. package/dist/{chunk-NKB43PGI.js → chunk-WZDTSBGQ.js} +2 -2
  115. package/dist/{chunk-2UM6WHME.js → chunk-XE2QCCT2.js} +2 -2
  116. package/dist/{chunk-FPEZ74F7.js → chunk-XNJQZLAP.js} +3 -3
  117. package/dist/{chunk-SYVGVK4S.js → chunk-XTMYB2HY.js} +2 -2
  118. package/dist/{chunk-OETHJBAL.js → chunk-XV6PUXJ5.js} +71 -71
  119. package/dist/{chunk-UCLMKNHA.js → chunk-YIMV2KYM.js} +2 -2
  120. package/dist/{chunk-DYZR3Z6D.js → chunk-YMQKPSB2.js} +4 -4
  121. package/dist/{chunk-AYVCKGA6.js → chunk-YTXNDEUS.js} +2 -2
  122. package/dist/{chunk-L3CHRBHE.js → chunk-ZRUEP4IK.js} +17 -11
  123. package/dist/chunk-ZRUEP4IK.js.map +1 -0
  124. package/dist/{cli-BFBDYV0D.d.ts → cli-BTNj5DzK.d.ts} +1 -1
  125. package/dist/cli.d.ts +2 -2
  126. package/dist/cli.js +59 -57
  127. package/dist/compounding/engine.js +10 -10
  128. package/dist/config.js +5 -3
  129. package/dist/connectors/codex-materialize-runner.js +10 -10
  130. package/dist/connectors/index.js +10 -10
  131. package/dist/consolidation-provenance-check.js +2 -2
  132. package/dist/convergence-refresh.js +12 -12
  133. package/dist/conversation-index/backend.js +2 -2
  134. package/dist/corpus-watermark.js +12 -12
  135. package/dist/entity-retrieval.js +10 -10
  136. package/dist/external-wiki-access.d.ts +1 -1
  137. package/dist/external-wiki-access.js +28 -26
  138. package/dist/external-wiki-mcp-tools.d.ts +1 -1
  139. package/dist/extraction-judge.d.ts +2 -1
  140. package/dist/extraction-judge.js +6 -1
  141. package/dist/extraction-normalization.js +2 -2
  142. package/dist/extraction-prompt.d.ts +20 -1
  143. package/dist/extraction-prompt.js +11 -1
  144. package/dist/extraction.js +13 -13
  145. package/dist/{first-start-migration-4NETATFF.js → first-start-migration-Q2VB2E2D.js} +4 -4
  146. package/dist/harmonic-retrieval.js +1 -1
  147. package/dist/index.d.ts +680 -680
  148. package/dist/index.js +92 -90
  149. package/dist/maintenance/memory-governance.js +10 -10
  150. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
  151. package/dist/maintenance/rebuild-memory-projection.js +11 -11
  152. package/dist/mcp-memory-inspector-app.d.ts +1 -1
  153. package/dist/namespaces/migrate.js +11 -11
  154. package/dist/namespaces/search.js +4 -4
  155. package/dist/namespaces/storage.js +10 -10
  156. package/dist/offline-sync.js +3 -2
  157. package/dist/operator-doctor-corpus.js +13 -13
  158. package/dist/operator-toolkit.js +24 -22
  159. package/dist/orchestration/maintenance.js +16 -16
  160. package/dist/orchestrator.js +85 -83
  161. package/dist/{public-http-8PcrDIYi.d.ts → public-http-BmPktTe3.d.ts} +1 -1
  162. package/dist/reconcile/cursor.js +7 -3
  163. package/dist/reconcile/cursor.js.map +1 -1
  164. package/dist/reconcile/manifest.js +3 -7
  165. package/dist/reconcile/plan.d.ts +15 -1
  166. package/dist/reconcile/plan.js +6 -10
  167. package/dist/resume-bundles.js +6 -4
  168. package/dist/schemas.d.ts +108 -108
  169. package/dist/search/factory.js +3 -3
  170. package/dist/search/index.js +3 -3
  171. package/dist/semantic-consolidation.js +16 -16
  172. package/dist/semantic-rule-promotion.js +10 -10
  173. package/dist/semantic-rule-verifier.js +10 -10
  174. package/dist/shared-context/manager.d.ts +8 -8
  175. package/dist/source-agent-qualifier.js +10 -10
  176. package/dist/storage.js +9 -9
  177. package/dist/support-passport/index.d.ts +4 -4
  178. package/dist/support-passport/index.js +30 -28
  179. package/dist/transfer/autodetect.js +1 -1
  180. package/dist/transfer/backup.js +1 -1
  181. package/dist/transfer/capsule-export.js +2 -2
  182. package/dist/transfer/types.d.ts +66 -66
  183. package/dist/verified-recall.js +10 -10
  184. package/package.json +2 -2
  185. package/src/calibration.ts +3 -1
  186. package/src/census-validation.ts +25 -0
  187. package/src/coding/architecture-surfaces.test.ts +9 -10
  188. package/src/coding/architecture-surfaces.ts +2 -35
  189. package/src/coding/codegraph-runtime-loader.test.ts +228 -0
  190. package/src/coding/codegraph-runtime.ts +30 -50
  191. package/src/coding/codegraph-surfaces.ts +2 -2
  192. package/src/coding/coding-knowledge-config.ts +35 -1
  193. package/src/coding/decision-surfaces.test.ts +9 -10
  194. package/src/coding/decision-surfaces.ts +2 -40
  195. package/src/coding/session-delta-surfaces.test.ts +9 -10
  196. package/src/coding/session-delta-surfaces.ts +2 -33
  197. package/src/extraction-judge.ts +3 -1
  198. package/src/extraction-output-language.test.ts +116 -0
  199. package/src/extraction-prompt.ts +70 -1
  200. package/src/extraction.ts +23 -95
  201. package/src/offline-sync.ts +4 -4
  202. package/src/reconcile/census-parity.test.ts +152 -0
  203. package/src/reconcile/cursor.ts +5 -5
  204. package/src/reconcile/manifest.ts +4 -11
  205. package/src/reconcile/plan.ts +18 -15
  206. package/dist/chunk-EFKZT6AY.js.map +0 -1
  207. package/dist/chunk-IVXVLDNT.js.map +0 -1
  208. package/dist/chunk-L3CHRBHE.js.map +0 -1
  209. package/dist/chunk-R7TZ2HCS.js.map +0 -1
  210. package/dist/chunk-RYST72N5.js.map +0 -1
  211. package/dist/chunk-TWTUGCTY.js.map +0 -1
  212. package/dist/chunk-YXMHUBN2.js.map +0 -1
  213. package/dist/chunk-Z4N7SVVF.js.map +0 -1
  214. package/dist/{access-service-cKQrte9Z.d.ts → access-service-CocW74uA.d.ts} +64 -64
  215. /package/dist/{chunk-LEPOP46W.js.map → chunk-227UFSEQ.js.map} +0 -0
  216. /package/dist/{chunk-3Z5TGRFA.js.map → chunk-3NZAVWU5.js.map} +0 -0
  217. /package/dist/{chunk-I5FLIB2J.js.map → chunk-3S3PNR5H.js.map} +0 -0
  218. /package/dist/{chunk-R6UMNP5C.js.map → chunk-3T6HIIKA.js.map} +0 -0
  219. /package/dist/{chunk-77QZW2D6.js.map → chunk-3TDAA7VA.js.map} +0 -0
  220. /package/dist/{chunk-DXDAQZ2K.js.map → chunk-42TIIO2M.js.map} +0 -0
  221. /package/dist/{chunk-Y7COCXY2.js.map → chunk-4F67JJPG.js.map} +0 -0
  222. /package/dist/{chunk-PB4WVYGC.js.map → chunk-5S5DUJFJ.js.map} +0 -0
  223. /package/dist/{chunk-KYTRF64R.js.map → chunk-5SCFDQEJ.js.map} +0 -0
  224. /package/dist/{chunk-GG4DN63W.js.map → chunk-62Y5AGCM.js.map} +0 -0
  225. /package/dist/{chunk-KHCUVYCY.js.map → chunk-6UKSOXAO.js.map} +0 -0
  226. /package/dist/{chunk-F6JYBVL6.js.map → chunk-7ZAC2C2U.js.map} +0 -0
  227. /package/dist/{chunk-GF7RQWXC.js.map → chunk-AJRJDXYR.js.map} +0 -0
  228. /package/dist/{chunk-YEQCMA5L.js.map → chunk-BOSIHHOH.js.map} +0 -0
  229. /package/dist/{chunk-7LF2OHIJ.js.map → chunk-BRHKW7GY.js.map} +0 -0
  230. /package/dist/{chunk-LX4TNSAE.js.map → chunk-CBY54QYW.js.map} +0 -0
  231. /package/dist/{chunk-6YMEU6OQ.js.map → chunk-G72IPMR7.js.map} +0 -0
  232. /package/dist/{chunk-7AF243WZ.js.map → chunk-GBVRVYF4.js.map} +0 -0
  233. /package/dist/{chunk-GTD4Q62X.js.map → chunk-H4GQ2Y24.js.map} +0 -0
  234. /package/dist/{chunk-QI5RDDEL.js.map → chunk-HXZNTZ3Y.js.map} +0 -0
  235. /package/dist/{chunk-CIAI24TQ.js.map → chunk-IBZQQOM5.js.map} +0 -0
  236. /package/dist/{chunk-VPHSMJ2E.js.map → chunk-JPWSYHLB.js.map} +0 -0
  237. /package/dist/{chunk-X7LEDA74.js.map → chunk-KFB4YNXM.js.map} +0 -0
  238. /package/dist/{chunk-IPLYGWQF.js.map → chunk-KQAFEZQX.js.map} +0 -0
  239. /package/dist/{chunk-ZUPKNWRZ.js.map → chunk-MIUL6AUZ.js.map} +0 -0
  240. /package/dist/{chunk-EJY3HJMT.js.map → chunk-MYI3RKI6.js.map} +0 -0
  241. /package/dist/{chunk-NA56NSKL.js.map → chunk-OOJCXJBN.js.map} +0 -0
  242. /package/dist/{chunk-7KQB4EBS.js.map → chunk-OPPBTPUU.js.map} +0 -0
  243. /package/dist/{chunk-ERPBRXVZ.js.map → chunk-PZKWPXRF.js.map} +0 -0
  244. /package/dist/{chunk-FHKXVTM5.js.map → chunk-Q5XKKDFQ.js.map} +0 -0
  245. /package/dist/{chunk-QRWMS6SI.js.map → chunk-Q6OL23WM.js.map} +0 -0
  246. /package/dist/{chunk-OW7NVMGQ.js.map → chunk-QIZOU3FN.js.map} +0 -0
  247. /package/dist/{chunk-V3KIRICY.js.map → chunk-QQYHYW2N.js.map} +0 -0
  248. /package/dist/{chunk-YFCEBB5P.js.map → chunk-QTXNS3HB.js.map} +0 -0
  249. /package/dist/{chunk-2QSZNTDO.js.map → chunk-RKNJBZ55.js.map} +0 -0
  250. /package/dist/{chunk-E4KMY4KR.js.map → chunk-SNEUAZ6B.js.map} +0 -0
  251. /package/dist/{chunk-TSTI6A3A.js.map → chunk-SYJW273H.js.map} +0 -0
  252. /package/dist/{chunk-PBRCYYO7.js.map → chunk-ULZFZL7K.js.map} +0 -0
  253. /package/dist/{chunk-UGHCZS6K.js.map → chunk-VFZQXYN5.js.map} +0 -0
  254. /package/dist/{chunk-NKB43PGI.js.map → chunk-WZDTSBGQ.js.map} +0 -0
  255. /package/dist/{chunk-2UM6WHME.js.map → chunk-XE2QCCT2.js.map} +0 -0
  256. /package/dist/{chunk-FPEZ74F7.js.map → chunk-XNJQZLAP.js.map} +0 -0
  257. /package/dist/{chunk-SYVGVK4S.js.map → chunk-XTMYB2HY.js.map} +0 -0
  258. /package/dist/{chunk-OETHJBAL.js.map → chunk-XV6PUXJ5.js.map} +0 -0
  259. /package/dist/{chunk-UCLMKNHA.js.map → chunk-YIMV2KYM.js.map} +0 -0
  260. /package/dist/{chunk-DYZR3Z6D.js.map → chunk-YMQKPSB2.js.map} +0 -0
  261. /package/dist/{chunk-AYVCKGA6.js.map → chunk-YTXNDEUS.js.map} +0 -0
  262. /package/dist/{first-start-migration-4NETATFF.js.map → first-start-migration-Q2VB2E2D.js.map} +0 -0
@@ -0,0 +1,152 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import {
5
+ OFFLINE_SYNC_MAX_MTIME_MS,
6
+ OFFLINE_SYNC_SNAPSHOT_FORMAT,
7
+ normalizeOfflineSyncSnapshot,
8
+ } from "../offline-sync.js";
9
+ import {
10
+ type ReconcilePlanEntry,
11
+ ReconcilePlanInputError,
12
+ compareReconcilePlanEntries,
13
+ planNamespaceReconciliation,
14
+ planReconciliation,
15
+ semanticAgreementKey,
16
+ } from "./plan.js";
17
+
18
+ /**
19
+ * Issue #2477: offline-sync and the reconcile planner validate the same peer
20
+ * census fields, and cursor/manifest order and key plan entries the same way.
21
+ * These tests fail the moment one surface drifts from the other, which was
22
+ * previously a silent convergence bug.
23
+ */
24
+
25
+ const PATH = "facts/2026-03-01/a.md";
26
+ const GOOD_SHA = "a".repeat(64);
27
+
28
+ type CensusOutcome = { accepted: boolean; digest?: string };
29
+
30
+ /** Validate one census file record through the offline-sync snapshot surface. */
31
+ function offlineSyncOutcome(overrides: Record<string, unknown>): CensusOutcome {
32
+ try {
33
+ const snapshot = normalizeOfflineSyncSnapshot({
34
+ format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
35
+ schemaVersion: 1,
36
+ createdAt: "2026-08-17T00:00:00.000Z",
37
+ sourceId: "peer",
38
+ includeTranscripts: false,
39
+ files: [{ path: PATH, bytes: 1, mtimeMs: 0, sha256: GOOD_SHA, ...overrides }],
40
+ });
41
+ return { accepted: true, digest: snapshot.files[0]?.sha256 };
42
+ } catch {
43
+ return { accepted: false };
44
+ }
45
+ }
46
+
47
+ /** Validate the same record through the reconcile planner's census surface. */
48
+ function reconcileOutcome(overrides: Record<string, unknown>): CensusOutcome {
49
+ try {
50
+ const entries = planNamespaceReconciliation({
51
+ namespace: "default",
52
+ local: [{ path: PATH, sha256: GOOD_SHA, mtimeMs: 0, ...overrides }],
53
+ peer: [],
54
+ });
55
+ return { accepted: true, digest: entries.find((e) => e.path === PATH)?.localSha256 };
56
+ } catch (err) {
57
+ assert.ok(err instanceof ReconcilePlanInputError, "plan census rejects must stay ReconcilePlanInputError");
58
+ return { accepted: false };
59
+ }
60
+ }
61
+
62
+ const SHA256_FIXTURES: unknown[] = [
63
+ "b".repeat(64), // accepted, already canonical
64
+ "B".repeat(64), // accepted, canonicalized to lowercase
65
+ "b".repeat(63), // too short
66
+ "b".repeat(65), // too long
67
+ "z".repeat(64), // not hex
68
+ "",
69
+ 42,
70
+ null,
71
+ { length: 64 },
72
+ ];
73
+
74
+ const MTIME_FIXTURES: unknown[] = [
75
+ 0,
76
+ 1.25, // fractional mtimes are valid
77
+ OFFLINE_SYNC_MAX_MTIME_MS,
78
+ OFFLINE_SYNC_MAX_MTIME_MS + 1,
79
+ -1,
80
+ Number.NaN,
81
+ Number.POSITIVE_INFINITY,
82
+ "100",
83
+ null,
84
+ ];
85
+
86
+ test("sha256 census fixtures accept and canonicalize identically on both surfaces", () => {
87
+ const offline = SHA256_FIXTURES.map((sha256) => offlineSyncOutcome({ sha256 }));
88
+ const reconcile = SHA256_FIXTURES.map((sha256) => reconcileOutcome({ sha256 }));
89
+ assert.deepEqual(reconcile, offline);
90
+ assert.deepEqual(
91
+ offline.map((o) => o.accepted),
92
+ [true, true, false, false, false, false, false, false, false]
93
+ );
94
+ assert.equal(offline[1].digest, "b".repeat(64));
95
+ });
96
+
97
+ test("mtimeMs census fixtures accept and reject identically on both surfaces", () => {
98
+ const offline = MTIME_FIXTURES.map((mtimeMs) => offlineSyncOutcome({ mtimeMs }));
99
+ const reconcile = MTIME_FIXTURES.map((mtimeMs) => reconcileOutcome({ mtimeMs }));
100
+ assert.deepEqual(reconcile, offline);
101
+ assert.deepEqual(
102
+ offline.map((o) => o.accepted),
103
+ [true, true, true, false, false, false, false, false, false]
104
+ );
105
+ });
106
+
107
+ test("compareReconcilePlanEntries is the total order plan output itself uses", () => {
108
+ const entry = (namespace: string, path: string): ReconcilePlanEntry => ({
109
+ path,
110
+ namespace,
111
+ action: "identical",
112
+ reason: "same_content",
113
+ });
114
+ const a = entry("alpha", "facts/a.md");
115
+ const b = entry("beta", "facts/a.md");
116
+ const c = entry("beta", "facts/b.md");
117
+ const sorted = [c, { ...a }, b, a].sort(compareReconcilePlanEntries);
118
+ assert.deepEqual(
119
+ sorted.map((e) => [e.namespace, e.path]),
120
+ [
121
+ ["alpha", "facts/a.md"],
122
+ ["alpha", "facts/a.md"],
123
+ ["beta", "facts/a.md"],
124
+ ["beta", "facts/b.md"],
125
+ ]
126
+ );
127
+ assert.equal(compareReconcilePlanEntries(a, { ...a }), 0);
128
+
129
+ const plan = planReconciliation([
130
+ { namespace: "beta", local: [], peer: [] },
131
+ { namespace: "alpha", local: [{ path: "facts/z.md", sha256: GOOD_SHA }], peer: [] },
132
+ ]);
133
+ assert.ok(plan.entries.length > 0);
134
+ for (let i = 1; i < plan.entries.length; i += 1) {
135
+ assert.ok(compareReconcilePlanEntries(plan.entries[i - 1], plan.entries[i]) <= 0);
136
+ }
137
+ });
138
+
139
+ test("semanticAgreementKey pins the ordered path-pair identity shared by cursor and manifest", () => {
140
+ const localFirst = semanticAgreementKey({
141
+ local: { path: "facts/local.md", sha256: GOOD_SHA },
142
+ peer: { path: "facts/peer.md", sha256: GOOD_SHA },
143
+ });
144
+ assert.equal(localFirst, "facts/local.md\0facts/peer.md");
145
+ assert.notEqual(
146
+ localFirst,
147
+ semanticAgreementKey({
148
+ local: { path: "facts/peer.md", sha256: GOOD_SHA },
149
+ peer: { path: "facts/local.md", sha256: GOOD_SHA },
150
+ })
151
+ );
152
+ });
@@ -1,7 +1,11 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
- import type { ReconcilePlanEntry, ReconcileSemanticAgreement } from "./plan.js";
4
+ import {
5
+ type ReconcilePlanEntry,
6
+ type ReconcileSemanticAgreement,
7
+ semanticAgreementKey,
8
+ } from "./plan.js";
5
9
 
6
10
  export type ConvergeRefreshTarget = "local" | "receiver";
7
11
 
@@ -76,10 +80,6 @@ function digestAfterReconcile(entry: ReconcilePlanEntry): string | undefined {
76
80
  return entry.localSha256 ?? entry.peerSha256;
77
81
  }
78
82
 
79
- function semanticAgreementKey(agreement: ReconcileSemanticAgreement): string {
80
- return `${agreement.local.path}\0${agreement.peer.path}`;
81
- }
82
-
83
83
  export function deriveConvergeCursorBase(
84
84
  entries: readonly ReconcilePlanEntry[],
85
85
  namespace: string,
@@ -12,6 +12,8 @@ import {
12
12
  type ReconcilePlanEntry,
13
13
  type ReconcileSemanticAgreement,
14
14
  type ReconcileSemanticChange,
15
+ compareReconcilePlanEntries,
16
+ semanticAgreementKey,
15
17
  summarizeReconcilePlan,
16
18
  } from "./plan.js";
17
19
 
@@ -218,15 +220,6 @@ function contentHashRows(
218
220
  return rows;
219
221
  }
220
222
 
221
- function comparePlanEntries(left: ReconcilePlanEntry, right: ReconcilePlanEntry): number {
222
- if (left.namespace !== right.namespace) return left.namespace < right.namespace ? -1 : 1;
223
- return left.path < right.path ? -1 : left.path > right.path ? 1 : 0;
224
- }
225
-
226
- function semanticAgreementKey(agreement: ReconcileSemanticAgreement): string {
227
- return `${agreement.local.path}\0${agreement.peer.path}`;
228
- }
229
-
230
223
  function classifySemanticChange(
231
224
  current: ReconcileSemanticAgreement,
232
225
  prior: ReconcileSemanticAgreement | undefined
@@ -396,13 +389,13 @@ export function collapseActiveFactDuplicates(
396
389
  if (removed.size > 0 || replacements.length > 0) {
397
390
  entriesByNamespace.set(
398
391
  namespace,
399
- [...entries.filter((entry) => !removed.has(entry)), ...replacements].sort(comparePlanEntries)
392
+ [...entries.filter((entry) => !removed.has(entry)), ...replacements].sort(compareReconcilePlanEntries)
400
393
  );
401
394
  }
402
395
  }
403
396
 
404
397
  if (!changed) return plan;
405
- const entries = [...entriesByNamespace.values()].flat().sort(comparePlanEntries);
398
+ const entries = [...entriesByNamespace.values()].flat().sort(compareReconcilePlanEntries);
406
399
  return {
407
400
  entries,
408
401
  byNamespace: summarizeReconcilePlan(entries),
@@ -1,5 +1,5 @@
1
+ import { CENSUS_MAX_MTIME_MS, isCensusMtimeMs, isSha256Hex } from "../census-validation.js";
1
2
  import { normalizeNamespaceIdentity } from "../namespaces/identity.js";
2
- import { OFFLINE_SYNC_MAX_MTIME_MS } from "../offline-sync.js";
3
3
  import { validateArchiveRelativePath } from "../transfer/fs-utils.js";
4
4
  import type { OfflineSyncFileState } from "../offline-sync.js";
5
5
  import {
@@ -197,8 +197,7 @@ export class ReconcilePlanInputError extends Error {
197
197
  }
198
198
  }
199
199
 
200
- /** Matches `assertSha256` in offline-sync so both surfaces reject the same values (§40). */
201
- const SHA256_PATTERN = /^[a-f0-9]{64}$/i;
200
+ /** Shared with offline-sync's census validation (issue #2477): one acceptance set. */
202
201
 
203
202
  /**
204
203
  * Validate a census record's identity fields.
@@ -247,14 +246,9 @@ function assertCensusRecord(
247
246
  * deliberately does not require an integer.
248
247
  */
249
248
  function assertMtimeMs(value: unknown, context: string): number {
250
- if (
251
- typeof value !== "number"
252
- || !Number.isFinite(value)
253
- || value < 0
254
- || value > OFFLINE_SYNC_MAX_MTIME_MS
255
- ) {
249
+ if (!isCensusMtimeMs(value)) {
256
250
  throw new ReconcilePlanInputError(
257
- `reconcile: ${context} has an out-of-range mtimeMs; expected a finite value between 0 and ${OFFLINE_SYNC_MAX_MTIME_MS}`,
251
+ `reconcile: ${context} has an out-of-range mtimeMs; expected a finite value between 0 and ${CENSUS_MAX_MTIME_MS}`,
258
252
  );
259
253
  }
260
254
  return value;
@@ -267,7 +261,7 @@ function assertMtimeMs(value: unknown, context: string): number {
267
261
  * and tombstone lookups would silently miss.
268
262
  */
269
263
  function assertDigest(value: unknown, context: string): string {
270
- if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
264
+ if (!isSha256Hex(value)) {
271
265
  throw new ReconcilePlanInputError(
272
266
  `reconcile: ${context} must carry a 64-character sha256 hex digest`,
273
267
  );
@@ -503,14 +497,23 @@ function assertConflictPolicy(value: ConvergeConflictPolicy | undefined): Conver
503
497
  * Total ordering for plan entries (§12): namespace, then path. Both are unique
504
498
  * per entry, so equal keys are impossible and the comparator never has to
505
499
  * return 0 for distinct rows — the output is byte-identical across runs, which
506
- * is what makes a convergence report diffable.
500
+ * is what makes a convergence report diffable. Cursor and manifest sort with
501
+ * this same export; a local copy on either side is how ordering drifts (§2477).
507
502
  */
508
- function compareEntries(a: ReconcilePlanEntry, b: ReconcilePlanEntry): number {
503
+ export function compareReconcilePlanEntries(a: ReconcilePlanEntry, b: ReconcilePlanEntry): number {
509
504
  if (a.namespace !== b.namespace) return a.namespace < b.namespace ? -1 : 1;
510
505
  if (a.path !== b.path) return a.path < b.path ? -1 : 1;
511
506
  return 0;
512
507
  }
513
508
 
509
+ /**
510
+ * Identity of a semantic-duplicate pair: the ordered (local, peer) path pair.
511
+ * Cursor derivation and manifest collapse must key prior agreements with this
512
+ * same export or a stored cursor stops matching the plan that wrote it.
513
+ */
514
+ export function semanticAgreementKey(agreement: ReconcileSemanticAgreement): string {
515
+ return `${agreement.local.path}\0${agreement.peer.path}`;
516
+ }
514
517
 
515
518
  function resolveConflict(
516
519
  policy: ConvergeConflictPolicy,
@@ -795,7 +798,7 @@ export function planNamespaceReconciliation(
795
798
  });
796
799
  }
797
800
 
798
- return entries.sort(compareEntries);
801
+ return entries.sort(compareReconcilePlanEntries);
799
802
  }
800
803
 
801
804
  /** Aggregate entries into the per-namespace convergence report (#2150). */
@@ -852,7 +855,7 @@ export function planReconciliation(
852
855
  seenNamespaces.add(name);
853
856
  entries.push(...planNamespaceReconciliation(namespace, options));
854
857
  }
855
- entries.sort(compareEntries);
858
+ entries.sort(compareReconcilePlanEntries);
856
859
  return {
857
860
  entries,
858
861
  byNamespace: summarizeReconcilePlan(entries),
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/reconcile/manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { computeLegacyContentHash, normalizeLegacyContent } from \"../content-hash.js\";\nimport { inferMemoryStatus } from \"../memory-lifecycle-ledger-utils.js\";\nimport { ContentHashIndex, type ContentHashPathEntry } from \"../storage/content-hash-index.js\";\nimport { DEFAULT_CITATION_FORMAT, stripCitationForTemplate } from \"../source-attribution.js\";\nimport { storedContentIdentityCandidates } from \"../structured-attributes.js\";\nimport type { MemoryFrontmatter, MemoryStatus } from \"../types.js\";\nimport { RECALL_FALLBACK_DIRS } from \"../utils/category-dir.js\";\nimport {\n type ReconcileFileState,\n type ReconcilePlan,\n type ReconcilePlanEntry,\n type ReconcileSemanticAgreement,\n type ReconcileSemanticChange,\n summarizeReconcilePlan,\n} from \"./plan.js\";\n\nexport const RECONCILE_MANIFEST_FORMAT = \"remnic-reconcile-manifest\";\nexport const RECONCILE_MANIFEST_SCHEMA_VERSION = 1;\nconst CONTENT_HASH_NORMALIZER_VERSION = 4;\n/**\n * Version of the identity-resolution LOGIC (alias recovery, candidate\n * ordering), independent of the content normalizer: a cached manifest entry\n * written before an identity-resolution change carries an older value and is\n * reparsed even when its normalizerVersion still matches (issue #2367\n * review round 2). Bump when parsedMemoryIdentity's output shape or\n * semantics change.\n */\nconst IDENTITY_RESOLUTION_VERSION = 2;\n\nexport interface ReconcileMemoryIdentity {\n id: string;\n category: string;\n contentHash: string;\n /**\n * Recovered current-normalizer identities for a record whose persisted hash\n * is ambiguous under the legacy normalizer (issue #2367). The persisted\n * hash stays primary; these participate in duplicate bucketing so a\n * replica carrying the current form of the same fact still collapses.\n */\n contentHashAliases?: readonly string[];\n /** See IDENTITY_RESOLUTION_VERSION; absent on pre-versioning cache entries. */\n normalizerVersion?: number;\n identityResolutionVersion?: number;\n status: MemoryStatus;\n}\n\nexport interface ReconcileManifestFile extends ReconcileFileState {\n memory?: ReconcileMemoryIdentity;\n}\n\ntype ActiveFactManifestFile = ReconcileManifestFile & { memory: ReconcileMemoryIdentity };\n\nexport interface ReconcileManifest {\n format: typeof RECONCILE_MANIFEST_FORMAT;\n schemaVersion: typeof RECONCILE_MANIFEST_SCHEMA_VERSION;\n files: ReconcileManifestFile[];\n}\n\nexport type ReconcileMemoryParser = (\n raw: string,\n) => { frontmatter: MemoryFrontmatter; content: string } | null;\n\nexport interface BuildReconcileManifestOptions {\n files: Iterable<ReconcileFileState>;\n readFile: (file: ReconcileFileState) => Promise<Buffer | string | null>;\n parseMemory: ReconcileMemoryParser;\n cachedFiles?: Iterable<ReconcileManifestFile>;\n citationTemplate?: string;\n}\n\nconst SHA256_PATTERN = /^[a-f0-9]{64}$/i;\nconst MEMORY_DIRS = new Set(RECALL_FALLBACK_DIRS);\n\nfunction isMemoryPath(filePath: string): boolean {\n if (!filePath.endsWith(\".md\")) return false;\n const segments = filePath.split(\"/\");\n let index = 0;\n if (segments[index] === \"cold\" || segments[index] === \"archive\") index += 1;\n return MEMORY_DIRS.has(segments[index] ?? \"\");\n}\n\n\nfunction parsedMemoryIdentity(\n filePath: string,\n raw: Buffer | string,\n parseMemory: ReconcileMemoryParser,\n citationTemplate: string,\n): ReconcileMemoryIdentity | undefined {\n if (!isMemoryPath(filePath)) return undefined;\n const parsed = parseMemory(Buffer.isBuffer(raw) ? raw.toString(\"utf8\") : raw);\n if (!parsed?.frontmatter.id) return undefined;\n\n const storedHash =\n parsed.frontmatter.contentHash &&\n SHA256_PATTERN.test(parsed.frontmatter.contentHash)\n ? parsed.frontmatter.contentHash.toLowerCase()\n : undefined;\n const candidates = storedContentIdentityCandidates(parsed.content, (content) =>\n stripCitationForTemplate(content, citationTemplate)\n );\n const bodyHash = ContentHashIndex.computeHash(candidates[0] ?? \"\");\n const currentMatch = candidates.find(\n (content) => ContentHashIndex.computeHash(content) === storedHash\n );\n const legacyMatch = [...candidates].reverse().find(\n (content) => computeLegacyContentHash(content) === storedHash\n );\n let contentHash: string;\n let contentHashAliases: string[] | undefined;\n if (currentMatch) {\n contentHash = ContentHashIndex.computeHash(currentMatch);\n } else if (legacyMatch && normalizeLegacyContent(legacyMatch).length > 0) {\n // A non-empty legacy equality is ambiguous (issue #2367): the persisted\n // hash may be an explicit contentHashSource identity that merely collides\n // with the body's ASCII skeleton under the lossy legacy normalizer, and\n // nothing in the record proves which it is. Keep the persisted hash as\n // the primary identity and expose the recovered current identity as an\n // alias so replicas carrying either form still collapse.\n contentHash = storedHash ?? bodyHash;\n const recovered = ContentHashIndex.computeHash(legacyMatch);\n if (recovered !== contentHash) contentHashAliases = [recovered];\n } else {\n // An EMPTY legacy skeleton identifies nothing (every non-ASCII body maps\n // to it), so preserving it would collapse unrelated records. Recovering\n // the current identity here is repair, not replacement.\n contentHash = legacyMatch ? ContentHashIndex.computeHash(legacyMatch) : storedHash ?? bodyHash;\n }\n\n return {\n id: parsed.frontmatter.id,\n category: parsed.frontmatter.category,\n contentHash,\n ...(contentHashAliases ? { contentHashAliases } : {}),\n normalizerVersion: CONTENT_HASH_NORMALIZER_VERSION,\n identityResolutionVersion: IDENTITY_RESOLUTION_VERSION,\n status: inferMemoryStatus(parsed.frontmatter, filePath),\n };\n}\n\nexport async function buildReconcileManifestFile(\n file: ReconcileFileState,\n readFile: (file: ReconcileFileState) => Promise<Buffer | string | null>,\n parseMemory: ReconcileMemoryParser,\n citationTemplate = DEFAULT_CITATION_FORMAT,\n): Promise<ReconcileManifestFile> {\n let raw: Buffer | string | null = null;\n if (isMemoryPath(file.path)) {\n try {\n raw = await readFile(file);\n } catch {\n raw = null;\n }\n }\n if (raw !== null && createHash(\"sha256\").update(raw).digest(\"hex\") !== file.sha256.toLowerCase()) {\n raw = null;\n }\n const memory = raw === null ? undefined : parsedMemoryIdentity(file.path, raw, parseMemory, citationTemplate);\n return { ...file, ...(memory ? { memory } : {}) };\n}\n\nexport async function buildReconcileManifest(options: BuildReconcileManifestOptions): Promise<ReconcileManifest> {\n const cachedByPath = new Map<string, ReconcileManifestFile>();\n for (const cached of options.cachedFiles ?? []) {\n cachedByPath.set(cached.path, cached);\n }\n\n const files: ReconcileManifestFile[] = [];\n for (const file of options.files) {\n const cached = cachedByPath.get(file.path);\n if (\n cached?.sha256.toLowerCase() === file.sha256.toLowerCase() &&\n (cached.memory === undefined ||\n (cached.memory.normalizerVersion === CONTENT_HASH_NORMALIZER_VERSION &&\n cached.memory.identityResolutionVersion === IDENTITY_RESOLUTION_VERSION))\n ) {\n files.push({ ...file, ...(cached.memory ? { memory: cached.memory } : {}) });\n continue;\n }\n\n files.push(await buildReconcileManifestFile(\n file,\n options.readFile,\n options.parseMemory,\n options.citationTemplate,\n ));\n }\n files.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));\n return {\n format: RECONCILE_MANIFEST_FORMAT,\n schemaVersion: RECONCILE_MANIFEST_SCHEMA_VERSION,\n files,\n };\n}\n\nfunction activeFactByPath(manifest: ReconcileManifest | undefined): Map<string, ActiveFactManifestFile> {\n const result = new Map<string, ActiveFactManifestFile>();\n for (const file of manifest?.files ?? []) {\n if (file.memory?.category === \"fact\" && file.memory.status === \"active\") {\n result.set(file.path, file as ActiveFactManifestFile);\n }\n }\n return result;\n}\n\nfunction memoryIdentityHashes(memory: ReconcileMemoryIdentity): string[] {\n return [memory.contentHash, ...(memory.contentHashAliases ?? [])];\n}\n\nfunction contentHashRows(\n files: Iterable<ActiveFactManifestFile>,\n contentHash: string,\n): ContentHashPathEntry[] {\n const rows: ContentHashPathEntry[] = [];\n for (const file of files) {\n rows.push({ path: file.path, contentHash });\n }\n return rows;\n}\n\nfunction comparePlanEntries(left: ReconcilePlanEntry, right: ReconcilePlanEntry): number {\n if (left.namespace !== right.namespace) return left.namespace < right.namespace ? -1 : 1;\n return left.path < right.path ? -1 : left.path > right.path ? 1 : 0;\n}\n\nfunction semanticAgreementKey(agreement: ReconcileSemanticAgreement): string {\n return `${agreement.local.path}\\0${agreement.peer.path}`;\n}\n\nfunction classifySemanticChange(\n current: ReconcileSemanticAgreement,\n prior: ReconcileSemanticAgreement | undefined\n): ReconcileSemanticChange {\n if (!prior) return \"unchanged\";\n const localChanged = current.local.sha256 !== prior.local.sha256;\n const peerChanged = current.peer.sha256 !== prior.peer.sha256;\n if (localChanged && peerChanged) return \"both_modified\";\n if (localChanged) return \"local_changed\";\n if (peerChanged) return \"peer_changed\";\n return \"unchanged\";\n}\n\nexport function collapseActiveFactDuplicates(\n plan: ReconcilePlan,\n localManifests: ReadonlyMap<string, ReconcileManifest>,\n peerManifests: ReadonlyMap<string, ReconcileManifest>,\n priorSemanticAgreements?: ReadonlyMap<string, readonly ReconcileSemanticAgreement[]>,\n): ReconcilePlan {\n const entriesByNamespace = new Map<string, ReconcilePlanEntry[]>();\n for (const entry of plan.entries) {\n const entries = entriesByNamespace.get(entry.namespace) ?? [];\n entries.push({ ...entry });\n entriesByNamespace.set(entry.namespace, entries);\n }\n\n let changed = false;\n for (const namespace of new Set([...localManifests.keys(), ...peerManifests.keys()])) {\n const entries = entriesByNamespace.get(namespace) ?? [];\n const localManifest = localManifests.get(namespace);\n const peerManifest = peerManifests.get(namespace);\n const localByPath = activeFactByPath(localManifest);\n const peerByPath = activeFactByPath(peerManifest);\n const localFilesByPath = new Map((localManifest?.files ?? []).map((file) => [file.path, file]));\n const peerFilesByPath = new Map((peerManifest?.files ?? []).map((file) => [file.path, file]));\n const priorSemanticByPathPair = new Map(\n (priorSemanticAgreements?.get(namespace) ?? []).map((agreement) => [\n semanticAgreementKey(agreement),\n agreement,\n ])\n );\n const localByHash = new Map<string, ActiveFactManifestFile[]>();\n const peerByHash = new Map<string, ActiveFactManifestFile[]>();\n\n for (const file of localByPath.values()) {\n for (const hash of memoryIdentityHashes(file.memory)) {\n const bucket = localByHash.get(hash) ?? [];\n bucket.push(file);\n localByHash.set(hash, bucket);\n }\n }\n for (const file of peerByPath.values()) {\n for (const hash of memoryIdentityHashes(file.memory)) {\n const bucket = peerByHash.get(hash) ?? [];\n bucket.push(file);\n peerByHash.set(hash, bucket);\n }\n }\n\n const removed = new Set<ReconcilePlanEntry>();\n const replacements: ReconcilePlanEntry[] = [];\n const processedPathPairs = new Set<string>();\n for (const hash of new Set([...localByHash.keys(), ...peerByHash.keys()])) {\n const localCandidates = (localByHash.get(hash) ?? []).filter((file) => {\n const opposite = peerFilesByPath.get(file.path);\n const activeOpposite = peerByPath.get(file.path);\n return opposite === undefined || (\n activeOpposite !== undefined &&\n memoryIdentityHashes(activeOpposite.memory).includes(hash)\n );\n });\n const peerCandidates = (peerByHash.get(hash) ?? []).filter((file) => {\n const opposite = localFilesByPath.get(file.path);\n const activeOpposite = localByPath.get(file.path);\n return opposite === undefined || (\n activeOpposite !== undefined &&\n memoryIdentityHashes(activeOpposite.memory).includes(hash)\n );\n });\n const localPath = ContentHashIndex.resolvePathByHash(hash, contentHashRows(localCandidates, hash));\n const peerPath = ContentHashIndex.resolvePathByHash(hash, contentHashRows(peerCandidates, hash));\n\n if (localPath && peerPath && localPath !== peerPath) {\n const pathPair = `${localPath}\\0${peerPath}`;\n if (processedPathPairs.has(pathPair)) continue;\n processedPathPairs.add(pathPair);\n const localFile = localByPath.get(localPath);\n const peerFile = peerByPath.get(peerPath);\n if (!localFile || !peerFile) continue;\n const duplicatePaths = new Set([\n ...localCandidates.map((file) => file.path),\n ...peerCandidates.map((file) => file.path),\n ]);\n const authoritativeSamePathEntries = new Set(entries.filter(\n (entry) =>\n duplicatePaths.has(entry.path)\n && localFilesByPath.has(entry.path)\n && peerFilesByPath.has(entry.path)\n && entry.action !== \"identical\"\n ));\n if (authoritativeSamePathEntries.size > 0) {\n for (const entry of entries) {\n if (\n duplicatePaths.has(entry.path)\n && !authoritativeSamePathEntries.has(entry)\n && (entry.action === \"pull\" || entry.action === \"push\" || entry.action === \"identical\")\n ) {\n removed.add(entry);\n changed = true;\n }\n }\n continue;\n }\n const unsafeEntry = entries.some(\n (entry) => duplicatePaths.has(entry.path) && (entry.action === \"suppress\" || entry.action === \"conflict\")\n );\n if (unsafeEntry) continue;\n for (const entry of entries) {\n if (\n duplicatePaths.has(entry.path) &&\n (entry.action === \"pull\" || entry.action === \"push\" || entry.action === \"identical\")\n ) {\n removed.add(entry);\n }\n }\n const semanticAgreement: ReconcileSemanticAgreement = {\n local: { path: localPath, sha256: localFile.sha256 },\n peer: { path: peerPath, sha256: peerFile.sha256 },\n };\n replacements.push({\n path: localPath < peerPath ? localPath : peerPath,\n namespace,\n action: \"identical\",\n reason: \"semantic_duplicate\",\n semanticAgreement,\n semanticChange: classifySemanticChange(\n semanticAgreement,\n priorSemanticByPathPair.get(semanticAgreementKey(semanticAgreement))\n ),\n });\n changed = true;\n continue;\n }\n\n const sameSideEntries = entries.filter((entry) => {\n if (localPath && entry.reason === \"local_only\") {\n return localCandidates.some((file) => file.path === entry.path);\n }\n if (peerPath && entry.reason === \"peer_only\") {\n return peerCandidates.some((file) => file.path === entry.path);\n }\n return false;\n });\n const canonicalPath = localPath ?? peerPath;\n const hasSharedCanonical = localPath !== undefined && localPath === peerPath;\n if (!canonicalPath || sameSideEntries.length === 0 || (!hasSharedCanonical && sameSideEntries.length < 2)) {\n continue;\n }\n for (const entry of sameSideEntries) {\n if (entry.path !== canonicalPath) {\n removed.add(entry);\n changed = true;\n }\n }\n }\n\n if (removed.size > 0 || replacements.length > 0) {\n entriesByNamespace.set(\n namespace,\n [...entries.filter((entry) => !removed.has(entry)), ...replacements].sort(comparePlanEntries)\n );\n }\n }\n\n if (!changed) return plan;\n const entries = [...entriesByNamespace.values()].flat().sort(comparePlanEntries);\n return {\n entries,\n byNamespace: summarizeReconcilePlan(entries),\n converged: entries.every((entry) => entry.action === \"identical\"),\n };\n}\n"],"mappings":"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","names":["entries"]}