chainlesschain 0.166.101 → 0.166.103

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 (219) hide show
  1. package/package.json +2 -2
  2. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  3. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-BjBS_29I.js → AIOps-BLrXpdnP.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-CUnnQUaj.js → ActionButton-C0xy9uFy.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-BqYZxgnY.js → Analytics-C6EephQP.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-m8B886ka.js → AppLayout-ZCXdOD9y.js} +5 -5
  8. package/src/assets/web-panel/assets/{Artifacts-JEOxU3Ug.js → Artifacts-D4MfZ-pl.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-DAXS5c9J.js → Audit-BQ8c3bBu.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-e3a9XHw3.js → BackgroundAgents-BOA7_ljM.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-D3Qmp68h.js → Backup-60uiQjGT.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-C7CXyiUl.js → BaseInput-BDvEbGs_.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-2AHlAGi8.js → Chat-B3IkAuFQ.js} +6 -6
  14. package/src/assets/web-panel/assets/ChatBubbleRenderer-n6BVStHf.js +1 -0
  15. package/src/assets/web-panel/assets/{Checkbox-YfBuQlBH.js → Checkbox-njGbfJWq.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-DCCQDfTL.js → Codegen-CF7DtdxC.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-03i2WfrV.js → Col-C_3Zix40.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-CJQIpng6.js → Community-CzU-ZC2d.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-CCtcQ2pq.js → Compact-BgeqsPRh.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-Vxl7ZszJ.js → Compliance-6l7cwiqp.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-DN-BwA8I.js → Cowork-D4s905fI.js} +2 -2
  22. package/src/assets/web-panel/assets/{Cron-Dc2Q46X7.js → Cron-CGTYSxRS.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-CQCblsl-.js → Crosschain-BnSo-Yt3.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-YYvqUR8C.js → DID-Dm4vk2X1.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-Cafy3DZT.js → Dashboard-D7H-PZ_N.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-BOyYpIpv.js → Dropdown-COslkgxn.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-B7NYjc1j.js → EmailListRenderer-CJWEetmk.js} +1 -1
  28. package/src/assets/web-panel/assets/{EvolutionSettings-C5wfxztf.js → EvolutionSettings-BbzebAWe.js} +1 -1
  29. package/src/assets/web-panel/assets/{FamilyGuardDashboard-aA7KnDGK.js → FamilyGuardDashboard-BsoTOXwk.js} +1 -1
  30. package/src/assets/web-panel/assets/{Federation-DlgXUu1L.js → Federation-Ck5kM_5H.js} +1 -1
  31. package/src/assets/web-panel/assets/{FormItemContext-B5YsrLDg.js → FormItemContext-gShxCqtF.js} +1 -1
  32. package/src/assets/web-panel/assets/{GenericCardRenderer-DaWojPFw.js → GenericCardRenderer-DarN1G-z.js} +1 -1
  33. package/src/assets/web-panel/assets/{Git-dkhh6Fx2.js → Git-CJfpLyP5.js} +2 -2
  34. package/src/assets/web-panel/assets/{Governance-Cz6vmkjc.js → Governance-CVcJmpQz.js} +1 -1
  35. package/src/assets/web-panel/assets/{Inference-DWt0hbh8.js → Inference-ComU_ig8.js} +1 -1
  36. package/src/assets/web-panel/assets/{KnowledgeGraph-BZJ0Z-nL.js → KnowledgeGraph-35hprFJy.js} +1 -1
  37. package/src/assets/web-panel/assets/{Logs-BaBBmL1A.js → Logs-BQDqBTz0.js} +2 -2
  38. package/src/assets/web-panel/assets/MarkdownRenderer-BpdGAVCC.js +1 -0
  39. package/src/assets/web-panel/assets/{Marketplace-ct8sgQ0s.js → Marketplace-B9O9OKnn.js} +1 -1
  40. package/src/assets/web-panel/assets/{McpTools-CXKCRGYQ.js → McpTools-VMKxA4Wc.js} +3 -3
  41. package/src/assets/web-panel/assets/{Memory-O7ZwlS6a.js → Memory-CcBbf3Bc.js} +2 -2
  42. package/src/assets/web-panel/assets/{MobileBridge-By12C-eC.js → MobileBridge-D7myz46N.js} +1 -1
  43. package/src/assets/web-panel/assets/{MobileProjects-Ba-EVdex.js → MobileProjects-Da5KBXyx.js} +1 -1
  44. package/src/assets/web-panel/assets/{Mtc-g-932zV2.js → Mtc-x3NPZfuL.js} +5 -5
  45. package/src/assets/web-panel/assets/{MtcAudit-BW8hB0ly.js → MtcAudit-CwnzptEC.js} +4 -4
  46. package/src/assets/web-panel/assets/{Multisig-zspLaMbT.js → Multisig-CCAxf4Ye.js} +3 -3
  47. package/src/assets/web-panel/assets/{NLProgramming-dJeUPDoq.js → NLProgramming-CptHKz-Q.js} +1 -1
  48. package/src/assets/web-panel/assets/{Notes-C4msiRQL.js → Notes-0uEp4DCJ.js} +3 -3
  49. package/src/assets/web-panel/assets/{NotificationSettings-MOCHM-qg.js → NotificationSettings-DthvrOLd.js} +1 -1
  50. package/src/assets/web-panel/assets/{OrderTableRenderer-CX0T66NM.js → OrderTableRenderer-CxXuPzZg.js} +1 -1
  51. package/src/assets/web-panel/assets/{Organization-DOZurBfk.js → Organization-BWf2tmQQ.js} +4 -4
  52. package/src/assets/web-panel/assets/{Overflow-qOT9BhUp.js → Overflow-BbvAtEth.js} +1 -1
  53. package/src/assets/web-panel/assets/{P2P-BMoy7Eo4.js → P2P-zBo6enit.js} +2 -2
  54. package/src/assets/web-panel/assets/{PdhVaultBrowser-ByyV1q-D.js → PdhVaultBrowser-D_LMQ4A4.js} +3 -3
  55. package/src/assets/web-panel/assets/{Permissions-6Qyro4ye.js → Permissions-C1ucb2ck.js} +4 -4
  56. package/src/assets/web-panel/assets/{PersonalDataHub-DR3DRNSX.js → PersonalDataHub-Bz64RddJ.js} +5 -5
  57. package/src/assets/web-panel/assets/{Pipeline-Cr6TTdud.js → Pipeline-DXJt3y3_.js} +1 -1
  58. package/src/assets/web-panel/assets/{Privacy-B70otG0l.js → Privacy-DiZ8b_ut.js} +1 -1
  59. package/src/assets/web-panel/assets/{ProjectInit-BHCqxoSk.js → ProjectInit-DfLKKrJ4.js} +2 -2
  60. package/src/assets/web-panel/assets/{ProjectSettings-B80GZpZk.js → ProjectSettings-Ct6kJuIY.js} +2 -2
  61. package/src/assets/web-panel/assets/{Projects-cq-b9-fQ.js → Projects-BBUMlZ3m.js} +1 -1
  62. package/src/assets/web-panel/assets/{Providers-dsVTimHa.js → Providers-DYVQPHvc.js} +1 -1
  63. package/src/assets/web-panel/assets/{QrScannerModal-DRJ8YepZ.js → QrScannerModal-23Il7iPp.js} +1 -1
  64. package/src/assets/web-panel/assets/{QuickAsk-BSXdEjIB.js → QuickAsk-WgOZKrv1.js} +1 -1
  65. package/src/assets/web-panel/assets/{Recommend-CQLJGyKC.js → Recommend-CRIka6xT.js} +1 -1
  66. package/src/assets/web-panel/assets/{RemoteSession-BF9WUFVZ.js → RemoteSession-DOy3GjVZ.js} +2 -2
  67. package/src/assets/web-panel/assets/{Reputation-Cy9o5tvW.js → Reputation-BXyi6xeR.js} +1 -1
  68. package/src/assets/web-panel/assets/{Row-BP346_3b.js → Row-CoMxuIcR.js} +1 -1
  69. package/src/assets/web-panel/assets/{RssFeed-BKulxXf3.js → RssFeed-87hPkoM6.js} +3 -3
  70. package/src/assets/web-panel/assets/{Search-EemQj2Dv.js → Search-B1EqFCO4.js} +1 -1
  71. package/src/assets/web-panel/assets/{Security-DcWPAwwk.js → Security-HcC41eGs.js} +4 -4
  72. package/src/assets/web-panel/assets/{Services-Bltqciay.js → Services-BqLwmvCM.js} +2 -2
  73. package/src/assets/web-panel/assets/{Skeleton-BwRyOGY8.js → Skeleton-CIMmjW4r.js} +1 -1
  74. package/src/assets/web-panel/assets/{Skills-D98qlvpJ.js → Skills-CgurezJU.js} +1 -1
  75. package/src/assets/web-panel/assets/{Sla-BUTtBMY0.js → Sla-CJuRuqPE.js} +1 -1
  76. package/src/assets/web-panel/assets/{SpeechSettings-C7kcY_98.js → SpeechSettings-BUwsbd1o.js} +1 -1
  77. package/src/assets/web-panel/assets/{SyncSettings-Bsk6lKvT.js → SyncSettings-qo04TOX-.js} +2 -2
  78. package/src/assets/web-panel/assets/Tasks-QEFENVsp.js +1 -0
  79. package/src/assets/web-panel/assets/{Templates-BUM6LwPs.js → Templates-D1PTczPW.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tenant-D9TkOm7z.js → Tenant-Bi0FkEPR.js} +1 -1
  81. package/src/assets/web-panel/assets/{Terminal-CP8ZVErN.js → Terminal-DfSt3bIF.js} +2 -2
  82. package/src/assets/web-panel/assets/{TimelineRenderer-V3SoKZ1T.js → TimelineRenderer-B7bIVs4R.js} +1 -1
  83. package/src/assets/web-panel/assets/{Tokens-CCRlUTOA.js → Tokens-B6w5XrMb.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trigger-CznUcPme.js → Trigger-XXVSDWiF.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trust-D1GIk_NF.js → Trust-DYvlgcK-.js} +1 -1
  86. package/src/assets/web-panel/assets/{UkeySign-DHtvtyC-.js → UkeySign-XyYwJMBM.js} +1 -1
  87. package/src/assets/web-panel/assets/{VideoEditing-CVxdaJIq.js → VideoEditing-C47WbtHH.js} +1 -1
  88. package/src/assets/web-panel/assets/{Wallet-D221OjyE.js → Wallet-CYfqTj_C.js} +4 -4
  89. package/src/assets/web-panel/assets/{WebAuthn-Cw0-ESsq.js → WebAuthn-3AduFn1l.js} +3 -3
  90. package/src/assets/web-panel/assets/{WorkflowEditor-ZYScrBx3.js → WorkflowEditor-DHYEHjMJ.js} +1 -1
  91. package/src/assets/web-panel/assets/{chat-BU0e1obR.js → chat-AeehErSw.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-gzYweuha.js → colors-DKXJ8paX.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-ChAXal_k.js → compact-item-BOlCiomD.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-of3FolD7.js → createContext-BAP6U85b.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-rGldFhxo.js +1 -0
  96. package/src/assets/web-panel/assets/{hasIn-4PsK_Kx8.js → hasIn-BLmeL1Xe.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-Cn1clrxx.js → index-B0IjlB9m.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BOeX7BzS.js → index-BAS2QqLn.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-Cx4Z6nUl.js → index-BRcJlKSa.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DjRAIRX7.js → index-BSOenF5s.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Djfgsjfy.js → index-BTnp4Nt3.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BfI4dXbi.js → index-BhcpWl5z.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-D_5LTPMH.js → index-Bn54FU3i.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CyDQNprx.js → index-Bpx54vfs.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DEXBckNR.js → index-Bs2O3f81.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Ef5YkVAn.js → index-Bt-FCVzh.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-E8oDMSAo.js → index-BuUwKmEI.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BdxRVNPY.js → index-C5AKY6bJ.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Wr91cnWr.js → index-C8qi53Ki.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-B5L7zddz.js → index-CFKzWIo8.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Dy0ojH6G.js → index-CRa4pAaM.js} +3 -3
  112. package/src/assets/web-panel/assets/{index-wo0De0iG.js → index-CaHhfpBi.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-D-Zss5KL.js → index-CdNSmvog.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DEzjUgkl.js → index-CgtaOzMo.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DDYj14Q4.js → index-CiqeF0Pf.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-D_9k680X.js → index-ClpwxOXo.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-D1PGrfHE.js → index-Co7pjF53.js} +1 -1
  118. package/src/assets/web-panel/assets/index-Cw-lfvvV.js +1 -0
  119. package/src/assets/web-panel/assets/{index-CeEE_SGZ.js → index-DE7cbDDe.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CxzBcXOa.js → index-DQLQuwpA.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DEB5fQqG.js → index-DWYqOI1A.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CO2owKe0.js → index-De2fPxdb.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-1dzYRBbg.js → index-DpZqbc4w.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CGFL59q4.js → index-DsQ38fVY.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DsoV9rzn.js → index-Dv8-g1pY.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-yMf7zmWv.js → index-EKNODw4P.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CHv_63Cp.js → index-FPXLIXoy.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CCLsbul3.js → index-IlzfIRNh.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-cFkMIx1x.js → index-OLKhib9T.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Bftq5rJR.js → index-PEe9OWEQ.js} +1 -1
  131. package/src/assets/web-panel/assets/index-QBXRSyoZ.js +1 -0
  132. package/src/assets/web-panel/assets/{index-DJVrzXtk.js → index-T28naovw.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-By-pper5.js → index-ZMbXl5lo.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-DZ0oKtBg.js → index-urPvyUVk.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-D_ialOHx.js → index-wyKpTsGM.js} +1 -1
  136. package/src/assets/web-panel/assets/{initDefaultProps-D0Bx6pgr.js → initDefaultProps-CDWa1rnn.js} +1 -1
  137. package/src/assets/web-panel/assets/{motion-BnLuJqJ5.js → motion-CX2Cnsd5.js} +1 -1
  138. package/src/assets/web-panel/assets/{move-Bxpq9xgR.js → move-Bfe1NcEF.js} +1 -1
  139. package/src/assets/web-panel/assets/{mtc-parser-BdmrNhOs.js → mtc-parser-DbdqleHE.js} +1 -1
  140. package/src/assets/web-panel/assets/{omit-tjJD34BZ.js → omit-CE2K7NVv.js} +1 -1
  141. package/src/assets/web-panel/assets/{pickAttrs-CMFzLt7b.js → pickAttrs-y_bZgxWG.js} +1 -1
  142. package/src/assets/web-panel/assets/{placementArrow-BZf6O3cd.js → placementArrow-BOHR4zwr.js} +1 -1
  143. package/src/assets/web-panel/assets/{responsiveObserve-DFDLazsV.js → responsiveObserve-B8Jwq05-.js} +1 -1
  144. package/src/assets/web-panel/assets/{slide-CMbTvp4Y.js → slide-BD7RJ3Kn.js} +1 -1
  145. package/src/assets/web-panel/assets/{statusUtils-FOan8MI7.js → statusUtils-BuxDJeDx.js} +1 -1
  146. package/src/assets/web-panel/assets/{styleChecker-DTABspd4.js → styleChecker-DgYQ2Q0o.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFlexGapSupport-CNHXWBpY.js → useFlexGapSupport-DAHF0Fwi.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFs-5K-c_RoL.js → useFs-D4kDyLFp.js} +1 -1
  149. package/src/assets/web-panel/assets/{usePersonalDataHub-Di0hL4G-.js → usePersonalDataHub-BHtSxW4A.js} +1 -1
  150. package/src/assets/web-panel/assets/{vnode-DTyDv4ja.js → vnode-CHKtdqW0.js} +1 -1
  151. package/src/assets/web-panel/assets/{zoom-ontRt8ft.js → zoom-CIZAMma9.js} +1 -1
  152. package/src/assets/web-panel/index.html +1 -1
  153. package/src/command-help-index.json +1 -1
  154. package/src/commands/agent.js +64 -40
  155. package/src/commands/ask.js +6 -4
  156. package/src/commands/team.js +28 -19
  157. package/src/data/changelog.json +26 -0
  158. package/src/harness/prompt-compressor.js +2 -0
  159. package/src/lib/advisor-runtime.js +4 -2
  160. package/src/lib/agent-ipc-bus.js +2 -0
  161. package/src/lib/background-agent-keeper-client.js +3 -0
  162. package/src/lib/browser-automation.js +4 -0
  163. package/src/lib/cc-android-bridge.js +4 -0
  164. package/src/lib/chat-core.js +11 -9
  165. package/src/lib/cloud/cloud-client.js +10 -1
  166. package/src/lib/config-schema.js +18 -5
  167. package/src/lib/context-memory-kernel/memory-query-index.js +5 -2
  168. package/src/lib/context-memory-kernel/runtime.js +4 -0
  169. package/src/lib/context-memory-kernel/segmented-memory-port.js +28 -3
  170. package/src/lib/context-source-ledger.js +52 -9
  171. package/src/lib/cowork-adapter.js +4 -3
  172. package/src/lib/decision-layer/providers.js +16 -11
  173. package/src/lib/evolution/governed-hub-llm.js +31 -16
  174. package/src/lib/evolution/governed-hub-resolver.js +4 -0
  175. package/src/lib/evolution/governed-skill-synthesis-process-grader.js +4 -0
  176. package/src/lib/evomap-client.js +14 -2
  177. package/src/lib/formal-budget-authority.js +191 -59
  178. package/src/lib/formal-budget-contract.js +7 -13
  179. package/src/lib/formal-budget-errors.js +31 -0
  180. package/src/lib/formal-budget-lineage-registry.js +612 -0
  181. package/src/lib/formal-budget-provider-scope.js +143 -0
  182. package/src/lib/formal-budget-transport.js +235 -100
  183. package/src/lib/gateway-client-resilience.js +5 -0
  184. package/src/lib/goal-condition-engine.js +25 -2
  185. package/src/lib/hub-llm-client.js +25 -16
  186. package/src/lib/llm-config-defaults.js +10 -4
  187. package/src/lib/llm-connection-probe.js +17 -10
  188. package/src/lib/llm-pricing.js +16 -4
  189. package/src/lib/llm-providers.js +4 -1
  190. package/src/lib/model-capabilities.js +26 -0
  191. package/src/lib/model-catalog-review.js +18 -1
  192. package/src/lib/model-context-catalog.js +41 -1
  193. package/src/lib/model-failure-policy.js +2 -0
  194. package/src/lib/openai-responses.js +8 -0
  195. package/src/lib/plugin-runtime/marketplace-network.js +9 -5
  196. package/src/lib/process-execution-broker/credential-agent.js +7 -2
  197. package/src/lib/process-execution-broker/credential-transport.js +4 -0
  198. package/src/lib/process-execution-broker/index.js +8 -0
  199. package/src/lib/provider-stream.js +28 -13
  200. package/src/lib/sandbox-egress-worker.js +3 -0
  201. package/src/lib/skill-retrieval-router.js +1 -0
  202. package/src/lib/stream-retry.js +2 -0
  203. package/src/lib/sub-agent-context.js +6 -3
  204. package/src/lib/usage-pricing-context.js +33 -2
  205. package/src/lib/web-fetch.js +2 -0
  206. package/src/lib/web-search.js +5 -0
  207. package/src/repl/agent-repl.js +247 -153
  208. package/src/runtime/agent-core.js +228 -96
  209. package/src/runtime/coding-agent-contract-shared.cjs +14 -1
  210. package/src/runtime/headless-runner.js +1 -0
  211. package/src/runtime/headless-stream.js +1 -0
  212. package/src/runtime/mcp-tool-search.js +0 -0
  213. package/src/runtime/provider-stream-state.js +14 -1
  214. package/src/assets/web-panel/assets/ChatBubbleRenderer-D3KsyCUV.js +0 -1
  215. package/src/assets/web-panel/assets/MarkdownRenderer-C1yByobo.js +0 -1
  216. package/src/assets/web-panel/assets/Tasks-DvSrtz0P.js +0 -1
  217. package/src/assets/web-panel/assets/devWarning-Pa13TVrO.js +0 -1
  218. package/src/assets/web-panel/assets/index-BhQg-v6V.js +0 -1
  219. package/src/assets/web-panel/assets/index-DUD5o-vW.js +0 -1
@@ -0,0 +1,612 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createHash, randomUUID } from "node:crypto";
4
+ import { createRequire } from "node:module";
5
+ import { types } from "node:util";
6
+ import {
7
+ budgetError,
8
+ isFormalBudgetError,
9
+ label,
10
+ requireBudget,
11
+ } from "./formal-budget-errors.js";
12
+
13
+ const DIGEST = /^[a-f0-9]{64}$/u;
14
+ const FILE_IDENTITY = /^\d+:\d+$/u;
15
+ export const FORMAL_BUDGET_LINEAGE_CHECKPOINT_SCHEMA =
16
+ "chainlesschain.formal-budget-lineage-checkpoint/v1";
17
+ const IMMUTABLE_HEAD_FIELDS = [
18
+ "lineage_id",
19
+ "binding_id",
20
+ "plan_digest",
21
+ "activation_digest",
22
+ "filename",
23
+ "file_identity",
24
+ ];
25
+ function synchronous(operation, code = "CC_BUDGET_LINEAGE_ASYNC") {
26
+ requireBudget(
27
+ typeof operation === "function" &&
28
+ !types.isAsyncFunction(operation) &&
29
+ !types.isGeneratorFunction(operation),
30
+ "Lineage callbacks must be synchronous functions",
31
+ code,
32
+ );
33
+ }
34
+ /** Receipt binding only; authentication and durable monotonic storage belong to the host. */
35
+ export function lineageCheckpointDigest(checkpoint) {
36
+ const registry = checkpoint.registry;
37
+ return createHash("sha256")
38
+ .update(
39
+ JSON.stringify({
40
+ schema: checkpoint.schema,
41
+ registry: {
42
+ registryId: registry?.registryId,
43
+ filename: registry?.filename,
44
+ fileIdentity: registry?.fileIdentity,
45
+ },
46
+ head: checkedHead(checkpoint.head),
47
+ }),
48
+ )
49
+ .digest("hex");
50
+ }
51
+ function checkedHead(row) {
52
+ label(row?.lineage_id, "checkpoint lineage ID");
53
+ label(row.binding_id, "checkpoint binding ID");
54
+ requireBudget(
55
+ DIGEST.test(row.plan_digest) &&
56
+ DIGEST.test(row.activation_digest) &&
57
+ DIGEST.test(row.state_digest) &&
58
+ Number.isSafeInteger(row.revision) &&
59
+ row.revision >= 0 &&
60
+ typeof row.filename === "string" &&
61
+ path.isAbsolute(row.filename) &&
62
+ FILE_IDENTITY.test(row.file_identity),
63
+ "Invalid independent lineage head",
64
+ "CC_BUDGET_LINEAGE_CHECKPOINT",
65
+ );
66
+ return Object.fromEntries(
67
+ [...IMMUTABLE_HEAD_FIELDS, "state_digest", "revision"].map((key) => [
68
+ key,
69
+ row[key],
70
+ ]),
71
+ );
72
+ }
73
+ const canonicalPath = (filename) =>
74
+ process.platform === "win32" ? filename.toLowerCase() : filename;
75
+ const unavailable = (cause) =>
76
+ isFormalBudgetError(cause)
77
+ ? cause
78
+ : Object.assign(
79
+ budgetError(
80
+ "CC_BUDGET_LINEAGE_STORAGE",
81
+ "Independent lineage registry is unavailable; budget access denied",
82
+ ),
83
+ { cause },
84
+ );
85
+
86
+ function identity(filename) {
87
+ requireBudget(
88
+ path.isAbsolute(filename),
89
+ "Absolute registry/authority path required",
90
+ );
91
+ const stat = fs.lstatSync(filename, { bigint: true });
92
+ requireBudget(
93
+ stat.isFile() && !stat.isSymbolicLink() && stat.ino > 0n,
94
+ "Lineage storage requires a regular file with a stable identity",
95
+ "CC_BUDGET_LINEAGE_IDENTITY",
96
+ );
97
+ return {
98
+ filename: canonicalPath(fs.realpathSync(filename)),
99
+ fileIdentity: `${stat.dev}:${stat.ino}`,
100
+ };
101
+ }
102
+
103
+ function openDatabase(filename) {
104
+ const Database = createRequire(import.meta.url)("better-sqlite3");
105
+ const db = new Database(filename, { fileMustExist: true, timeout: 5000 });
106
+ try {
107
+ db.pragma("journal_mode = WAL");
108
+ db.pragma("synchronous = FULL");
109
+ return db;
110
+ } catch (error) {
111
+ db.close();
112
+ throw error;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Trusted-host storage, independently protected from authority backups/tasks.
118
+ * Live observations and independently pinned checkpoints detect older heads;
119
+ * they do not authenticate themselves or replace OS/hardware monotonic storage.
120
+ *
121
+ * initialize({ filename, checkpointHostId }) permanently opts a new registry
122
+ * into a synchronous host port: { id, load(bindingIdentity), publish(head, digest) }.
123
+ * load returns ALL authenticated latest checkpoints for this registry, including
124
+ * after restart; only a genuinely empty registry may have an empty result.
125
+ * publish must keep monotonic, idempotent records independently durable before
126
+ * returning { durable: true, checkpointDigest: digest }. Its acknowledgement is
127
+ * read back before local authority COMMIT. A crash between any stores stops
128
+ * access for external reconciliation; neither missing nor stale heads are seeded.
129
+ * No production host, authentication or storage protection is supplied here.
130
+ */
131
+ export class FormalBudgetLineageRegistry {
132
+ #db;
133
+ #identity;
134
+ #id;
135
+ #active = false;
136
+ #observed = new Map();
137
+ #checkpointHost;
138
+ #checkpointHostId = null;
139
+
140
+ static initialize({ filename, checkpointHostId = null }) {
141
+ requireBudget(path.isAbsolute(filename), "Absolute registry path required");
142
+ if (checkpointHostId !== null)
143
+ label(checkpointHostId, "checkpoint host ID");
144
+ fs.closeSync(fs.openSync(filename, "wx", 0o600));
145
+ let db;
146
+ try {
147
+ db = openDatabase(filename);
148
+ const registryId = randomUUID();
149
+ db.exec(`BEGIN IMMEDIATE;
150
+ CREATE TABLE registry_meta(id INTEGER PRIMARY KEY CHECK(id=1),registry_id TEXT NOT NULL);
151
+ CREATE TABLE lineage(lineage_id TEXT PRIMARY KEY,binding_id TEXT NOT NULL UNIQUE,plan_digest TEXT NOT NULL,activation_digest TEXT NOT NULL,filename TEXT NOT NULL UNIQUE,file_identity TEXT NOT NULL,state_digest TEXT NOT NULL,revision INTEGER NOT NULL CHECK(revision>=0));`);
152
+ db.prepare("INSERT INTO registry_meta VALUES (1,?)").run(registryId);
153
+ if (checkpointHostId !== null) {
154
+ db.exec(
155
+ "CREATE TABLE checkpoint_policy(id INTEGER PRIMARY KEY CHECK(id=1),host_id TEXT NOT NULL)",
156
+ );
157
+ db.prepare("INSERT INTO checkpoint_policy VALUES (1,?)").run(
158
+ checkpointHostId,
159
+ );
160
+ }
161
+ db.exec("COMMIT");
162
+ return {
163
+ filename: fs.realpathSync(filename),
164
+ registryId,
165
+ registryFileIdentity: identity(filename).fileIdentity,
166
+ ...(checkpointHostId === null ? {} : { checkpointHostId }),
167
+ };
168
+ } catch (error) {
169
+ throw unavailable(error);
170
+ } finally {
171
+ db?.close();
172
+ }
173
+ }
174
+
175
+ constructor({
176
+ filename,
177
+ registryId,
178
+ registryFileIdentity,
179
+ trustedCheckpoints = [],
180
+ checkpointHostId,
181
+ checkpointHost,
182
+ }) {
183
+ label(registryId, "registry ID");
184
+ try {
185
+ this.#identity = identity(filename);
186
+ requireBudget(
187
+ FILE_IDENTITY.test(registryFileIdentity) &&
188
+ registryFileIdentity === this.#identity.fileIdentity,
189
+ "Registry descriptor must pin the original physical file identity",
190
+ "CC_BUDGET_LINEAGE_IDENTITY",
191
+ );
192
+ this.#db = openDatabase(filename);
193
+ requireBudget(
194
+ this.#db
195
+ .prepare("SELECT registry_id FROM registry_meta WHERE id=1")
196
+ .get()?.registry_id === registryId &&
197
+ this.#db.pragma("quick_check", { simple: true }) === "ok",
198
+ "Independent registry identity differs or storage is corrupt",
199
+ "CC_BUDGET_LINEAGE_IDENTITY",
200
+ );
201
+ this.#id = registryId;
202
+ this.#checkpointHostId = this.#storedHostId();
203
+ requireBudget(
204
+ checkpointHostId === undefined ||
205
+ checkpointHostId === this.#checkpointHostId,
206
+ "Pinned checkpoint host policy differs from registry",
207
+ "CC_BUDGET_LINEAGE_HOST_REQUIRED",
208
+ );
209
+ if (this.#checkpointHostId !== null) {
210
+ requireBudget(
211
+ checkpointHost?.id === this.#checkpointHostId,
212
+ "This registry requires its pinned independent checkpoint host on every open",
213
+ "CC_BUDGET_LINEAGE_HOST_REQUIRED",
214
+ );
215
+ const { load, publish } = checkpointHost;
216
+ synchronous(load, "CC_BUDGET_LINEAGE_HOST_ASYNC");
217
+ synchronous(publish, "CC_BUDGET_LINEAGE_HOST_ASYNC");
218
+ this.#checkpointHost = {
219
+ load: load.bind(checkpointHost),
220
+ publish: publish.bind(checkpointHost),
221
+ };
222
+ } else
223
+ requireBudget(
224
+ !checkpointHost,
225
+ "Checkpoint host policy must be fixed at explicit initialization",
226
+ "CC_BUDGET_LINEAGE_HOST_REQUIRED",
227
+ );
228
+ requireBudget(
229
+ Array.isArray(trustedCheckpoints),
230
+ "Trusted checkpoint array required",
231
+ "CC_BUDGET_LINEAGE_CHECKPOINT",
232
+ );
233
+ for (const checkpoint of trustedCheckpoints) {
234
+ requireBudget(
235
+ checkpoint?.schema === FORMAL_BUDGET_LINEAGE_CHECKPOINT_SCHEMA &&
236
+ checkpoint.registry?.registryId === this.#id &&
237
+ checkpoint.registry.filename === this.#identity.filename &&
238
+ checkpoint.registry.fileIdentity === this.#identity.fileIdentity,
239
+ "Checkpoint does not belong to this pinned registry",
240
+ "CC_BUDGET_LINEAGE_CHECKPOINT",
241
+ );
242
+ const head = checkedHead(checkpoint.head);
243
+ requireBudget(
244
+ !this.#observed.has(head.lineage_id),
245
+ "Duplicate trusted lineage checkpoint",
246
+ "CC_BUDGET_LINEAGE_CHECKPOINT",
247
+ );
248
+ this.#observed.set(head.lineage_id, head);
249
+ }
250
+ this.#locked(() => {});
251
+ } catch (error) {
252
+ this.#db?.close();
253
+ throw unavailable(error);
254
+ }
255
+ }
256
+
257
+ get registryId() {
258
+ return this.#id;
259
+ }
260
+
261
+ get checkpointHostId() {
262
+ return this.#checkpointHostId;
263
+ }
264
+
265
+ #storedHostId() {
266
+ const exists = this.#db
267
+ .prepare(
268
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name='checkpoint_policy'",
269
+ )
270
+ .get();
271
+ if (!exists) return null;
272
+ const row = this.#db
273
+ .prepare("SELECT host_id FROM checkpoint_policy WHERE id=1")
274
+ .get();
275
+ requireBudget(
276
+ row,
277
+ "Missing checkpoint host policy",
278
+ "CC_BUDGET_LINEAGE_HOST_REQUIRED",
279
+ );
280
+ return label(row.host_id, "checkpoint host ID");
281
+ }
282
+
283
+ #callHost(method, ...args) {
284
+ try {
285
+ const result = this.#checkpointHost[method](...args);
286
+ // Drain native rejected Promises, but never run an arbitrary thenable.
287
+ if (types.isPromise(result)) void result.catch(() => {});
288
+ requireBudget(
289
+ !result?.then,
290
+ "Checkpoint host operations must finish synchronously",
291
+ "CC_BUDGET_LINEAGE_HOST_ASYNC",
292
+ );
293
+ return result;
294
+ } catch (cause) {
295
+ if (isFormalBudgetError(cause)) throw cause;
296
+ throw Object.assign(
297
+ budgetError(
298
+ "CC_BUDGET_LINEAGE_HOST_UNAVAILABLE",
299
+ "Independent checkpoint host failed; external reconciliation required",
300
+ ),
301
+ { cause },
302
+ );
303
+ }
304
+ }
305
+
306
+ #checkpoint(row) {
307
+ return Object.freeze({
308
+ schema: FORMAL_BUDGET_LINEAGE_CHECKPOINT_SCHEMA,
309
+ registry: this.bindingIdentity,
310
+ head: Object.freeze(checkedHead(row)),
311
+ });
312
+ }
313
+
314
+ #verifyHostHeads() {
315
+ if (!this.#checkpointHost) return;
316
+ const checkpoints = this.#callHost("load", this.bindingIdentity);
317
+ requireBudget(
318
+ Array.isArray(checkpoints),
319
+ "Checkpoint host must return all latest lineage heads",
320
+ "CC_BUDGET_LINEAGE_HOST_RECEIPT",
321
+ );
322
+ const heads = new Map();
323
+ for (const checkpoint of checkpoints) {
324
+ const head = checkedHead(checkpoint?.head);
325
+ requireBudget(
326
+ !heads.has(head.lineage_id) &&
327
+ lineageCheckpointDigest(checkpoint) ===
328
+ lineageCheckpointDigest(this.#checkpoint(head)),
329
+ "Duplicate or foreign checkpoint host record",
330
+ "CC_BUDGET_LINEAGE_HOST_RECEIPT",
331
+ );
332
+ heads.set(head.lineage_id, head);
333
+ }
334
+ const rows = this.#db
335
+ .prepare("SELECT * FROM lineage ORDER BY lineage_id")
336
+ .all();
337
+ requireBudget(
338
+ rows.length === heads.size &&
339
+ rows.every((row) => {
340
+ const head = heads.get(row.lineage_id);
341
+ return (
342
+ head &&
343
+ [...IMMUTABLE_HEAD_FIELDS, "state_digest", "revision"].every(
344
+ (key) => row[key] === head[key],
345
+ )
346
+ );
347
+ }),
348
+ "Registry and independent latest checkpoints differ; never seed, downgrade or reset",
349
+ "CC_BUDGET_LINEAGE_HOST_STALE",
350
+ );
351
+ }
352
+
353
+ #commitHead(head) {
354
+ // Registry -> independent host acknowledgement/readback -> authority COMMIT.
355
+ // Any failure after the first commit deliberately leaves a closed mismatch.
356
+ this.#db.exec("COMMIT");
357
+ this.#observed.set(head.lineage_id, head);
358
+ if (this.#checkpointHost) {
359
+ const checkpoint = this.#checkpoint(head);
360
+ const digest = lineageCheckpointDigest(checkpoint);
361
+ const receipt = this.#callHost("publish", checkpoint, digest);
362
+ requireBudget(
363
+ receipt?.durable === true && receipt.checkpointDigest === digest,
364
+ "Checkpoint host did not acknowledge this exact durable head",
365
+ "CC_BUDGET_LINEAGE_HOST_RECEIPT",
366
+ );
367
+ // A claimed acknowledgement must also be visible to the next load.
368
+ this.#verifyHostHeads();
369
+ }
370
+ }
371
+
372
+ get bindingIdentity() {
373
+ return Object.freeze({ registryId: this.#id, ...this.#identity });
374
+ }
375
+
376
+ #observeHeads() {
377
+ const rows = this.#db
378
+ .prepare("SELECT * FROM lineage ORDER BY lineage_id")
379
+ .all()
380
+ .map(checkedHead);
381
+ const current = new Map(rows.map((row) => [row.lineage_id, row]));
382
+ for (const [lineageId, previous] of this.#observed) {
383
+ const row = current.get(lineageId);
384
+ requireBudget(
385
+ row &&
386
+ row.revision >= previous.revision &&
387
+ IMMUTABLE_HEAD_FIELDS.every((key) => row[key] === previous[key]) &&
388
+ (row.revision > previous.revision ||
389
+ row.state_digest === previous.state_digest),
390
+ "Registry lost or changed a previously observed lineage head; external reconciliation required",
391
+ "CC_BUDGET_LINEAGE_ROLLBACK",
392
+ );
393
+ }
394
+ for (const row of rows) this.#observed.set(row.lineage_id, row);
395
+ }
396
+
397
+ #locked(operation) {
398
+ requireBudget(
399
+ !this.#active,
400
+ "Nested lineage operation denied",
401
+ "CC_BUDGET_LINEAGE_BUSY",
402
+ );
403
+ this.#active = true;
404
+ try {
405
+ const current = identity(this.#identity.filename);
406
+ requireBudget(
407
+ current.filename === this.#identity.filename &&
408
+ current.fileIdentity === this.#identity.fileIdentity,
409
+ "Registry file was replaced",
410
+ "CC_BUDGET_LINEAGE_IDENTITY",
411
+ );
412
+ this.#db.exec("BEGIN IMMEDIATE");
413
+ requireBudget(
414
+ this.#db
415
+ .prepare("SELECT registry_id FROM registry_meta WHERE id=1")
416
+ .get()?.registry_id === this.#id,
417
+ "Pinned registry UUID changed",
418
+ "CC_BUDGET_LINEAGE_IDENTITY",
419
+ );
420
+ this.#observeHeads();
421
+ requireBudget(
422
+ this.#storedHostId() === this.#checkpointHostId,
423
+ "Checkpoint host policy changed while registry was open",
424
+ "CC_BUDGET_LINEAGE_HOST_REQUIRED",
425
+ );
426
+ this.#verifyHostHeads();
427
+ const result = operation();
428
+ requireBudget(
429
+ !result?.then,
430
+ "Lineage operations must be synchronous",
431
+ "CC_BUDGET_LINEAGE_ASYNC",
432
+ );
433
+ if (this.#db.inTransaction) {
434
+ this.#observeHeads();
435
+ this.#db.exec("COMMIT");
436
+ }
437
+ return result;
438
+ } catch (error) {
439
+ try {
440
+ if (this.#db?.inTransaction) this.#db.exec("ROLLBACK");
441
+ } catch {
442
+ // Preserve a budget failure even when storage cannot roll back.
443
+ }
444
+ throw unavailable(error);
445
+ } finally {
446
+ this.#active = false;
447
+ }
448
+ }
449
+
450
+ /** Register once, never adopt another database or reset a prior lineage. */
451
+ register(binding, initializeAuthority) {
452
+ synchronous(initializeAuthority);
453
+ label(binding.lineageId, "lineage ID");
454
+ requireBudget(
455
+ DIGEST.test(binding.planDigest) && DIGEST.test(binding.activationDigest),
456
+ "Pinned plan and activation digests required",
457
+ );
458
+ return this.#locked(() => {
459
+ requireBudget(
460
+ !this.#db
461
+ .prepare("SELECT 1 FROM lineage WHERE lineage_id=?")
462
+ .get(binding.lineageId),
463
+ "Original lineage already has an authority; no new allowance is permitted",
464
+ "CC_BUDGET_LINEAGE_EXISTS",
465
+ );
466
+ const bindingId = randomUUID();
467
+ const stateDigest = initializeAuthority({
468
+ registryId: this.#id,
469
+ bindingId,
470
+ registryFilename: this.#identity.filename,
471
+ registryFileIdentity: this.#identity.fileIdentity,
472
+ checkpointHostId: this.#checkpointHostId,
473
+ });
474
+ const file = identity(binding.filename);
475
+ requireBudget(
476
+ file.filename !== this.#identity.filename && DIGEST.test(stateDigest),
477
+ "Independent authority state digest required",
478
+ );
479
+ this.#db
480
+ .prepare("INSERT INTO lineage VALUES (?,?,?,?,?,?,?,0)")
481
+ .run(
482
+ binding.lineageId,
483
+ bindingId,
484
+ binding.planDigest,
485
+ binding.activationDigest,
486
+ file.filename,
487
+ file.fileIdentity,
488
+ stateDigest,
489
+ );
490
+ this.#commitHead(
491
+ checkedHead(
492
+ this.#db
493
+ .prepare("SELECT * FROM lineage WHERE lineage_id=?")
494
+ .get(binding.lineageId),
495
+ ),
496
+ );
497
+ return { registryId: this.#id, bindingId };
498
+ });
499
+ }
500
+
501
+ /**
502
+ * Lock registry before authority. Advance the independent head durably BEFORE
503
+ * committing local state. A crash in between leaves a mismatch, never credit.
504
+ * The caller must roll back its local transaction on any failure.
505
+ */
506
+ coordinate(binding, operation) {
507
+ synchronous(operation);
508
+ return this.#locked(() => {
509
+ const row = this.#db
510
+ .prepare("SELECT * FROM lineage WHERE lineage_id=?")
511
+ .get(binding.lineageId);
512
+ const file = identity(binding.filename);
513
+ requireBudget(
514
+ row &&
515
+ row.binding_id === binding.bindingId &&
516
+ row.plan_digest === binding.planDigest &&
517
+ row.activation_digest === binding.activationDigest &&
518
+ row.filename === file.filename &&
519
+ row.file_identity === file.fileIdentity,
520
+ "Authority is not the uniquely registered lineage database",
521
+ "CC_BUDGET_LINEAGE_IDENTITY",
522
+ );
523
+ let asserted = false,
524
+ advanced = false,
525
+ active = true;
526
+ try {
527
+ const result = operation({
528
+ assertState(stateDigest) {
529
+ requireBudget(
530
+ active,
531
+ "Lineage lease has expired",
532
+ "CC_BUDGET_LINEAGE_LEASE_EXPIRED",
533
+ );
534
+ requireBudget(
535
+ !asserted &&
536
+ !advanced &&
537
+ DIGEST.test(stateDigest) &&
538
+ stateDigest === row.state_digest,
539
+ "Authority state differs from the independent head; reconcile without resetting credit",
540
+ "CC_BUDGET_LINEAGE_STATE",
541
+ );
542
+ asserted = true;
543
+ },
544
+ advance: (stateDigest) => {
545
+ requireBudget(
546
+ active,
547
+ "Lineage lease has expired",
548
+ "CC_BUDGET_LINEAGE_LEASE_EXPIRED",
549
+ );
550
+ requireBudget(
551
+ asserted && !advanced && DIGEST.test(stateDigest),
552
+ "Invalid lineage advance",
553
+ );
554
+ const increment = stateDigest === row.state_digest ? 0 : 1;
555
+ requireBudget(
556
+ Number.isSafeInteger(row.revision + increment),
557
+ "Lineage revision exhausted",
558
+ );
559
+ this.#db
560
+ .prepare(
561
+ "UPDATE lineage SET state_digest=?,revision=revision+? WHERE lineage_id=? AND state_digest=?",
562
+ )
563
+ .run(stateDigest, increment, binding.lineageId, row.state_digest);
564
+ this.#commitHead({
565
+ ...checkedHead(row),
566
+ state_digest: stateDigest,
567
+ revision: row.revision + increment,
568
+ });
569
+ advanced = true;
570
+ },
571
+ });
572
+ requireBudget(
573
+ !result?.then,
574
+ "Lineage callbacks must complete synchronously",
575
+ "CC_BUDGET_LINEAGE_ASYNC",
576
+ );
577
+ requireBudget(
578
+ advanced,
579
+ "Authority did not persist its independent head",
580
+ );
581
+ return result;
582
+ } finally {
583
+ active = false;
584
+ }
585
+ });
586
+ }
587
+
588
+ status(lineageId) {
589
+ label(lineageId, "lineage ID");
590
+ return this.#locked(
591
+ () =>
592
+ this.#db
593
+ .prepare("SELECT * FROM lineage WHERE lineage_id=?")
594
+ .get(lineageId) ?? null,
595
+ );
596
+ }
597
+
598
+ /** Persist/authenticate this outside both database backup domains at the host. */
599
+ checkpoint(lineageId) {
600
+ const row = this.status(lineageId);
601
+ requireBudget(
602
+ row,
603
+ "Unknown lineage checkpoint",
604
+ "CC_BUDGET_LINEAGE_CHECKPOINT",
605
+ );
606
+ return this.#checkpoint(row);
607
+ }
608
+
609
+ close() {
610
+ this.#db.close();
611
+ }
612
+ }