chainlesschain 0.166.84 → 0.166.86

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 (187) hide show
  1. package/README.md +17 -4
  2. package/package.json +3 -2
  3. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  4. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  5. package/src/assets/web-panel/assets/{AIOps-OYLnAlB5.js → AIOps-DZwiXAIh.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-uc9kbLR6.js → ActionButton-aNzXoIJP.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-BH901FG0.js → Analytics-ETQk4ZXt.js} +3 -3
  8. package/src/assets/web-panel/assets/{AppLayout-qypKb8GL.js → AppLayout-D5oEbeEC.js} +4 -4
  9. package/src/assets/web-panel/assets/{Artifacts-Q-uPWyLY.js → Artifacts-ja48FAne.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-B8hMGMim.js → Audit-DlN0YnDt.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-BSs8rJL_.js → BackgroundAgents-CzvyNsRg.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-2YMU_nCd.js → Backup-DXgEC2MQ.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-DyMppTuT.js → BaseInput-D9XqejLx.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-B5QGMaDO.js → Chat-BTwuYUlN.js} +5 -5
  15. package/src/assets/web-panel/assets/{ChatBubbleRenderer-B8ts_JnN.js → ChatBubbleRenderer-0anIC2Wn.js} +1 -1
  16. package/src/assets/web-panel/assets/{Checkbox-DAvtgEGd.js → Checkbox-CLuaQbDt.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-BSChEVM3.js → Codegen-KwQUZs8A.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-G0zYohIP.js → Col-BCmROkzc.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-CzCN0B_p.js → Community-CXlUEHR4.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-DrIN3G8z.js → Compact-Dzx-lWI2.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-CIsLa64Z.js → Compliance-CfXW82FF.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-CGYoafsB.js → Cowork-DIISTRm2.js} +3 -3
  23. package/src/assets/web-panel/assets/{Cron-DJ1JPXuH.js → Cron-nyJ-KcFE.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-B-IqVFJl.js → Crosschain-Dtv1E4cV.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-CAAczPz_.js → DID-BCTpektS.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-Dkpel8VE.js → Dashboard-C1Q_m0So.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-ETNBf8-Z.js → Dropdown-BrQ5_PPL.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer-CJGDyjVD.js → EmailListRenderer-DjoNlONP.js} +1 -1
  29. package/src/assets/web-panel/assets/{EvolutionSettings-CL_hvVYe.js → EvolutionSettings-DaGaE6bX.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-iexOh6Dh.js → FamilyGuardDashboard-nJDWBECt.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-DnDHvV5D.js → Federation-DhMUY_wG.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-DLCDJjUp.js → FormItemContext-CbONJPuc.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-CaCgXCjS.js → GenericCardRenderer-CDdX4dX-.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-BVm-euUF.js → Git-DXbDVJdR.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-DPnUvLWr.js → Governance-DbAfAvs1.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-5qCWSYKl.js → Inference-Dy-fGea2.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-DXdv2oEh.js → KnowledgeGraph-DAxRpGUF.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-BF2f7bBA.js → Logs-5L1UCR8X.js} +2 -2
  39. package/src/assets/web-panel/assets/MarkdownRenderer-u0FPafvT.js +1 -0
  40. package/src/assets/web-panel/assets/{Marketplace-Bp-0Y7Ng.js → Marketplace-DUk_McDG.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-dm-Xdi63.js → McpTools-B6PNsZ_p.js} +2 -2
  42. package/src/assets/web-panel/assets/{Memory-TBHK031z.js → Memory-BUUs8j5F.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-BTILAlds.js → MobileBridge-pXRsgyuO.js} +1 -1
  44. package/src/assets/web-panel/assets/{MobileProjects-DC0Clgzs.js → MobileProjects-D7v-2Hwm.js} +1 -1
  45. package/src/assets/web-panel/assets/{Mtc-BvyWkmRw.js → Mtc-Deaj4faB.js} +4 -4
  46. package/src/assets/web-panel/assets/{MtcAudit-D7ksLoNa.js → MtcAudit-wUV0VUH1.js} +2 -2
  47. package/src/assets/web-panel/assets/{Multisig-Dpzu7X1t.js → Multisig-C67IIkts.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-DZ0ADHSB.js → NLProgramming-BCA_CWrW.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-hIdkmRug.js → Notes-D9eu14hQ.js} +4 -4
  50. package/src/assets/web-panel/assets/{NotificationSettings-R3jyP6Vf.js → NotificationSettings-Cnd-J4LM.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer-d1zrFkqx.js → OrderTableRenderer-C4pHUuyZ.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-DoA5WMcc.js → Organization-4YExAbnp.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-DHjIloAQ.js → Overflow-BF-wFuZz.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-2TMon_Wy.js → P2P-CdiajqCT.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-DEUxGSWO.js → PdhVaultBrowser-BkjfS6Th.js} +5 -5
  56. package/src/assets/web-panel/assets/{Permissions-CxKcZ-8X.js → Permissions-DrZ4D78_.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-CcYtkHqc.js → PersonalDataHub-lDc6OXjz.js} +6 -6
  58. package/src/assets/web-panel/assets/{Pipeline-BxwOuq22.js → Pipeline-DqDS9ON1.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-VIx9pdsH.js → Privacy-BMekZRZu.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-COjp_erK.js → ProjectInit-LYS7NFvJ.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-x9NrZU80.js → ProjectSettings-CcJNHk9A.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-DMgkw7QJ.js → Projects-DrfAHgdF.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-B_fR7Y7Y.js → Providers-aqsly9SR.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-BUvzrn3V.js → QrScannerModal-ZWvR3zqg.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-C0sJwLy2.js → QuickAsk-BT5CgsvW.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-CJC7Wpce.js → Recommend-Cz8Dz2RN.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-B62J6ZP0.js → RemoteSession-BlxPVbtQ.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-4f48kVBn.js → Reputation-B3VGBE-1.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-EzEMaYoV.js → Row-Bwv7vSEA.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-Dy0wD83j.js → RssFeed-DHI085r_.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-GeVRHQND.js → Search-DBler5T0.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-omLr1J7w.js → Security-BZdw43NW.js} +3 -3
  73. package/src/assets/web-panel/assets/{Services-DmGPaDAs.js → Services-Cslk5lUj.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-CAla21Gm.js → Skeleton-Bur-C-25.js} +1 -1
  75. package/src/assets/web-panel/assets/Skills-v0zgLy0X.js +1 -0
  76. package/src/assets/web-panel/assets/{Sla-Cwi15-pZ.js → Sla-B1OWgQ51.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-Cs8Zi1J8.js → SpeechSettings-CaBcEaV4.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-ipuJsDKP.js → SyncSettings-CBbG8L6J.js} +2 -2
  79. package/src/assets/web-panel/assets/Tasks-C7EjXrnN.js +1 -0
  80. package/src/assets/web-panel/assets/{Templates-slUp9yXF.js → Templates-VBBOZ1H5.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-6edCSG5K.js → Tenant-CtQTj7IF.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-DKq84G3h.js → Terminal-DXVTQjnl.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-Dg0Aw6se.js → TimelineRenderer-BRIno0aN.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-D63h7qYd.js → Tokens-uiiAfkYN.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-F7gTP_gv.js → Trigger-CPApN8o6.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-zw8Zqp8k.js → Trust-T2jKVwCE.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-C-YBUmod.js → UkeySign-C2U_gbzP.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-AhVxTgmC.js → VideoEditing-BCdlr3Kj.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-C58zxjIg.js → Wallet-CWnjoSc1.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-D-n-rlfI.js → WebAuthn-CqSbYqHq.js} +4 -4
  91. package/src/assets/web-panel/assets/{WorkflowEditor-Ddc8gGD4.js → WorkflowEditor-Dr_I3jcr.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-C66V_U7w.js → chat-Cg21FKdE.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-BcJyVZRj.js → colors-CK85tY9e.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-UgUXWcgP.js → compact-item-m-XYPC7W.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-oxEKHKYP.js → createContext-BmiEeNvs.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-CkMEODFC.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-CbqzrKQO.js → hasIn-CCw4KAoc.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CH13yc9x.js → index-6jXf-Lln.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BdFgUn1S.js → index-8KzhlvJ9.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-D3xNhHF3.js → index-B7xJkhtJ.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DAIuMvVR.js → index-B8IC9ZeZ.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CrG-1Kqs.js → index-BGUYgc96.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-Bu1A2sXy.js → index-BGb1j2z9.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BQlGMcZ9.js → index-BHsiSoGY.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DWFNv_EG.js → index-BIOTRzaF.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BrWZmUdA.js → index-BStyjmLG.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-C-p9hhAz.js → index-BUYyhYP6.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CM2S5TJZ.js → index-BcfVHmFK.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-7lSY8B9T.js → index-BiVPFsat.js} +1 -1
  110. package/src/assets/web-panel/assets/index-BjE5qaKn.js +1 -0
  111. package/src/assets/web-panel/assets/{index-B-h0Jgtj.js → index-Br0ZNdu1.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-D8rgeCoC.js → index-Br9t_I1X.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DRrxJygS.js → index-BtvVupUL.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DO-5vgTW.js → index-Bv90WEWa.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DBEL07sa.js → index-BvjC3t35.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-UpN_kC9R.js → index-ByVvAXdl.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-SzGgAqai.js → index-CBcgIWOe.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-C3j9jTfB.js → index-CN2xbLGz.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Bmhw_Ler.js → index-CYEzWoyZ.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Crqqbu-W.js → index-Cf5arJie.js} +1 -1
  121. package/src/assets/web-panel/assets/index-CsFH-2Zk.js +1 -0
  122. package/src/assets/web-panel/assets/{index-BMOY4PSW.js → index-D1sU5-XA.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BT2PZ9df.js → index-D7gS_TJl.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-YvNh1UdB.js → index-DAlGVix-.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BZ7mv_oS.js → index-DI9GJFcn.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-Ov77O9mH.js → index-DIlGxS2W.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CJluGB2F.js → index-DMbrl43d.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-C6X26RJu.js → index-DmSgfXuz.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DMbJ06sF.js → index-DpIy2HzE.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CUmYwZyf.js → index-DxRhnsEC.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BNIq0NAn.js → index-IskAKXhx.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DR4YKlLt.js → index-J81uvUMN.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-RnxuL8wM.js → index-OIbfsi-o.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-Bp9rfr-P.js → index-lhoexBDH.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-BpqJNeIj.js → index-sWUmvT8H.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-CuZupgUw.js → index-yYmjkf38.js} +3 -3
  137. package/src/assets/web-panel/assets/{initDefaultProps-BK9ncFiF.js → initDefaultProps-Cc1XOmNe.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-BxzXi1Pe.js → motion-BehsX88u.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-CRmwRuah.js → move-BPx2YsqY.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-BQq5_Znx.js → mtc-parser-BSFJgFQA.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-Ch4VNY2j.js → omit-DBN5KUeo.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-CUioBhrz.js → pickAttrs-BDK1yZ-9.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-Hj3o1CrW.js → placementArrow-C4rMmKNJ.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-PfqJrlkI.js → responsiveObserve-BoeoOnxX.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-BXcnR-Lu.js → slide-BKlr1bl9.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-C74a-ofo.js → statusUtils-Ky1apWPI.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-Bq57I3ls.js → styleChecker-DxxR7uXk.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-ITPUjcDw.js → useFlexGapSupport-DmsUqVe9.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-DzPuXDIS.js → useFs-CwkG7sOg.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-DYSgt9L5.js → usePersonalDataHub-CddgeEfF.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-NPs_EJEk.js → vnode-C_-H8i4Y.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-DZzO6KVS.js → zoom-aAsHUMNE.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/commands/permissions.js +27 -2
  155. package/src/data/changelog.json +26 -0
  156. package/src/lib/background-interaction-resolver.js +4 -0
  157. package/src/lib/context-memory-kernel/durable-memory-port.js +71 -15
  158. package/src/lib/context-memory-kernel/index.js +1 -0
  159. package/src/lib/context-memory-kernel/memory-service.js +43 -16
  160. package/src/lib/context-memory-kernel/runtime.js +5 -3
  161. package/src/lib/context-memory-kernel/segmented-memory-port.js +687 -0
  162. package/src/lib/packer/pack-update-applier.js +41 -17
  163. package/src/lib/packer/pkg-config-generator.js +119 -2
  164. package/src/lib/packer/pkg-runner.js +7 -0
  165. package/src/lib/packer/pkg-targets.js +83 -0
  166. package/src/lib/packer/web-panel-builder.js +7 -0
  167. package/src/lib/permission-authority.js +157 -13
  168. package/src/lib/process-execution-broker/index.js +11 -0
  169. package/src/lib/process-execution-broker/linux-subreaper-helper.js +87 -13
  170. package/src/lib/process-execution-broker/packer-build-policy.js +31 -0
  171. package/src/lib/process-execution-broker/platform-sandbox.js +11 -3
  172. package/src/lib/sandbox-egress-authority-monitor.js +62 -5
  173. package/src/lib/scoped-permission-store.js +119 -6
  174. package/src/lib/settings-authority-domain.cjs +37 -2
  175. package/src/lib/settings-loader.cjs +112 -42
  176. package/src/lib/settings-permission-authority.cjs +373 -0
  177. package/src/runtime/agent-core.js +79 -383
  178. package/src/runtime/headless-runner.js +8 -3
  179. package/src/runtime/headless-stream.js +10 -3
  180. package/src/runtime/permission-authority-host.js +137 -0
  181. package/src/runtime/provider-stream-state.js +379 -0
  182. package/src/assets/web-panel/assets/MarkdownRenderer-qNJS3ygh.js +0 -1
  183. package/src/assets/web-panel/assets/Skills-QqEyIKOI.js +0 -1
  184. package/src/assets/web-panel/assets/Tasks-BbVAg9Z7.js +0 -1
  185. package/src/assets/web-panel/assets/devWarning-BoDvrsgY.js +0 -1
  186. package/src/assets/web-panel/assets/index-BhqRbl5P.js +0 -1
  187. package/src/assets/web-panel/assets/index-DreT6MIR.js +0 -1
@@ -1,5 +1,5 @@
1
1
  import { ContextMemoryKernel } from "@chainlesschain/context-memory-kernel";
2
- import { DurableJsonMemoryPort } from "./durable-memory-port.js";
2
+ import { SegmentedMemoryPort } from "./segmented-memory-port.js";
3
3
  import { JsonlSessionContextPort } from "./jsonl-session-context-port.js";
4
4
  import { CliLegacyMemoryPrivacyPurgePort } from "./privacy-purge-port.js";
5
5
  import {
@@ -8,7 +8,8 @@ import {
8
8
  } from "./authority.js";
9
9
 
10
10
  export function createCliContextMemoryRuntime(options = {}) {
11
- const scopeKey = options.scopeKey ||
11
+ const scopeKey =
12
+ options.scopeKey ||
12
13
  (options.sessionId ? `cli:session:${options.sessionId}` : "cli:memory");
13
14
  const decision = resolveCliContextMemoryCutover({
14
15
  env: options.env,
@@ -20,7 +21,8 @@ export function createCliContextMemoryRuntime(options = {}) {
20
21
  });
21
22
  const memoryPort =
22
23
  options.memoryPort ||
23
- new DurableJsonMemoryPort({
24
+ new SegmentedMemoryPort({
25
+ readOnly: !decision.canonical,
24
26
  ...(options.memoryFilePath ? { filePath: options.memoryFilePath } : {}),
25
27
  });
26
28
  const sessionPort =
@@ -0,0 +1,687 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import {
3
+ closeSync,
4
+ fsyncSync,
5
+ lstatSync,
6
+ mkdirSync,
7
+ openSync,
8
+ opendirSync,
9
+ realpathSync,
10
+ renameSync,
11
+ rmSync,
12
+ writeFileSync,
13
+ } from "node:fs";
14
+ import { dirname, join, resolve } from "node:path";
15
+ import {
16
+ canonicalDigest,
17
+ cloneCanonical,
18
+ normalizeMemoryRecord,
19
+ } from "@chainlesschain/context-memory-kernel";
20
+ import {
21
+ DurableJsonMemoryPort,
22
+ STORE_SCHEMA,
23
+ corruptStore,
24
+ normalizeState,
25
+ readBoundedState,
26
+ stateDigest,
27
+ syncDirectory,
28
+ } from "./durable-memory-port.js";
29
+
30
+ export const SEGMENTED_STORE_SCHEMA =
31
+ "chainlesschain.cli-context-memory-store/v2";
32
+ export const DEFAULT_MAX_TOTAL_BYTES = 1024 * 1024 * 1024;
33
+ export const DEFAULT_SEGMENTED_MAX_EVENTS = 1_000_000;
34
+ const MANIFEST_LIMIT = 256 * 1024;
35
+ const HEX = /^sha256:[a-f0-9]{64}$/u;
36
+ const BUCKET = /^[a-f0-9]{2}$/u;
37
+
38
+ function bucketOf(value) {
39
+ return createHash("sha256").update(value).digest("hex").slice(0, 2);
40
+ }
41
+ function own(object, key) {
42
+ return Object.hasOwn(object, key) ? object[key] : null;
43
+ }
44
+ function put(object, key, value) {
45
+ Object.defineProperty(object, key, {
46
+ value,
47
+ writable: true,
48
+ configurable: true,
49
+ enumerable: true,
50
+ });
51
+ }
52
+ function limit(reason) {
53
+ return Object.assign(
54
+ new Error(`Context/Memory store limit reached: ${reason}`),
55
+ { code: "CONTEXT_MEMORY_STORE_LIMIT" },
56
+ );
57
+ }
58
+ function manifestDigest(value) {
59
+ const copy = { ...value };
60
+ delete copy.digest;
61
+ return canonicalDigest(copy, SEGMENTED_STORE_SCHEMA);
62
+ }
63
+ function blankShard() {
64
+ return {
65
+ schema: STORE_SCHEMA,
66
+ schemaVersion: 1,
67
+ storeRevision: 0,
68
+ records: {},
69
+ events: [],
70
+ reconciliations: {},
71
+ };
72
+ }
73
+ function blankManifest() {
74
+ return {
75
+ schema: SEGMENTED_STORE_SCHEMA,
76
+ schemaVersion: 2,
77
+ storeRevision: 0,
78
+ shards: {},
79
+ totalBytes: 0,
80
+ eventCount: 0,
81
+ recordCount: 0,
82
+ };
83
+ }
84
+ function regular(file) {
85
+ const stat = lstatSync(file);
86
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1) {
87
+ throw corruptStore(file, "authority must be an unlinked regular file");
88
+ }
89
+ }
90
+ function directories(directory) {
91
+ let current = resolve(directory);
92
+ while (true) {
93
+ const stat = lstatSync(current);
94
+ // macOS exposes its system temporary tree through the root-owned /var
95
+ // alias. This exact OS alias is not a user-controlled authority redirect.
96
+ const systemAlias =
97
+ process.platform === "darwin" &&
98
+ ["/var", "/tmp"].includes(current) &&
99
+ stat.isSymbolicLink() &&
100
+ stat.uid === 0 &&
101
+ realpathSync(current) === `/private${current}`;
102
+ if (!systemAlias && (!stat.isDirectory() || stat.isSymbolicLink()))
103
+ throw corruptStore(current, "authority directory is unsafe");
104
+ const parent = dirname(current);
105
+ if (parent === current) break;
106
+ current = parent;
107
+ }
108
+ }
109
+ function identity(directory) {
110
+ const stat = lstatSync(directory);
111
+ return `${stat.dev}:${stat.ino}`;
112
+ }
113
+ function existsSync(file) {
114
+ try {
115
+ lstatSync(file);
116
+ return true;
117
+ } catch (error) {
118
+ if (error.code === "ENOENT") return false;
119
+ throw error;
120
+ }
121
+ }
122
+ function ensureSafeDirectory(directory) {
123
+ let existing = resolve(directory);
124
+ while (!existsSync(existing)) existing = dirname(existing);
125
+ directories(existing);
126
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
127
+ directories(directory);
128
+ }
129
+
130
+ /**
131
+ * 256 immutable hash buckets, committed by one atomic manifest replacement.
132
+ * The inherited lock is on the original authority path, including during v1
133
+ * migration. A v1-only client therefore rejects v2 instead of writing a second
134
+ * authority. Audit events are retained, not truncated to buy record capacity.
135
+ */
136
+ export class SegmentedMemoryPort extends DurableJsonMemoryPort {
137
+ constructor({
138
+ maxTotalBytes = DEFAULT_MAX_TOTAL_BYTES,
139
+ maxEvents = DEFAULT_SEGMENTED_MAX_EVENTS,
140
+ readOnly = false,
141
+ ...options
142
+ } = {}) {
143
+ super({ ...options, maxEvents });
144
+ if (!Number.isSafeInteger(maxTotalBytes) || maxTotalBytes < 1)
145
+ throw new TypeError("maxTotalBytes must be a positive safe integer");
146
+ this.maxTotalBytes = maxTotalBytes;
147
+ this.readOnly = Boolean(readOnly);
148
+ this.filePath = resolve(this.filePath);
149
+ this.shardDirectory = `${this.filePath}.shards`;
150
+ }
151
+
152
+ _locked(operation) {
153
+ ensureSafeDirectory(dirname(this.filePath));
154
+ directories(dirname(this.filePath));
155
+ return super._locked(() => {
156
+ directories(dirname(this.filePath));
157
+ this._parentIdentity = identity(dirname(this.filePath));
158
+ this._shardIdentity = existsSync(this.shardDirectory)
159
+ ? identity(this.shardDirectory)
160
+ : null;
161
+ if (existsSync(this.filePath)) regular(this.filePath);
162
+ return operation();
163
+ });
164
+ }
165
+
166
+ _checkDirectories() {
167
+ directories(dirname(this.filePath));
168
+ directories(this.shardDirectory);
169
+ if (
170
+ identity(dirname(this.filePath)) !== this._parentIdentity ||
171
+ (this._shardIdentity !== null &&
172
+ identity(this.shardDirectory) !== this._shardIdentity)
173
+ ) {
174
+ throw corruptStore(
175
+ this.filePath,
176
+ "authority directory was replaced during an operation",
177
+ );
178
+ }
179
+ this._shardIdentity = identity(this.shardDirectory);
180
+ if (
181
+ process.platform !== "win32" &&
182
+ lstatSync(this.shardDirectory).mode & 0o077
183
+ )
184
+ throw corruptStore(
185
+ this.shardDirectory,
186
+ "shard directory must be private",
187
+ );
188
+ }
189
+
190
+ _shardPath(bucket, descriptor) {
191
+ return join(
192
+ this.shardDirectory,
193
+ `${bucket}-${descriptor.digest.slice(7)}.json`,
194
+ );
195
+ }
196
+
197
+ _validateManifest(input) {
198
+ const invalid = () => {
199
+ throw corruptStore(this.filePath, "segmented manifest is invalid");
200
+ };
201
+ if (
202
+ !input ||
203
+ input.schema !== SEGMENTED_STORE_SCHEMA ||
204
+ input.schemaVersion !== 2 ||
205
+ !Number.isSafeInteger(input.storeRevision) ||
206
+ input.storeRevision < 0 ||
207
+ !input.shards ||
208
+ Array.isArray(input.shards) ||
209
+ typeof input.shards !== "object" ||
210
+ Object.keys(input).sort().join() !==
211
+ "digest,eventCount,recordCount,schema,schemaVersion,shards,storeRevision,totalBytes" ||
212
+ input.digest !== manifestDigest(input)
213
+ )
214
+ invalid();
215
+ let bytes = 0;
216
+ let events = 0;
217
+ let records = 0;
218
+ for (const [bucket, entry] of Object.entries(input.shards)) {
219
+ if (
220
+ !BUCKET.test(bucket) ||
221
+ !entry ||
222
+ Object.keys(entry).sort().join() !==
223
+ "bytes,digest,eventCount,recordCount" ||
224
+ !HEX.test(entry.digest)
225
+ )
226
+ invalid();
227
+ for (const key of ["bytes", "eventCount", "recordCount"])
228
+ if (!Number.isSafeInteger(entry[key]) || entry[key] < 0) invalid();
229
+ if (entry.bytes > this.maxStoreBytes)
230
+ throw limit("shard exceeds configured byte limit");
231
+ bytes += entry.bytes;
232
+ events += entry.eventCount;
233
+ records += entry.recordCount;
234
+ }
235
+ if (
236
+ bytes !== input.totalBytes ||
237
+ events !== input.eventCount ||
238
+ records !== input.recordCount ||
239
+ ![bytes, events, records].every(Number.isSafeInteger)
240
+ )
241
+ invalid();
242
+ if (bytes > this.maxTotalBytes || events > this.maxEvents)
243
+ throw limit("aggregate capacity exceeded");
244
+ return input;
245
+ }
246
+
247
+ _loadManifest() {
248
+ if (!existsSync(this.filePath)) return blankManifest();
249
+ regular(this.filePath);
250
+ // v1 can be larger than a manifest; preserve its existing read boundary.
251
+ let input;
252
+ let bytes;
253
+ try {
254
+ bytes = readBoundedState(
255
+ this.filePath,
256
+ Math.max(this.maxStoreBytes, MANIFEST_LIMIT),
257
+ true,
258
+ );
259
+ input = JSON.parse(bytes.toString("utf8"));
260
+ } catch (cause) {
261
+ throw corruptStore(this.filePath, "authority cannot be parsed", cause);
262
+ }
263
+ if (input?.schema === STORE_SCHEMA) {
264
+ if (bytes.length > this.maxStoreBytes)
265
+ throw corruptStore(
266
+ this.filePath,
267
+ "legacy store exceeds its configured byte limit",
268
+ );
269
+ const legacy = normalizeState(input, this.filePath);
270
+ if (this.readOnly) return legacy;
271
+ return this._installSnapshot(legacy);
272
+ }
273
+ const manifest = this._validateManifest(input);
274
+ if (bytes.length > MANIFEST_LIMIT)
275
+ throw corruptStore(this.filePath, "manifest is too large");
276
+ this._checkDirectories();
277
+ if (!this.readOnly) this._collect(manifest);
278
+ return manifest;
279
+ }
280
+
281
+ _readShard(manifest, bucket) {
282
+ if (manifest.schema === STORE_SCHEMA) return manifest;
283
+ const descriptor = manifest.shards[bucket];
284
+ if (!descriptor) return blankShard();
285
+ return this._decodeShard(
286
+ manifest,
287
+ bucket,
288
+ this._readShardBytes(manifest, bucket),
289
+ );
290
+ }
291
+
292
+ _readShardBytes(manifest, bucket) {
293
+ const descriptor = manifest.shards[bucket];
294
+ this._checkDirectories();
295
+ const file = this._shardPath(bucket, descriptor);
296
+ regular(file);
297
+ const bytes = readBoundedState(
298
+ file,
299
+ Math.min(this.maxStoreBytes, descriptor.bytes),
300
+ true,
301
+ );
302
+ if (bytes.length !== descriptor.bytes)
303
+ throw corruptStore(file, "shard byte length changed");
304
+ return bytes;
305
+ }
306
+
307
+ _decodeShard(manifest, bucket, bytes) {
308
+ const descriptor = manifest.shards[bucket];
309
+ const file = this._shardPath(bucket, descriptor);
310
+ let state;
311
+ try {
312
+ state = normalizeState(JSON.parse(bytes.toString("utf8")), file);
313
+ } catch (cause) {
314
+ throw corruptStore(file, "shard cannot be validated", cause);
315
+ }
316
+ if (
317
+ state.digest !== descriptor.digest ||
318
+ state.events.length !== descriptor.eventCount ||
319
+ Object.keys(state.records).length !== descriptor.recordCount ||
320
+ Object.keys(state.records).some((id) => bucketOf(id) !== bucket) ||
321
+ Object.keys(state.reconciliations).some(
322
+ (id) => bucketOf(id) !== bucket,
323
+ ) ||
324
+ state.events.some(
325
+ (entry, index) =>
326
+ !entry ||
327
+ Object.keys(entry).sort().join() !== "event,sequence" ||
328
+ !Number.isSafeInteger(entry.sequence) ||
329
+ entry.sequence < 1 ||
330
+ entry.sequence > manifest.eventCount ||
331
+ (index > 0 && entry.sequence <= state.events[index - 1].sequence),
332
+ )
333
+ ) {
334
+ throw corruptStore(file, "shard does not match its manifest");
335
+ }
336
+ return state;
337
+ }
338
+
339
+ _collect(manifest) {
340
+ this._checkDirectories();
341
+ const live = new Set(
342
+ Object.entries(manifest.shards).map(
343
+ ([bucket, entry]) => `${bucket}-${entry.digest.slice(7)}.json`,
344
+ ),
345
+ );
346
+ // All readers hold the same authority lock. No reader can still be using
347
+ // a superseded shard here. Unknown files are never treated as our garbage.
348
+ const directory = opendirSync(this.shardDirectory);
349
+ let scanned = 0;
350
+ let removed = false;
351
+ try {
352
+ let entry;
353
+ while ((entry = directory.readSync()) !== null) {
354
+ if (++scanned > 4096)
355
+ throw limit("shard directory inventory exceeds 4096 entries");
356
+ const owned =
357
+ /^[a-f0-9]{2}-[a-f0-9]{64}\.json$/u.test(entry.name) ||
358
+ /^manifest-[a-f0-9-]{36}\.tmp$/u.test(entry.name);
359
+ if (owned && !live.has(entry.name)) {
360
+ const file = join(this.shardDirectory, entry.name);
361
+ regular(file);
362
+ this._checkDirectories();
363
+ rmSync(file);
364
+ removed = true;
365
+ }
366
+ }
367
+ } finally {
368
+ directory.closeSync();
369
+ }
370
+ // A successful purge must persist the removal of superseded plaintext
371
+ // shards on platforms supporting directory fsync, not just the manifest.
372
+ if (removed) syncDirectory(this.shardDirectory);
373
+ }
374
+
375
+ _publish(manifest, changed) {
376
+ if (this.readOnly)
377
+ throw Object.assign(
378
+ new Error("Context/Memory shadow authority is read-only"),
379
+ { code: "CONTEXT_MEMORY_READ_ONLY" },
380
+ );
381
+ ensureSafeDirectory(this.shardDirectory);
382
+ this._checkDirectories();
383
+ this._collect(manifest);
384
+ const next = cloneCanonical(manifest);
385
+ const pending = [];
386
+ let pendingBytes = manifest.totalBytes;
387
+ let pendingEvents = manifest.eventCount;
388
+ for (const [bucket, state] of changed) {
389
+ state.digest = stateDigest(state);
390
+ const bytes = `${JSON.stringify(state)}\n`;
391
+ const size = Buffer.byteLength(bytes);
392
+ if (size > this.maxStoreBytes)
393
+ throw limit("shard exceeds configured byte limit");
394
+ pendingBytes += size - (next.shards[bucket]?.bytes || 0);
395
+ pendingEvents +=
396
+ state.events.length - (next.shards[bucket]?.eventCount || 0);
397
+ if (pendingBytes > this.maxTotalBytes || pendingEvents > this.maxEvents)
398
+ throw limit("aggregate capacity exceeded");
399
+ next.shards[bucket] = {
400
+ digest: state.digest,
401
+ bytes: size,
402
+ eventCount: state.events.length,
403
+ recordCount: Object.keys(state.records).length,
404
+ };
405
+ pending.push({
406
+ file: this._shardPath(bucket, next.shards[bucket]),
407
+ bytes,
408
+ });
409
+ }
410
+ next.totalBytes = Object.values(next.shards).reduce(
411
+ (sum, entry) => sum + entry.bytes,
412
+ 0,
413
+ );
414
+ next.eventCount = Object.values(next.shards).reduce(
415
+ (sum, entry) => sum + entry.eventCount,
416
+ 0,
417
+ );
418
+ next.recordCount = Object.values(next.shards).reduce(
419
+ (sum, entry) => sum + entry.recordCount,
420
+ 0,
421
+ );
422
+ next.digest = manifestDigest(next);
423
+ this._validateManifest(next);
424
+ const serialized = `${JSON.stringify(next)}\n`;
425
+ if (Buffer.byteLength(serialized) > MANIFEST_LIMIT)
426
+ throw limit("manifest exceeds byte limit");
427
+ const temporary = join(this.shardDirectory, `manifest-${randomUUID()}.tmp`);
428
+ const created = [];
429
+ let published = false;
430
+ let directorySynced = false;
431
+ let failure = null;
432
+ try {
433
+ for (const { file, bytes } of pending) {
434
+ // Content-addressed existing files must match, including crash leftovers.
435
+ if (existsSync(file)) {
436
+ regular(file);
437
+ if (
438
+ !readBoundedState(file, this.maxStoreBytes, true).equals(
439
+ Buffer.from(bytes),
440
+ )
441
+ )
442
+ throw corruptStore(file, "immutable shard was modified");
443
+ continue;
444
+ }
445
+ const fd = openSync(file, "wx", 0o600);
446
+ created.push(file);
447
+ try {
448
+ writeFileSync(fd, bytes, "utf8");
449
+ fsyncSync(fd);
450
+ } finally {
451
+ closeSync(fd);
452
+ }
453
+ }
454
+ syncDirectory(this.shardDirectory);
455
+ const fd = openSync(temporary, "wx", 0o600);
456
+ try {
457
+ writeFileSync(fd, serialized, "utf8");
458
+ fsyncSync(fd);
459
+ } finally {
460
+ closeSync(fd);
461
+ }
462
+ this._checkDirectories();
463
+ if (existsSync(this.filePath)) regular(this.filePath);
464
+ renameSync(temporary, this.filePath);
465
+ published = true;
466
+ syncDirectory(dirname(this.filePath));
467
+ directorySynced = true;
468
+ this._collect(next);
469
+ return next;
470
+ } catch (cause) {
471
+ failure = cause;
472
+ if (published) {
473
+ // The manifest is now authoritative: never roll it back or remove its
474
+ // shards. Callers must observe the revision before attempting a retry.
475
+ throw Object.assign(
476
+ new Error(
477
+ "Context/Memory commit was published but post-commit maintenance failed",
478
+ { cause },
479
+ ),
480
+ {
481
+ code: "CONTEXT_MEMORY_COMMIT_PUBLISHED",
482
+ committed: true,
483
+ storeRevision: next.storeRevision,
484
+ durability:
485
+ process.platform === "win32"
486
+ ? "directory-sync-unsupported"
487
+ : directorySynced
488
+ ? "directory-sync-completed"
489
+ : "unknown",
490
+ cleanupComplete: false,
491
+ },
492
+ );
493
+ }
494
+ throw cause;
495
+ } finally {
496
+ if (!published) {
497
+ try {
498
+ this._checkDirectories();
499
+ rmSync(temporary, { force: true });
500
+ for (const file of created) {
501
+ this._checkDirectories();
502
+ rmSync(file, { force: true });
503
+ }
504
+ } catch {
505
+ // Preserve the primary failure and leave recoverable private orphans;
506
+ // never follow a replaced parent directory to perform cleanup.
507
+ if (failure) failure.unpublishedCleanupIncomplete = true;
508
+ }
509
+ }
510
+ }
511
+ }
512
+
513
+ _installSnapshot(state) {
514
+ const shards = new Map();
515
+ const get = (id) => {
516
+ const bucket = bucketOf(id);
517
+ if (!shards.has(bucket)) shards.set(bucket, blankShard());
518
+ return shards.get(bucket);
519
+ };
520
+ for (const [id, record] of Object.entries(state.records))
521
+ put(get(id).records, id, record);
522
+ for (const [index, event] of state.events.entries()) {
523
+ // Keep even legacy events without a memoryId; never discard audit data.
524
+ get(
525
+ typeof event?.memoryId === "string" ? event.memoryId : "",
526
+ ).events.push({ sequence: index + 1, event });
527
+ }
528
+ for (const [id, operation] of Object.entries(state.reconciliations))
529
+ put(get(id).reconciliations, id, operation);
530
+ const manifest = blankManifest();
531
+ manifest.storeRevision = state.storeRevision;
532
+ return this._publish(manifest, shards);
533
+ }
534
+
535
+ /** Explicit atomic import into an empty authority; also used by migration. */
536
+ async importSnapshot(snapshot) {
537
+ const state = normalizeState(snapshot, this.filePath);
538
+ return this._locked(() => {
539
+ if (existsSync(this.filePath))
540
+ throw Object.assign(
541
+ new Error("Snapshot import requires an absent authority"),
542
+ { code: "CONTEXT_MEMORY_IMPORT_CONFLICT" },
543
+ );
544
+ const result = this._installSnapshot(state);
545
+ return {
546
+ ok: true,
547
+ storeRevision: result.storeRevision,
548
+ recordCount: result.recordCount,
549
+ };
550
+ });
551
+ }
552
+
553
+ async read(id) {
554
+ return this._locked(() =>
555
+ cloneCanonical(
556
+ own(this._readShard(this._loadManifest(), bucketOf(id)).records, id),
557
+ ),
558
+ );
559
+ }
560
+ async query() {
561
+ const snapshot = await this._locked(() => {
562
+ const manifest = this._loadManifest();
563
+ if (manifest.schema === STORE_SCHEMA) return { legacy: manifest };
564
+ let capturedBytes = 0;
565
+ const shards = Object.keys(manifest.shards).map((bucket) => {
566
+ const bytes = this._readShardBytes(manifest, bucket);
567
+ capturedBytes += bytes.length;
568
+ if (
569
+ capturedBytes > manifest.totalBytes ||
570
+ capturedBytes > this.maxTotalBytes
571
+ )
572
+ throw limit("snapshot exceeds aggregate byte limit");
573
+ return [bucket, bytes];
574
+ });
575
+ if (capturedBytes !== manifest.totalBytes)
576
+ throw corruptStore(
577
+ this.filePath,
578
+ "snapshot byte count does not match manifest",
579
+ );
580
+ return { manifest, shards };
581
+ });
582
+ if (snapshot.legacy) return Object.values(snapshot.legacy.records);
583
+ // The lock protects capture of every byte in one manifest generation.
584
+ // Expensive validation uses owned buffers after releasing the lock, so GC
585
+ // and other readers/writers need not wait for whole-store JSON validation.
586
+ return snapshot.shards.flatMap(([bucket, bytes]) =>
587
+ Object.values(
588
+ this._decodeShard(snapshot.manifest, bucket, bytes).records,
589
+ ),
590
+ );
591
+ }
592
+ async getRevision() {
593
+ return this._locked(() => this._loadManifest().storeRevision);
594
+ }
595
+ async exportSnapshot() {
596
+ return this._locked(() => {
597
+ const manifest = this._loadManifest();
598
+ if (manifest.schema === STORE_SCHEMA) return manifest;
599
+ const state = blankShard();
600
+ state.storeRevision = manifest.storeRevision;
601
+ const events = [];
602
+ for (const bucket of Object.keys(manifest.shards)) {
603
+ const shard = this._readShard(manifest, bucket);
604
+ for (const [id, record] of Object.entries(shard.records))
605
+ put(state.records, id, record);
606
+ for (const [id, operation] of Object.entries(shard.reconciliations))
607
+ put(state.reconciliations, id, operation);
608
+ for (const entry of shard.events) events.push(entry);
609
+ }
610
+ events.sort((left, right) => left.sequence - right.sequence);
611
+ if (events.some((entry, index) => entry.sequence !== index + 1))
612
+ throw corruptStore(this.filePath, "audit sequence is invalid");
613
+ state.events = events.map((entry) => entry.event);
614
+ state.digest = stateDigest(state);
615
+ return state;
616
+ });
617
+ }
618
+ async getReconciliation(id) {
619
+ return this._locked(() =>
620
+ cloneCanonical(
621
+ own(
622
+ this._readShard(this._loadManifest(), bucketOf(id)).reconciliations,
623
+ id,
624
+ ),
625
+ ),
626
+ );
627
+ }
628
+ _advance(manifest) {
629
+ if (manifest.storeRevision >= Number.MAX_SAFE_INTEGER)
630
+ throw limit("store revision is exhausted");
631
+ manifest.storeRevision += 1;
632
+ }
633
+ async commit({ record, event, reconciliation }, expectedRevision = 0) {
634
+ const normalized = normalizeMemoryRecord(record);
635
+ return this._locked(() => {
636
+ const manifest = this._loadManifest();
637
+ const bucket = bucketOf(normalized.memoryId);
638
+ const shard = this._readShard(manifest, bucket);
639
+ const actual = own(shard.records, normalized.memoryId)?.revision || 0;
640
+ if (actual !== expectedRevision)
641
+ return {
642
+ ok: false,
643
+ currentRevision: actual,
644
+ storeRevision: manifest.storeRevision,
645
+ };
646
+ const changed = new Map([[bucket, shard]]);
647
+ put(shard.records, normalized.memoryId, normalized);
648
+ shard.events.push({
649
+ sequence: manifest.eventCount + 1,
650
+ event: cloneCanonical(event),
651
+ });
652
+ if (reconciliation) {
653
+ const target = bucketOf(reconciliation.requestId);
654
+ if (!changed.has(target))
655
+ changed.set(target, this._readShard(manifest, target));
656
+ put(
657
+ changed.get(target).reconciliations,
658
+ reconciliation.requestId,
659
+ cloneCanonical(reconciliation),
660
+ );
661
+ }
662
+ this._advance(manifest);
663
+ this._publish(manifest, changed);
664
+ return {
665
+ ok: true,
666
+ revision: normalized.revision,
667
+ storeRevision: manifest.storeRevision,
668
+ reconciliationStored: Boolean(reconciliation),
669
+ };
670
+ });
671
+ }
672
+ async putReconciliation(operation) {
673
+ return this._locked(() => {
674
+ const manifest = this._loadManifest();
675
+ const bucket = bucketOf(operation.requestId);
676
+ const shard = this._readShard(manifest, bucket);
677
+ put(
678
+ shard.reconciliations,
679
+ operation.requestId,
680
+ cloneCanonical(operation),
681
+ );
682
+ this._advance(manifest);
683
+ this._publish(manifest, new Map([[bucket, shard]]));
684
+ return { ok: true, storeRevision: manifest.storeRevision };
685
+ });
686
+ }
687
+ }