chainlesschain 0.163.2 → 0.163.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/package.json +3 -2
  2. package/scripts/command-lifecycle-report.mjs +330 -38
  3. package/src/assets/web-panel/assets/{AIOps-DnGsmsqJ.js → AIOps-DB5X7qqV.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-CrO6fcQ7.js → ActionButton-DxHRtKLk.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-DsBdOaP2.js → Analytics-DIH5QrxT.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-DmQslKUB.js → AppLayout-uu0oxVz7.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-BvyrT_QQ.js → Artifacts-BNSMiW9Y.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-prndoLiC.js → Audit-DcAzB4_x.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-DOTFyA55.js → BackgroundAgents-ClSsvjIc.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-9zOnPp_8.js → Backup-BMciY0js.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-C4PC6sAG.js → BaseInput-Hn3b57Sh.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-BBAF-W9v.js → Chat-CoSlC53V.js} +6 -6
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CWhvvloO.js → ChatBubbleRenderer-CFevJ3So.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-BI9iculd.js → Checkbox-B4L4Eu4w.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-3DP9BCKb.js → Codegen-C77FgRxF.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-df-2TnJX.js → Col-BrLtJLH3.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-CHs8gG_A.js → Community-Be7pXXhA.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-CDfF6q7V.js → Compact-DuGPecjo.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-CthrDnO7.js → Compliance-CzS66mjP.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-HjIqsWJw.js → Cowork-CEhjCoyK.js} +2 -2
  21. package/src/assets/web-panel/assets/{Cron-BKc196fb.js → Cron-D7ir8Vja.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-CJnjdc8W.js → Crosschain-BcXmuLxf.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-BHUy-8Q2.js → DID-Cto1dtBy.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-QDB7sKeY.js → Dashboard-B7KjgwOX.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-DStcVK11.js → Dropdown-COMqxijg.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-BwSUskMJ.js → EmailListRenderer-BZrya-nf.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DgF_kurT.js → FamilyGuardDashboard-AmVeMq0J.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-k8wvv-wN.js → Federation-kZU8c-27.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-C11qoq2r.js → FormItemContext-sOV22v2-.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-BIIpr9kg.js → GenericCardRenderer-BUuLtGCz.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-BlA1exTe.js → Git-K-ZxsLsH.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CYa1ZYM4.js → Governance-D3OEnawI.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-6_kKVIt6.js → Inference-Bll0gRmP.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-CB4AwxYy.js → KnowledgeGraph-DBXwjr53.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-xf7nHFAe.js → Logs-CEzPwEfP.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-Bf3DEqP6.js → MarkdownRenderer-CLmCbHx6.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-aPg8e4Jt.js → Marketplace-BYyRyOPz.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-C-TNlVUf.js → McpTools-DVu8w_rM.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-DMTPIpGa.js → Memory-tenuZ_hO.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-BxyvveRq.js → MobileBridge-Bxgy4ozS.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-DyPCrAyQ.js → MobileProjects-Dgx1D6sc.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-BdhqNXp6.js → Mtc-CRfJ8r8j.js} +5 -5
  43. package/src/assets/web-panel/assets/{MtcAudit-Cz-HFxzW.js → MtcAudit-BBJq4Aiz.js} +5 -5
  44. package/src/assets/web-panel/assets/{Multisig-CIUaFNsx.js → Multisig-B3ii9lai.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-CWe6-J17.js → NLProgramming-FIi-Cubv.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-vYAKeviU.js → Notes-DIq8MPf1.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-DQL9-2d7.js → NotificationSettings-BFOD9eGd.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-DnXj1jBn.js → OrderTableRenderer--41u7vre.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-B3spU8AN.js → Organization-B_0AWztW.js} +2 -2
  50. package/src/assets/web-panel/assets/{Overflow-DgEwo7vm.js → Overflow-B6I7TuXv.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-hujMRH8X.js → P2P-Cdf6qens.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-C_qhdg9h.js → PdhVaultBrowser-B_0FcQJY.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-vp7LMcSf.js → Permissions-6nFBjsQp.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-Dxr5Hgnq.js → PersonalDataHub-nQYV7xpn.js} +3 -3
  55. package/src/assets/web-panel/assets/{Pipeline-DEPZqqH4.js → Pipeline-CBVod5jm.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-DTpDiKPe.js → Privacy-D52jW1Nt.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-jDJsRWmF.js → ProjectInit-Bdx3BYhf.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-Dhor5RWH.js → ProjectSettings-b7xr4DUa.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-DhfifCKx.js +1 -0
  60. package/src/assets/web-panel/assets/Providers-Dy_PgDMr.js +1 -0
  61. package/src/assets/web-panel/assets/{QrScannerModal-CjIw-wIb.js → QrScannerModal-FQKSmc3e.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-DX6jcqoi.js → QuickAsk-CSTrhzcF.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-DopXkhar.js → Recommend-CxMABOUh.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-BLvqJE9c.js → RemoteSession-Cv2T8yRa.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-DhRj2Me7.js → Reputation-CE1k4Vvu.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-DQ53vuPq.js → Row-BNgNcAV_.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-BslvwiBD.js → RssFeed-5ZiBZ3ph.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-DJKBDNKZ.js → Search-mDOIF1UV.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-COhpxHER.js → Security-CtuCTC-e.js} +3 -3
  70. package/src/assets/web-panel/assets/{Services-dbulD6cI.js → Services-DxFO2Zg7.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-ciIVNfhC.js → Skeleton-CKmKkH9y.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-AiRTkTCk.js → Skills-Bmzl9qkk.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-DDMcWYBc.js → Sla-9RqaWHOC.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-BPxWbpXO.js → SpeechSettings-CGdzFIYw.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-BoEOXCyg.js → SyncSettings-CvNuCeBT.js} +2 -2
  76. package/src/assets/web-panel/assets/Tasks-B_sLdcnw.js +1 -0
  77. package/src/assets/web-panel/assets/{Templates-UowY8Ixe.js → Templates-D9_YijdK.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-DauSLQXN.js → Tenant-Cuc7BUSP.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-DBj7Jneq.js → Terminal-6VYRnJgd.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-UMrPwoZC.js → TimelineRenderer-GtK8xd8Y.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-4ea96-bn.js → Tokens-BZ94Lt5W.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger--ISCqt4b.js → Trigger-DEcZgDaX.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-D4GVimRJ.js → Trust-CC3z9THe.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-Ci-nXRQc.js → UkeySign-Cvtw8cV3.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-Cjk7mKPw.js → VideoEditing-CL4e6Bn3.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-BgOLOO3Z.js → Wallet-ycEemP27.js} +2 -2
  87. package/src/assets/web-panel/assets/{WebAuthn-CCX0K29i.js → WebAuthn-DgZ4X_aw.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-Ch3zgIsE.js → WorkflowEditor-D0qMhSv7.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-DKOU_2Vb.js → chat-DsAg01Zn.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-6OVo4u3C.js → colors-B0oqAhrQ.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-BH78Hlsq.js → compact-item-CmX0Rabz.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-CFRpSbbC.js → createContext-Be4cn1Ub.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-CP2qzG6D.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-DNQhgcVi.js → hasIn-D5v12kVq.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BkN3iTTp.js → index-0eWLkQKT.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CkNEMANR.js → index-301ogenv.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-D3RtTT4K.js → index-5LGY0JbG.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-dDkoL4Pe.js → index-7VKDn-t1.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BHa4WyTZ.js → index-8WvYGnON.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CaYqY35Z.js → index-B1YGjOWC.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-bKm3TUDp.js → index-B3mUHuVc.js} +1 -1
  102. package/src/assets/web-panel/assets/{index--rq90rL9.js → index-BA4oulaP.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DaDgdJYj.js → index-BBREnn1V.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-B_Z-YoxX.js → index-BBvyHguy.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CkqHgG-b.js → index-BKmsS2jj.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CrcLHN2F.js → index-BRIqLy1m.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BaVE6E8y.js → index-BXGSbelg.js} +3 -3
  108. package/src/assets/web-panel/assets/{index-BYrnm5Ob.js → index-BaE0Ui07.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BDSR7ecX.js → index-BoAIAZ5C.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CNuCHHQT.js → index-BqltWojQ.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Eag8e_mA.js → index-CGiIyTZw.js} +1 -1
  112. package/src/assets/web-panel/assets/index-CKB0vbJU.js +1 -0
  113. package/src/assets/web-panel/assets/{index-BepBTpa8.js → index-CZ2wUEgr.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-HNGGVF6o.js → index-CZpfzcuy.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-AuBhLqOm.js → index-Ckrk3pIV.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-7TC3S8SN.js → index-CqoCZ5vR.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CFPJ8yzF.js → index-CyWBir7N.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Bb6kDrUx.js → index-CyyGTMKd.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DKN2noIF.js → index-D0BSkxYQ.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Du0rnksC.js → index-D5YVopyr.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BxRy5LPR.js → index-DKUBj36_.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-sm7W8AXy.js → index-DM724NgU.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BujsOZfc.js → index-DQRsQen0.js} +1 -1
  124. package/src/assets/web-panel/assets/index-DgoXmwqB.js +1 -0
  125. package/src/assets/web-panel/assets/{index-Y9MJv28e.js → index-DikJ0Qqe.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-caye7AOE.js → index-Dn2dSdXx.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-cdf7fd7d.js → index-DyUaFWEk.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BnnXQot_.js → index-Hfe_dEGx.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DvHGtNnF.js → index-_JjRLSD0.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-nbfHl1oV.js → index-frSojpHj.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Cu3L4r5l.js → index-jK3ax6Z6.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D84id2Y4.js → index-qMuZ-NRR.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-hPY0fFrd.js → index-q_fQ94SE.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-BZ9w6jPL.js → initDefaultProps-D4VdhJ9_.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BnwUiOho.js → motion-CZJA0MQW.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-ByUKJnNm.js → move-BqEs7Sb1.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-V7yK1KEl.js → mtc-parser-BpHEtsu1.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-BEXBBt4j.js → omit-BW94tY9W.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-qDm_46Gi.js → pickAttrs-BnBjPWP9.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-BIbhEo_0.js → placementArrow-uUkjQ_hu.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-NbPbDgA_.js → responsiveObserve-B-klUqXK.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-BnUWglZ0.js → slide-CvY1cNAs.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-BX8_wZI5.js → statusUtils-Ix5sEDT2.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-BV4OycQX.js → styleChecker-C8qcXOne.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-Od_H5llY.js → useFlexGapSupport-KNF6Kj8f.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-BWI-PyiX.js → useFs-DGJr345n.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-BqMuhG3g.js → usePersonalDataHub-BboN99rI.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-DNd23z6f.js → vnode-Bgt8Vgte.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-Cjaxnm7l.js → zoom-BhJtErgt.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-help-index.json +2 -2
  152. package/src/commands/agenda.js +6 -5
  153. package/src/commands/agent.js +237 -75
  154. package/src/commands/agents.js +6 -0
  155. package/src/commands/artifacts.js +98 -0
  156. package/src/commands/background-session.js +11 -1
  157. package/src/commands/mcp.js +218 -31
  158. package/src/commands/review.js +221 -22
  159. package/src/commands/session-mcp-recovery.js +5 -5
  160. package/src/data/changelog.json +27 -1
  161. package/src/gateways/ws/ws-agent-handler.js +492 -14
  162. package/src/harness/jsonl-session-store.js +39 -1
  163. package/src/harness/mcp-client.js +414 -71
  164. package/src/harness/prompt-compressor.js +24 -0
  165. package/src/harness/provider-backed-compaction.js +205 -0
  166. package/src/harness/worktree-isolator.js +26 -3
  167. package/src/lib/agent-worktree.js +41 -2
  168. package/src/lib/app-preview.js +3 -0
  169. package/src/lib/background-agent-supervisor.js +790 -186
  170. package/src/lib/background-command-argv.js +318 -0
  171. package/src/lib/background-launch-profile.js +1 -0
  172. package/src/lib/background-phase-reporter.js +22 -2
  173. package/src/lib/background-worktree-policy.js +109 -0
  174. package/src/lib/command-lifecycle-report.js +1340 -145
  175. package/src/lib/cowork-mcp-tools.js +51 -6
  176. package/src/lib/cowork-task-runner.js +1 -0
  177. package/src/lib/delivery-coordinator.js +30 -5
  178. package/src/lib/delivery-production-adapter.js +1777 -0
  179. package/src/lib/delivery-production-runner.js +240 -0
  180. package/src/lib/durable-security-store.js +29 -17
  181. package/src/lib/execution-trace/trace-context.cjs +179 -0
  182. package/src/lib/execution-trace/trace-context.js +8 -175
  183. package/src/lib/mcp-call-ledger-store.js +58 -6
  184. package/src/lib/mcp-headers-helper.js +76 -8
  185. package/src/lib/mcp-host-recovery-runtime.js +2 -0
  186. package/src/lib/mcp-ledger-recovery-admission.js +39 -3
  187. package/src/lib/mcp-recovery-adjudication.js +256 -19
  188. package/src/lib/mcp-sandbox-policy.js +184 -0
  189. package/src/lib/mcp-stdio-capsule-builder-worker.cjs +495 -0
  190. package/src/lib/mcp-stdio-executable-identity.js +33 -2
  191. package/src/lib/mcp-stdio-execution-authority.js +163 -0
  192. package/src/lib/mcp-stdio-immutable-vfs-resolver.cjs +830 -0
  193. package/src/lib/mcp-stdio-package-materialization.js +721 -333
  194. package/src/lib/mcp-stdio-workspace-authority.js +252 -0
  195. package/src/lib/observability/index.js +2 -0
  196. package/src/lib/observability/otlp-exporter.js +2 -2
  197. package/src/lib/packer/pack-update-applier.js +8 -1
  198. package/src/lib/packer/pkg-config-generator.js +34 -0
  199. package/src/lib/plugin-runtime/sandbox-policy.js +42 -2
  200. package/src/lib/process-execution-broker/index.js +213 -109
  201. package/src/lib/process-execution-broker/platform-sandbox.js +1024 -169
  202. package/src/lib/process-tree-termination.js +17 -1
  203. package/src/lib/scheduler-kernel/contract.js +315 -0
  204. package/src/lib/scheduler-kernel/store.js +1345 -0
  205. package/src/lib/session-host-lease.js +403 -30
  206. package/src/lib/session-persistence-failure.js +4 -1
  207. package/src/lib/skill-mcp.js +65 -4
  208. package/src/lib/sub-agent-context.js +8 -0
  209. package/src/lib/web-fetch.js +4 -4
  210. package/src/repl/agent-repl.js +285 -38
  211. package/src/runtime/agent-core.js +1044 -90
  212. package/src/runtime/agent-runtime.js +61 -8
  213. package/src/runtime/headless-runner.js +260 -166
  214. package/src/runtime/headless-stream.js +254 -25
  215. package/src/runtime/mcp-config.js +235 -28
  216. package/src/workers/background-agent-worker.js +492 -71
  217. package/src/assets/web-panel/assets/Projects-08xUDZGt.js +0 -1
  218. package/src/assets/web-panel/assets/Providers-BGs47s1a.js +0 -1
  219. package/src/assets/web-panel/assets/Tasks-CEYFJwmr.js +0 -1
  220. package/src/assets/web-panel/assets/devWarning-DEMblAIm.js +0 -1
  221. package/src/assets/web-panel/assets/index-BTvq-36D.js +0 -1
  222. package/src/assets/web-panel/assets/index-hUpvk6Jl.js +0 -1
@@ -5,7 +5,8 @@
5
5
  * sandbox. Every process sharing CHAINLESSCHAIN_HOME observes the same strict
6
6
  * state file. A monotonically increasing fencing token prevents an expired or
7
7
  * crashed host from publishing through guarded session-store write paths after
8
- * a successor takes over.
8
+ * a successor takes over. A separate monotonic revocation epoch lets an
9
+ * operator fence a still-live host; every guarded transition re-reads it.
9
10
  */
10
11
 
11
12
  import fs from "node:fs";
@@ -22,13 +23,19 @@ export const SESSION_HOST_LEASE_HELD_CODE = "CC_SESSION_HOST_LEASE_HELD";
22
23
  export const SESSION_HOST_LEASE_FENCED_CODE = "CC_SESSION_HOST_LEASE_FENCED";
23
24
  export const SESSION_HOST_LEASE_UNAVAILABLE_CODE =
24
25
  "CC_SESSION_HOST_LEASE_UNAVAILABLE";
26
+ export const SESSION_HOST_REVOCATION_CONFLICT_CODE =
27
+ "CC_SESSION_HOST_REVOCATION_CONFLICT";
25
28
 
26
- const STORE_VERSION = 1;
29
+ const STORE_VERSION = 2;
30
+ const LEGACY_STORE_VERSION = 1;
27
31
  const DEFAULT_TTL_MS = 30_000;
28
32
  const DEFAULT_HEARTBEAT_MS = 5_000;
29
33
  const MAX_SESSION_ID_BYTES = 1024;
34
+ const MAX_REVOCATIONS = 1024;
30
35
  const HOST_KIND_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
31
36
  const LEASE_ID_PATTERN = /^lease-[0-9a-f-]{36}$/;
37
+ const REVOCATION_REQUEST_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,255}$/;
38
+ const REVOCATION_REASON_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
32
39
 
33
40
  function leaseError(code, message, details = {}, cause = null) {
34
41
  const error = new Error(message, cause ? { cause } : undefined);
@@ -58,6 +65,21 @@ function canonicalHostKind(value) {
58
65
  return kind;
59
66
  }
60
67
 
68
+ function canonicalRevocationRequestId(value) {
69
+ if (typeof value !== "string" || !REVOCATION_REQUEST_ID_PATTERN.test(value)) {
70
+ throw new TypeError("session host revocation requestId is invalid");
71
+ }
72
+ return value;
73
+ }
74
+
75
+ function canonicalRevocationReason(value) {
76
+ const reason = String(value || "operator").toLowerCase();
77
+ if (!REVOCATION_REASON_PATTERN.test(reason)) {
78
+ throw new TypeError("session host revocation reasonCode is invalid");
79
+ }
80
+ return reason;
81
+ }
82
+
61
83
  function positiveInteger(value, label) {
62
84
  const number = Number(value);
63
85
  if (!Number.isSafeInteger(number) || number < 1) {
@@ -66,7 +88,7 @@ function positiveInteger(value, label) {
66
88
  return number;
67
89
  }
68
90
 
69
- function normalizeActiveLease(value) {
91
+ function normalizeActiveLease(value, fallbackRevocationEpoch = null) {
70
92
  if (value === null) return null;
71
93
  if (!value || typeof value !== "object" || Array.isArray(value)) {
72
94
  throw new TypeError("active session host lease must be an object or null");
@@ -76,6 +98,8 @@ function normalizeActiveLease(value) {
76
98
  !LEASE_ID_PATTERN.test(value.leaseId) ||
77
99
  !Number.isSafeInteger(value.fencingToken) ||
78
100
  value.fencingToken < 1 ||
101
+ !Number.isSafeInteger(value.revocationEpoch ?? fallbackRevocationEpoch) ||
102
+ (value.revocationEpoch ?? fallbackRevocationEpoch) < 0 ||
79
103
  !Number.isSafeInteger(value.ownerPid) ||
80
104
  value.ownerPid < 1 ||
81
105
  typeof value.hostKind !== "string" ||
@@ -92,6 +116,7 @@ function normalizeActiveLease(value) {
92
116
  return Object.freeze({
93
117
  leaseId: value.leaseId,
94
118
  fencingToken: value.fencingToken,
119
+ revocationEpoch: value.revocationEpoch ?? fallbackRevocationEpoch,
95
120
  ownerPid: value.ownerPid,
96
121
  hostKind: value.hostKind,
97
122
  acquiredAtMs: value.acquiredAtMs,
@@ -100,31 +125,92 @@ function normalizeActiveLease(value) {
100
125
  });
101
126
  }
102
127
 
128
+ function normalizeRevocation(value, expectedEpoch) {
129
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
130
+ throw new TypeError("session host revocation must be an object");
131
+ }
132
+ const targetIsNull = value.targetLeaseId === null;
133
+ if (
134
+ canonicalRevocationRequestId(value.requestId) !== value.requestId ||
135
+ canonicalRevocationReason(value.reasonCode) !== value.reasonCode ||
136
+ !Number.isSafeInteger(value.revocationEpoch) ||
137
+ value.revocationEpoch !== expectedEpoch ||
138
+ !Number.isSafeInteger(value.revokedAtMs) ||
139
+ value.revokedAtMs < 0 ||
140
+ (targetIsNull
141
+ ? value.targetFencingToken !== null || value.targetOwnerPid !== null
142
+ : !LEASE_ID_PATTERN.test(String(value.targetLeaseId || "")) ||
143
+ !Number.isSafeInteger(value.targetFencingToken) ||
144
+ value.targetFencingToken < 1 ||
145
+ !Number.isSafeInteger(value.targetOwnerPid) ||
146
+ value.targetOwnerPid < 1)
147
+ ) {
148
+ throw new TypeError("session host revocation is invalid");
149
+ }
150
+ return Object.freeze({
151
+ requestId: value.requestId,
152
+ revocationEpoch: value.revocationEpoch,
153
+ reasonCode: value.reasonCode,
154
+ revokedAtMs: value.revokedAtMs,
155
+ targetLeaseId: value.targetLeaseId,
156
+ targetFencingToken: value.targetFencingToken,
157
+ targetOwnerPid: value.targetOwnerPid,
158
+ });
159
+ }
160
+
103
161
  function validateStore(value, sessionId, { missing = false } = {}) {
104
162
  if (missing && Object.keys(value).length === 0) {
105
163
  return Object.freeze({
106
164
  version: STORE_VERSION,
107
165
  sessionId,
108
166
  lastFencingToken: 0,
167
+ revocationEpoch: 0,
168
+ revocations: Object.freeze([]),
109
169
  active: null,
110
170
  });
111
171
  }
172
+ const legacy = value?.version === LEGACY_STORE_VERSION;
112
173
  if (
113
- value?.version !== STORE_VERSION ||
174
+ (!legacy && value?.version !== STORE_VERSION) ||
114
175
  value?.sessionId !== sessionId ||
115
176
  !Number.isSafeInteger(value?.lastFencingToken) ||
116
177
  value.lastFencingToken < 0
117
178
  ) {
118
179
  throw new TypeError("session host lease store is invalid");
119
180
  }
120
- const active = normalizeActiveLease(value.active);
181
+ const revocationEpoch = legacy ? 0 : value.revocationEpoch;
182
+ const rawRevocations = legacy ? [] : value.revocations;
183
+ if (
184
+ !Number.isSafeInteger(revocationEpoch) ||
185
+ revocationEpoch < 0 ||
186
+ !Array.isArray(rawRevocations) ||
187
+ rawRevocations.length > MAX_REVOCATIONS ||
188
+ rawRevocations.length !== revocationEpoch
189
+ ) {
190
+ throw new TypeError("session host revocation authority is invalid");
191
+ }
192
+ const revocations = rawRevocations.map((record, index) =>
193
+ normalizeRevocation(record, index + 1),
194
+ );
195
+ if (
196
+ new Set(revocations.map((record) => record.requestId)).size !==
197
+ revocations.length
198
+ ) {
199
+ throw new TypeError("session host revocation requestIds must be unique");
200
+ }
201
+ const active = normalizeActiveLease(value.active, legacy ? 0 : null);
121
202
  if (active && active.fencingToken !== value.lastFencingToken) {
122
203
  throw new TypeError("active lease does not match the last fencing token");
123
204
  }
205
+ if (active && active.revocationEpoch !== revocationEpoch) {
206
+ throw new TypeError("active lease predates the revocation authority");
207
+ }
124
208
  return Object.freeze({
125
209
  version: STORE_VERSION,
126
210
  sessionId,
127
211
  lastFencingToken: value.lastFencingToken,
212
+ revocationEpoch,
213
+ revocations: Object.freeze(revocations),
128
214
  active,
129
215
  });
130
216
  }
@@ -152,6 +238,10 @@ function publicLease(active) {
152
238
  return Object.freeze({ ...active });
153
239
  }
154
240
 
241
+ function publicRevocation(record, replayed) {
242
+ return Object.freeze({ ...record, replayed });
243
+ }
244
+
155
245
  export function getSessionHostLeaseRoot() {
156
246
  return path.join(getStatePath(), "session-host-leases");
157
247
  }
@@ -177,6 +267,7 @@ export class SessionHostLeaseAuthority {
177
267
  this._clearInterval = clearIntervalFn;
178
268
  this._lock = lock;
179
269
  this._local = new Map();
270
+ this._fenced = new Map();
180
271
  }
181
272
 
182
273
  pathFor(sessionId) {
@@ -201,16 +292,20 @@ export class SessionHostLeaseAuthority {
201
292
  _unavailable(sessionId, filePath, cause) {
202
293
  if (
203
294
  cause?.code === SESSION_HOST_LEASE_HELD_CODE ||
204
- cause?.code === SESSION_HOST_LEASE_FENCED_CODE
295
+ cause?.code === SESSION_HOST_LEASE_FENCED_CODE ||
296
+ cause?.code === SESSION_HOST_LEASE_UNAVAILABLE_CODE ||
297
+ cause?.code === SESSION_HOST_REVOCATION_CONFLICT_CODE
205
298
  ) {
206
299
  return cause;
207
300
  }
208
- return leaseError(
301
+ const error = leaseError(
209
302
  SESSION_HOST_LEASE_UNAVAILABLE_CODE,
210
303
  `Session host lease authority is unavailable for ${sessionId}`,
211
304
  { sessionId, filePath },
212
305
  cause,
213
306
  );
307
+ if (cause?.commitState) error.commitState = cause.commitState;
308
+ return error;
214
309
  }
215
310
 
216
311
  _lose(record, cause) {
@@ -223,9 +318,18 @@ export class SessionHostLeaseAuthority {
223
318
  cause?.code === SESSION_HOST_LEASE_FENCED_CODE
224
319
  ? cause
225
320
  : this._unavailable(record.sessionId, record.filePath, cause);
321
+ this._fenced.set(record.key, reason);
226
322
  if (!record.controller.signal.aborted) record.controller.abort(reason);
227
323
  }
228
324
 
325
+ _writeSnapshot(draft, snapshot) {
326
+ draft.version = STORE_VERSION;
327
+ draft.sessionId = snapshot.sessionId;
328
+ draft.lastFencingToken = snapshot.lastFencingToken;
329
+ draft.revocationEpoch = snapshot.revocationEpoch;
330
+ draft.revocations = snapshot.revocations.map((record) => ({ ...record }));
331
+ }
332
+
229
333
  acquire(
230
334
  sessionId,
231
335
  {
@@ -243,6 +347,8 @@ export class SessionHostLeaseAuthority {
243
347
  );
244
348
  const filePath = this.pathFor(id);
245
349
  const key = this._key(id);
350
+ const priorFence = this._fenced.get(key);
351
+ if (priorFence) throw priorFence;
246
352
  if (this._local.has(key)) {
247
353
  throw leaseError(
248
354
  SESSION_HOST_LEASE_HELD_CODE,
@@ -282,6 +388,7 @@ export class SessionHostLeaseAuthority {
282
388
  const next = {
283
389
  leaseId: this._createLeaseId(),
284
390
  fencingToken,
391
+ revocationEpoch: snapshot.revocationEpoch,
285
392
  ownerPid: process.pid,
286
393
  hostKind: kind,
287
394
  acquiredAtMs: now,
@@ -289,8 +396,7 @@ export class SessionHostLeaseAuthority {
289
396
  expiresAtMs: now + ttl,
290
397
  };
291
398
  normalizeActiveLease(next);
292
- draft.version = STORE_VERSION;
293
- draft.sessionId = id;
399
+ this._writeSnapshot(draft, snapshot);
294
400
  draft.lastFencingToken = fencingToken;
295
401
  draft.active = next;
296
402
  return next;
@@ -329,7 +435,10 @@ export class SessionHostLeaseAuthority {
329
435
  "Session host lease authority",
330
436
  (draft) => {
331
437
  const snapshot = validateStore(draft, id);
332
- if (!sameLease(snapshot.active, record.active)) {
438
+ if (
439
+ snapshot.revocationEpoch !== record.active.revocationEpoch ||
440
+ !sameLease(snapshot.active, record.active)
441
+ ) {
333
442
  throw leaseError(
334
443
  SESSION_HOST_LEASE_FENCED_CODE,
335
444
  `Session host lease was superseded for ${id}`,
@@ -337,6 +446,8 @@ export class SessionHostLeaseAuthority {
337
446
  sessionId: id,
338
447
  fencingToken: record.active.fencingToken,
339
448
  activeFencingToken: snapshot.active?.fencingToken ?? null,
449
+ localRevocationEpoch: record.active.revocationEpoch,
450
+ activeRevocationEpoch: snapshot.revocationEpoch,
340
451
  },
341
452
  );
342
453
  }
@@ -353,6 +464,7 @@ export class SessionHostLeaseAuthority {
353
464
  renewedAtMs: now,
354
465
  expiresAtMs: now + ttl,
355
466
  };
467
+ this._writeSnapshot(draft, snapshot);
356
468
  draft.active = next;
357
469
  return next;
358
470
  },
@@ -371,19 +483,27 @@ export class SessionHostLeaseAuthority {
371
483
  if (record.released) return false;
372
484
  if (record.timer) this._clearInterval(record.timer);
373
485
  record.timer = null;
374
- let released = false;
486
+ let outcome = Object.freeze({ released: false, fenced: true });
375
487
  try {
376
- released = mutateSecurityStore(
488
+ outcome = mutateSecurityStore(
377
489
  filePath,
378
490
  "Session host lease authority",
379
491
  (draft) => {
380
492
  const snapshot = validateStore(draft, id);
381
- if (!sameLease(snapshot.active, record.active)) return false;
382
- draft.version = STORE_VERSION;
383
- draft.sessionId = id;
384
- draft.lastFencingToken = snapshot.lastFencingToken;
493
+ if (
494
+ snapshot.revocationEpoch !== record.active.revocationEpoch ||
495
+ !sameLease(snapshot.active, record.active)
496
+ ) {
497
+ return Object.freeze({
498
+ released: false,
499
+ fenced: true,
500
+ activeFencingToken: snapshot.active?.fencingToken ?? null,
501
+ activeRevocationEpoch: snapshot.revocationEpoch,
502
+ });
503
+ }
504
+ this._writeSnapshot(draft, snapshot);
385
505
  draft.active = null;
386
- return true;
506
+ return Object.freeze({ released: true, fenced: false });
387
507
  },
388
508
  { lock: this._lock, timeoutMs: 30_000, staleMs: 30_000 },
389
509
  );
@@ -392,9 +512,26 @@ export class SessionHostLeaseAuthority {
392
512
  this._lose(record, error);
393
513
  throw error;
394
514
  }
515
+ if (!outcome.released) {
516
+ this._lose(
517
+ record,
518
+ leaseError(
519
+ SESSION_HOST_LEASE_FENCED_CODE,
520
+ `Session host lease cannot be released after losing authority for ${id}`,
521
+ {
522
+ sessionId: id,
523
+ fencingToken: record.active.fencingToken,
524
+ activeFencingToken: outcome.activeFencingToken ?? null,
525
+ localRevocationEpoch: record.active.revocationEpoch,
526
+ activeRevocationEpoch: outcome.activeRevocationEpoch ?? null,
527
+ },
528
+ ),
529
+ );
530
+ return false;
531
+ }
395
532
  record.released = true;
396
533
  this._local.delete(key);
397
- return released;
534
+ return true;
398
535
  };
399
536
 
400
537
  record.timer = this._setInterval(() => {
@@ -406,54 +543,281 @@ export class SessionHostLeaseAuthority {
406
543
  }, heartbeat);
407
544
  record.timer?.unref?.();
408
545
 
546
+ const admitMcpDispatch = (_metadata, dispatch) => {
547
+ if (typeof dispatch !== "function") {
548
+ throw new TypeError("session host MCP dispatch callback is required");
549
+ }
550
+ return this.withWriteAuthority(id, (authority) => {
551
+ // A lease-owned dispatch must never inherit the legacy no-lease write
552
+ // authority represented by null. Invoke the actual transport send
553
+ // while the authority lock is held, then return its response promise
554
+ // without awaiting it so the lock covers dispatch only.
555
+ if (!authority) {
556
+ throw leaseError(
557
+ SESSION_HOST_LEASE_FENCED_CODE,
558
+ `Session host MCP dispatch is fenced for ${id}`,
559
+ { sessionId: id, fencingToken: record.active.fencingToken },
560
+ );
561
+ }
562
+ return dispatch();
563
+ });
564
+ };
565
+
409
566
  return Object.freeze({
410
567
  sessionId: id,
411
568
  leaseId: record.active.leaseId,
412
569
  fencingToken: record.active.fencingToken,
570
+ revocationEpoch: record.active.revocationEpoch,
413
571
  signal: controller.signal,
414
572
  renew,
415
573
  assert: () => this.withWriteAuthority(id, (authority) => authority),
574
+ admitMcpDispatch,
416
575
  release,
417
576
  });
418
577
  }
419
578
 
579
+ readAuthority(sessionId) {
580
+ const id = canonicalSessionId(sessionId);
581
+ const filePath = this.pathFor(id);
582
+ if (!fs.existsSync(filePath)) {
583
+ return Object.freeze({
584
+ version: STORE_VERSION,
585
+ sessionId: id,
586
+ lastFencingToken: 0,
587
+ revocationEpoch: 0,
588
+ revocationCount: 0,
589
+ latestRevocation: null,
590
+ active: null,
591
+ });
592
+ }
593
+ try {
594
+ return this._lock(
595
+ filePath,
596
+ () => {
597
+ const snapshot = this._read(filePath, id);
598
+ return Object.freeze({
599
+ version: STORE_VERSION,
600
+ sessionId: id,
601
+ lastFencingToken: snapshot.lastFencingToken,
602
+ revocationEpoch: snapshot.revocationEpoch,
603
+ revocationCount: snapshot.revocations.length,
604
+ latestRevocation:
605
+ snapshot.revocations.length > 0
606
+ ? publicRevocation(snapshot.revocations.at(-1), false)
607
+ : null,
608
+ active: snapshot.active ? publicLease(snapshot.active) : null,
609
+ });
610
+ },
611
+ {
612
+ timeoutMs: 30_000,
613
+ staleMs: 30_000,
614
+ retryMs: 1,
615
+ maxRetryMs: 8,
616
+ retryJitterMs: 4,
617
+ failIfUnavailable: true,
618
+ },
619
+ );
620
+ } catch (cause) {
621
+ throw this._unavailable(id, filePath, cause);
622
+ }
623
+ }
624
+
625
+ /**
626
+ * Durably revoke the current host generation. The request id is a permanent
627
+ * idempotency key: an exact replay returns the original record and never
628
+ * revokes a successor. The bounded journal fails closed at capacity rather
629
+ * than evicting replay authority.
630
+ */
631
+ revoke(sessionId, { requestId, reasonCode = "operator" } = {}) {
632
+ const id = canonicalSessionId(sessionId);
633
+ const canonicalRequestId = canonicalRevocationRequestId(requestId);
634
+ const canonicalReasonCode = canonicalRevocationReason(reasonCode);
635
+ const filePath = this.pathFor(id);
636
+ const key = this._key(id);
637
+ let result;
638
+ try {
639
+ result = mutateSecurityStore(
640
+ filePath,
641
+ "Session host lease authority",
642
+ (draft) => {
643
+ const snapshot = validateStore(draft, id, {
644
+ missing: !fs.existsSync(filePath),
645
+ });
646
+ const replay = snapshot.revocations.find(
647
+ (record) => record.requestId === canonicalRequestId,
648
+ );
649
+ if (replay) {
650
+ if (replay.reasonCode !== canonicalReasonCode) {
651
+ throw leaseError(
652
+ SESSION_HOST_REVOCATION_CONFLICT_CODE,
653
+ `Session host revocation requestId was reused with different authority: ${canonicalRequestId}`,
654
+ { sessionId: id, requestId: canonicalRequestId },
655
+ );
656
+ }
657
+ return publicRevocation(replay, true);
658
+ }
659
+ if (snapshot.revocations.length >= MAX_REVOCATIONS) {
660
+ throw new RangeError(
661
+ "session host revocation journal is full and requires explicit operator repair",
662
+ );
663
+ }
664
+ const revocationEpoch = snapshot.revocationEpoch + 1;
665
+ if (!Number.isSafeInteger(revocationEpoch)) {
666
+ throw new RangeError("session host revocation epoch is exhausted");
667
+ }
668
+ const active = snapshot.active;
669
+ const record = normalizeRevocation(
670
+ {
671
+ requestId: canonicalRequestId,
672
+ revocationEpoch,
673
+ reasonCode: canonicalReasonCode,
674
+ revokedAtMs: Math.trunc(this._now()),
675
+ targetLeaseId: active?.leaseId ?? null,
676
+ targetFencingToken: active?.fencingToken ?? null,
677
+ targetOwnerPid: active?.ownerPid ?? null,
678
+ },
679
+ revocationEpoch,
680
+ );
681
+ this._writeSnapshot(draft, snapshot);
682
+ draft.revocationEpoch = revocationEpoch;
683
+ draft.revocations = [
684
+ ...snapshot.revocations.map((entry) => ({ ...entry })),
685
+ { ...record },
686
+ ];
687
+ draft.active = null;
688
+ return publicRevocation(record, false);
689
+ },
690
+ { lock: this._lock, timeoutMs: 30_000, staleMs: 30_000 },
691
+ );
692
+ } catch (cause) {
693
+ const error = this._unavailable(id, filePath, cause);
694
+ if (["committed", "unknown"].includes(error.commitState)) {
695
+ try {
696
+ const latest = this.readAuthority(id).latestRevocation;
697
+ if (
698
+ latest?.requestId === canonicalRequestId &&
699
+ latest?.reasonCode === canonicalReasonCode
700
+ ) {
701
+ result = publicRevocation(latest, true);
702
+ }
703
+ } catch {
704
+ // The original classified write failure remains the authority when
705
+ // exact readback cannot prove this request's durable record.
706
+ }
707
+ }
708
+ if (!result) {
709
+ const local = this._local.get(key);
710
+ if (local) this._lose(local, error);
711
+ throw error;
712
+ }
713
+ }
714
+
715
+ const local = this._local.get(key);
716
+ if (
717
+ local &&
718
+ local.active.revocationEpoch < result.revocationEpoch &&
719
+ (!result.targetLeaseId || result.targetLeaseId === local.active.leaseId)
720
+ ) {
721
+ this._lose(
722
+ local,
723
+ leaseError(
724
+ SESSION_HOST_LEASE_FENCED_CODE,
725
+ `Session host authority was durably revoked for ${id}`,
726
+ {
727
+ sessionId: id,
728
+ requestId: result.requestId,
729
+ fencingToken: local.active.fencingToken,
730
+ localRevocationEpoch: local.active.revocationEpoch,
731
+ activeRevocationEpoch: result.revocationEpoch,
732
+ revoked: true,
733
+ },
734
+ ),
735
+ );
736
+ }
737
+ return result;
738
+ }
739
+
420
740
  withWriteAuthority(sessionId, task) {
421
741
  if (typeof task !== "function") {
422
742
  throw new TypeError("session host write authority callback is required");
423
743
  }
424
744
  const id = canonicalSessionId(sessionId);
425
745
  const filePath = this.pathFor(id);
426
- if (!fs.existsSync(filePath)) return task(null);
746
+ const key = this._key(id);
747
+ let callbackError = null;
748
+ const invokeTask = (authority) => {
749
+ try {
750
+ return task(authority);
751
+ } catch (cause) {
752
+ callbackError = cause;
753
+ throw cause;
754
+ }
755
+ };
756
+ const priorFence = this._fenced.get(key);
757
+ if (priorFence) throw priorFence;
758
+ const local = this._local.get(key);
759
+ if (!fs.existsSync(filePath)) {
760
+ if (!local) return invokeTask(null);
761
+ const error = leaseError(
762
+ SESSION_HOST_LEASE_FENCED_CODE,
763
+ `Session host authority disappeared while locally held: ${id}`,
764
+ {
765
+ sessionId: id,
766
+ localFencingToken: local.active.fencingToken,
767
+ localRevocationEpoch: local.active.revocationEpoch,
768
+ },
769
+ );
770
+ this._lose(local, error);
771
+ throw error;
772
+ }
427
773
  try {
428
774
  return this._lock(
429
775
  filePath,
430
776
  () => {
431
777
  const snapshot = this._read(filePath, id);
432
- if (!snapshot.active) return task(null);
433
- const record = this._local.get(this._key(id));
778
+ const record = this._local.get(key);
434
779
  const now = Math.trunc(this._now());
435
780
  if (
436
- !record ||
437
- record.released ||
438
- !sameLease(snapshot.active, record.active) ||
439
- snapshot.active.expiresAtMs <= now
781
+ record &&
782
+ (record.released ||
783
+ record.active.revocationEpoch !== snapshot.revocationEpoch ||
784
+ !sameLease(snapshot.active, record.active) ||
785
+ snapshot.active.expiresAtMs <= now)
440
786
  ) {
441
787
  const error = leaseError(
442
788
  SESSION_HOST_LEASE_FENCED_CODE,
443
- `Session write is fenced by another or expired host lease: ${id}`,
789
+ `Session write is fenced by a revocation, successor, or expired host lease: ${id}`,
444
790
  {
445
791
  sessionId: id,
446
- activeFencingToken: snapshot.active.fencingToken,
447
- localFencingToken: record?.active?.fencingToken ?? null,
792
+ activeFencingToken: snapshot.active?.fencingToken ?? null,
793
+ localFencingToken: record.active.fencingToken,
794
+ localRevocationEpoch: record.active.revocationEpoch,
795
+ activeRevocationEpoch: snapshot.revocationEpoch,
796
+ revoked:
797
+ record.active.revocationEpoch !== snapshot.revocationEpoch,
448
798
  },
449
799
  );
450
- if (record) this._lose(record, error);
800
+ this._lose(record, error);
451
801
  throw error;
452
802
  }
453
- return task(
803
+ if (!record && snapshot.active) {
804
+ throw leaseError(
805
+ SESSION_HOST_LEASE_FENCED_CODE,
806
+ `Session write is fenced by another host lease: ${id}`,
807
+ {
808
+ sessionId: id,
809
+ activeFencingToken: snapshot.active.fencingToken,
810
+ localFencingToken: null,
811
+ activeRevocationEpoch: snapshot.revocationEpoch,
812
+ },
813
+ );
814
+ }
815
+ if (!record) return invokeTask(null);
816
+ return invokeTask(
454
817
  Object.freeze({
455
818
  leaseId: snapshot.active.leaseId,
456
819
  fencingToken: snapshot.active.fencingToken,
820
+ revocationEpoch: snapshot.active.revocationEpoch,
457
821
  ownerPid: snapshot.active.ownerPid,
458
822
  hostKind: snapshot.active.hostKind,
459
823
  }),
@@ -469,6 +833,7 @@ export class SessionHostLeaseAuthority {
469
833
  },
470
834
  );
471
835
  } catch (cause) {
836
+ if (cause === callbackError) throw cause;
472
837
  throw this._unavailable(id, filePath, cause);
473
838
  }
474
839
  }
@@ -490,3 +855,11 @@ export function assertSessionHostWriteAuthority(sessionId) {
490
855
  (authority) => authority,
491
856
  );
492
857
  }
858
+
859
+ export function readSessionHostAuthority(sessionId) {
860
+ return defaultAuthority.readAuthority(sessionId);
861
+ }
862
+
863
+ export function revokeSessionHostAuthority(sessionId, options) {
864
+ return defaultAuthority.revoke(sessionId, options);
865
+ }
@@ -2,7 +2,10 @@ export const SESSION_PERSISTENCE_FAILURE_SCHEMA =
2
2
  "chainlesschain.session-persistence-failure.v1";
3
3
  export const SESSION_PERSISTENCE_FAILURE_CODE = "CC_SESSION_PERSISTENCE_FAILED";
4
4
 
5
- const STORAGE_FAILURE_CODES = new Set(["ENOSPC", "EROFS"]);
5
+ // EIO is a common fsync/FlushFileBuffers failure. Once the append bytes have
6
+ // been issued its outcome is unknown, so it belongs in the same bounded,
7
+ // content-free persistence error domain as ENOSPC/EROFS.
8
+ const STORAGE_FAILURE_CODES = new Set(["ENOSPC", "EROFS", "EIO"]);
6
9
  const COMMIT_STATES = new Set(["not-committed", "unknown", "committed"]);
7
10
  const OPERATIONS = new Set([
8
11
  "append-event",