@rudderhq/server 0.7.3 → 0.7.5

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 (268) hide show
  1. package/dist/agent-runtimes/index.d.ts +1 -1
  2. package/dist/agent-runtimes/index.d.ts.map +1 -1
  3. package/dist/agent-runtimes/types.d.ts +1 -1
  4. package/dist/agent-runtimes/types.d.ts.map +1 -1
  5. package/dist/bootstrap/register-api-routes.d.ts.map +1 -1
  6. package/dist/bootstrap/register-api-routes.js +2 -0
  7. package/dist/bootstrap/register-api-routes.js.map +1 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +10 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/redaction.d.ts +4 -0
  12. package/dist/redaction.d.ts.map +1 -1
  13. package/dist/redaction.js +15 -0
  14. package/dist/redaction.js.map +1 -1
  15. package/dist/routes/app-builder.d.ts.map +1 -1
  16. package/dist/routes/app-builder.js +1 -13
  17. package/dist/routes/app-builder.js.map +1 -1
  18. package/dist/routes/approvals.d.ts.map +1 -1
  19. package/dist/routes/approvals.js +36 -9
  20. package/dist/routes/approvals.js.map +1 -1
  21. package/dist/routes/chats.d.ts.map +1 -1
  22. package/dist/routes/chats.js +25 -8
  23. package/dist/routes/chats.js.map +1 -1
  24. package/dist/routes/chats.preflight.d.ts +2 -1
  25. package/dist/routes/chats.preflight.d.ts.map +1 -1
  26. package/dist/routes/chats.preflight.js +7 -0
  27. package/dist/routes/chats.preflight.js.map +1 -1
  28. package/dist/routes/chats.stream-routes.d.ts.map +1 -1
  29. package/dist/routes/chats.stream-routes.js +14 -3
  30. package/dist/routes/chats.stream-routes.js.map +1 -1
  31. package/dist/routes/chats.stream-support.d.ts.map +1 -1
  32. package/dist/routes/chats.stream-support.js +8 -0
  33. package/dist/routes/chats.stream-support.js.map +1 -1
  34. package/dist/routes/goals.d.ts.map +1 -1
  35. package/dist/routes/goals.js +176 -27
  36. package/dist/routes/goals.js.map +1 -1
  37. package/dist/routes/index.d.ts +1 -0
  38. package/dist/routes/index.d.ts.map +1 -1
  39. package/dist/routes/index.js +1 -0
  40. package/dist/routes/index.js.map +1 -1
  41. package/dist/routes/issues.d.ts.map +1 -1
  42. package/dist/routes/issues.js +2 -1
  43. package/dist/routes/issues.js.map +1 -1
  44. package/dist/routes/issues.mutations.d.ts.map +1 -1
  45. package/dist/routes/issues.mutations.js +121 -19
  46. package/dist/routes/issues.mutations.js.map +1 -1
  47. package/dist/routes/managed-mcp-runtime.js +1 -1
  48. package/dist/routes/managed-mcp-runtime.js.map +1 -1
  49. package/dist/routes/organization-skills.d.ts.map +1 -1
  50. package/dist/routes/organization-skills.js +23 -1
  51. package/dist/routes/organization-skills.js.map +1 -1
  52. package/dist/routes/orgs.d.ts.map +1 -1
  53. package/dist/routes/orgs.js +5 -0
  54. package/dist/routes/orgs.js.map +1 -1
  55. package/dist/routes/requests.d.ts +3 -0
  56. package/dist/routes/requests.d.ts.map +1 -0
  57. package/dist/routes/requests.js +122 -0
  58. package/dist/routes/requests.js.map +1 -0
  59. package/dist/routes/rudder-plugins.d.ts.map +1 -1
  60. package/dist/routes/rudder-plugins.js +1 -11
  61. package/dist/routes/rudder-plugins.js.map +1 -1
  62. package/dist/services/activity-log.d.ts.map +1 -1
  63. package/dist/services/activity-log.js +19 -9
  64. package/dist/services/activity-log.js.map +1 -1
  65. package/dist/services/agent-run-context.d.ts.map +1 -1
  66. package/dist/services/agent-run-context.js +2 -7
  67. package/dist/services/agent-run-context.js.map +1 -1
  68. package/dist/services/agents.d.ts +14 -14
  69. package/dist/services/agents.d.ts.map +1 -1
  70. package/dist/services/agents.js +2 -1
  71. package/dist/services/agents.js.map +1 -1
  72. package/dist/services/chat-agent-runs.d.ts +1 -0
  73. package/dist/services/chat-agent-runs.d.ts.map +1 -1
  74. package/dist/services/chat-agent-runs.js +1 -0
  75. package/dist/services/chat-agent-runs.js.map +1 -1
  76. package/dist/services/chat-assistant.d.ts.map +1 -1
  77. package/dist/services/chat-assistant.helpers.d.ts +2 -0
  78. package/dist/services/chat-assistant.helpers.d.ts.map +1 -1
  79. package/dist/services/chat-assistant.helpers.js +25 -0
  80. package/dist/services/chat-assistant.helpers.js.map +1 -1
  81. package/dist/services/chat-assistant.js +46 -9
  82. package/dist/services/chat-assistant.js.map +1 -1
  83. package/dist/services/chat-queued-message-materialization.d.ts +1 -1
  84. package/dist/services/chats.create.d.ts +2 -2
  85. package/dist/services/chats.create.d.ts.map +1 -1
  86. package/dist/services/chats.d.ts +13 -13
  87. package/dist/services/chats.d.ts.map +1 -1
  88. package/dist/services/chats.helpers.d.ts +2 -2
  89. package/dist/services/chats.helpers.d.ts.map +1 -1
  90. package/dist/services/chats.helpers.goal-context.test.d.ts +2 -0
  91. package/dist/services/chats.helpers.goal-context.test.d.ts.map +1 -0
  92. package/dist/services/chats.helpers.goal-context.test.js +40 -0
  93. package/dist/services/chats.helpers.goal-context.test.js.map +1 -0
  94. package/dist/services/chats.helpers.js +27 -2
  95. package/dist/services/chats.helpers.js.map +1 -1
  96. package/dist/services/chats.js.map +1 -1
  97. package/dist/services/finance.d.ts +1 -1
  98. package/dist/services/goals.d.ts +267 -244
  99. package/dist/services/goals.d.ts.map +1 -1
  100. package/dist/services/goals.js +1279 -297
  101. package/dist/services/goals.js.map +1 -1
  102. package/dist/services/index.d.ts +2 -1
  103. package/dist/services/index.d.ts.map +1 -1
  104. package/dist/services/index.js +2 -1
  105. package/dist/services/index.js.map +1 -1
  106. package/dist/services/instance-settings.d.ts.map +1 -1
  107. package/dist/services/instance-settings.js +6 -14
  108. package/dist/services/instance-settings.js.map +1 -1
  109. package/dist/services/issues.d.ts +16 -16
  110. package/dist/services/knowledge-portability/organization-skills.d.ts +2 -1
  111. package/dist/services/knowledge-portability/organization-skills.d.ts.map +1 -1
  112. package/dist/services/knowledge-portability/organization-skills.js +52 -17
  113. package/dist/services/knowledge-portability/organization-skills.js.map +1 -1
  114. package/dist/services/mcp/managed-bindings.d.ts +1 -3
  115. package/dist/services/mcp/managed-bindings.d.ts.map +1 -1
  116. package/dist/services/mcp/managed-bindings.js +2 -3
  117. package/dist/services/mcp/managed-bindings.js.map +1 -1
  118. package/dist/services/messenger.d.ts +2 -2
  119. package/dist/services/messenger.d.ts.map +1 -1
  120. package/dist/services/messenger.js +76 -17
  121. package/dist/services/messenger.js.map +1 -1
  122. package/dist/services/orgs.d.ts.map +1 -1
  123. package/dist/services/orgs.js +3 -1
  124. package/dist/services/orgs.js.map +1 -1
  125. package/dist/services/product-analytics.d.ts +1 -1
  126. package/dist/services/requests.d.ts +919 -0
  127. package/dist/services/requests.d.ts.map +1 -0
  128. package/dist/services/requests.js +532 -0
  129. package/dist/services/requests.js.map +1 -0
  130. package/dist/services/rudder-plugins.d.ts.map +1 -1
  131. package/dist/services/rudder-plugins.js +20 -5
  132. package/dist/services/rudder-plugins.js.map +1 -1
  133. package/dist/services/rudder-plugins.test.js +10 -0
  134. package/dist/services/rudder-plugins.test.js.map +1 -1
  135. package/dist/services/runtime-kernel/assignment-run-guardrail.d.ts +65 -0
  136. package/dist/services/runtime-kernel/assignment-run-guardrail.d.ts.map +1 -0
  137. package/dist/services/runtime-kernel/assignment-run-guardrail.js +204 -0
  138. package/dist/services/runtime-kernel/assignment-run-guardrail.js.map +1 -0
  139. package/dist/services/runtime-kernel/assignment-run-guardrail.test.d.ts +2 -0
  140. package/dist/services/runtime-kernel/assignment-run-guardrail.test.d.ts.map +1 -0
  141. package/dist/services/runtime-kernel/assignment-run-guardrail.test.js +114 -0
  142. package/dist/services/runtime-kernel/assignment-run-guardrail.test.js.map +1 -0
  143. package/dist/services/runtime-kernel/heartbeat.d.ts +12 -0
  144. package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
  145. package/dist/services/runtime-kernel/heartbeat.execute.d.ts.map +1 -1
  146. package/dist/services/runtime-kernel/heartbeat.execute.js +138 -7
  147. package/dist/services/runtime-kernel/heartbeat.execute.js.map +1 -1
  148. package/dist/services/runtime-kernel/heartbeat.js +93 -1
  149. package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
  150. package/dist/services/runtime-kernel/heartbeat.recovery.d.ts +2 -0
  151. package/dist/services/runtime-kernel/heartbeat.recovery.d.ts.map +1 -1
  152. package/dist/services/runtime-kernel/heartbeat.recovery.js +18 -3
  153. package/dist/services/runtime-kernel/heartbeat.recovery.js.map +1 -1
  154. package/dist/services/runtime-kernel/heartbeat.sessions.js +1 -1
  155. package/dist/services/runtime-kernel/heartbeat.sessions.js.map +1 -1
  156. package/dist/services/runtime-kernel/heartbeat.terminal.d.ts.map +1 -1
  157. package/dist/services/runtime-kernel/heartbeat.terminal.js +15 -1
  158. package/dist/services/runtime-kernel/heartbeat.terminal.js.map +1 -1
  159. package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts +1 -0
  160. package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts.map +1 -1
  161. package/dist/services/runtime-kernel/heartbeat.wakeup.js +89 -3
  162. package/dist/services/runtime-kernel/heartbeat.wakeup.js.map +1 -1
  163. package/dist/services/side-chats.d.ts +0 -2
  164. package/dist/services/side-chats.d.ts.map +1 -1
  165. package/dist/services/side-chats.js +4 -12
  166. package/dist/services/side-chats.js.map +1 -1
  167. package/dist/services/workspace-backup-v2.d.ts +127 -0
  168. package/dist/services/workspace-backup-v2.d.ts.map +1 -0
  169. package/dist/services/workspace-backup-v2.js +964 -0
  170. package/dist/services/workspace-backup-v2.js.map +1 -0
  171. package/dist/services/workspace-backup-v2.test.d.ts +2 -0
  172. package/dist/services/workspace-backup-v2.test.d.ts.map +1 -0
  173. package/dist/services/workspace-backup-v2.test.js +318 -0
  174. package/dist/services/workspace-backup-v2.test.js.map +1 -0
  175. package/dist/services/workspace-backups.d.ts +46 -1
  176. package/dist/services/workspace-backups.d.ts.map +1 -1
  177. package/dist/services/workspace-backups.js +579 -60
  178. package/dist/services/workspace-backups.js.map +1 -1
  179. package/dist/services/workspace-runtime.services.d.ts +1 -1
  180. package/package.json +14 -14
  181. package/resources/bundled-skills/rudder-docs/references/cli-reference.md +7 -2
  182. package/resources/bundled-skills/rudder-docs/references/operating-practices.md +10 -6
  183. package/skills/rudder-docs/references/cli-reference.md +7 -2
  184. package/skills/rudder-docs/references/operating-practices.md +10 -6
  185. package/ui-dist/assets/{LibraryLiveSurface-CoGcDfo0.js → LibraryLiveSurface-nSMExVEC.js} +1 -1
  186. package/ui-dist/assets/{abnfDiagram-VRR7QNED-BnPHHNfu.js → abnfDiagram-VRR7QNED-Av8TM585.js} +1 -1
  187. package/ui-dist/assets/addon-fit-DzFhQGnx.js +1 -0
  188. package/ui-dist/assets/{arc-CAvqQQR_.js → arc-Bc-Gf4Fc.js} +1 -1
  189. package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-DDa31YAK.js → architectureDiagram-ZJ3FMSHR-BOph0PHz.js} +1 -1
  190. package/ui-dist/assets/{blockDiagram-677ZJIJ3-DDXLU9Ej.js → blockDiagram-677ZJIJ3-BCUhdmf3.js} +1 -1
  191. package/ui-dist/assets/{c4Diagram-LMCZKHZV-zEm0rHCp.js → c4Diagram-LMCZKHZV-B_3pH6bx.js} +1 -1
  192. package/ui-dist/assets/channel-CyoUWdeD.js +1 -0
  193. package/ui-dist/assets/{chunk-2Q5K7J3B-y1Rw45sz.js → chunk-2Q5K7J3B-C4gdcZxO.js} +1 -1
  194. package/ui-dist/assets/{chunk-32BRIVSS-Cq8cQBba.js → chunk-32BRIVSS-JxQV72by.js} +1 -1
  195. package/ui-dist/assets/{chunk-5VM5RSS4-CjQA6jGI.js → chunk-5VM5RSS4-f0zO1uV_.js} +1 -1
  196. package/ui-dist/assets/{chunk-EX3LRPZG-VVYt7jh3.js → chunk-EX3LRPZG-Bbn2Dpmg.js} +1 -1
  197. package/ui-dist/assets/{chunk-JWPE2WC7-BJC3ISop.js → chunk-JWPE2WC7-B118_FhP.js} +1 -1
  198. package/ui-dist/assets/{chunk-MOJQB5TN-ClZgTqQk.js → chunk-MOJQB5TN-CEcGkJ3p.js} +1 -1
  199. package/ui-dist/assets/{chunk-RYQCIY6F-DRei3pn2.js → chunk-RYQCIY6F-C-WY_ozP.js} +1 -1
  200. package/ui-dist/assets/{chunk-V7JOEXUC-BGWrQfpZ.js → chunk-V7JOEXUC-DS5sjH7p.js} +1 -1
  201. package/ui-dist/assets/{chunk-VR4S4FIN-BBYT57Lt.js → chunk-VR4S4FIN-COyRk9XW.js} +1 -1
  202. package/ui-dist/assets/{chunk-XXDRQBXY-D7P4SoZB.js → chunk-XXDRQBXY-Cd2T_1Uf.js} +1 -1
  203. package/ui-dist/assets/classDiagram-OUVF2IWQ-BETmcliY.js +1 -0
  204. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-BETmcliY.js +1 -0
  205. package/ui-dist/assets/{cose-bilkent-JH36ORCC-mJXY7GQo.js → cose-bilkent-JH36ORCC-iEFFQ2c7.js} +1 -1
  206. package/ui-dist/assets/{cynefin-VYW2F7L2-C-I_lPmH.js → cynefin-VYW2F7L2-Bp-sv4UP.js} +1 -1
  207. package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-Bm1S_w7H.js → cynefinDiagram-TSTJHNR4-CET_eOH5.js} +1 -1
  208. package/ui-dist/assets/{dagre-VKFMJZFB-BuPIm1AB.js → dagre-VKFMJZFB-BkxxZr0z.js} +1 -1
  209. package/ui-dist/assets/{diagram-FQU43EPY-DTBCuV0W.js → diagram-FQU43EPY-2b0kz8hj.js} +1 -1
  210. package/ui-dist/assets/{diagram-G47NLZAW-B4CK8uR2.js → diagram-G47NLZAW-CWgvA-UY.js} +1 -1
  211. package/ui-dist/assets/{diagram-NH7WQ7WH-BkbS0GkL.js → diagram-NH7WQ7WH-DKoSLHQw.js} +1 -1
  212. package/ui-dist/assets/{diagram-OA4YK3LP-C_mkRe0c.js → diagram-OA4YK3LP-BtpJ7ObO.js} +1 -1
  213. package/ui-dist/assets/{diagram-WEI45ONY-CHUb3TIJ.js → diagram-WEI45ONY-cTRv44yL.js} +1 -1
  214. package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-D_lN4ZOY.js → ebnfDiagram-CCIWWBDH-BMwUwEZM.js} +1 -1
  215. package/ui-dist/assets/{erDiagram-Q63AITRT-CRnDqcsy.js → erDiagram-Q63AITRT-B9qW6I5c.js} +1 -1
  216. package/ui-dist/assets/{flowDiagram-23GEKE2U-ChELY-8y.js → flowDiagram-23GEKE2U-Cb1rJFcp.js} +1 -1
  217. package/ui-dist/assets/{ganttDiagram-NO4QXBWP-QIML25-M.js → ganttDiagram-NO4QXBWP-CiicdHPl.js} +1 -1
  218. package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-TB_AKH8l.js → gitGraphDiagram-IHSO6WYX-Cge-AgEB.js} +1 -1
  219. package/ui-dist/assets/{graph-HKPvyYQ1.js → graph-0wCGCEs4.js} +1 -1
  220. package/ui-dist/assets/{index-DmEf_U5I.js → index-5dD5l_7S.js} +1 -1
  221. package/ui-dist/assets/{index-gdvFtffQ.js → index-BOnG5igl.js} +1 -1
  222. package/ui-dist/assets/index-BT7dphT4.css +32 -0
  223. package/ui-dist/assets/{index-rnOTiIXd.js → index-BXR0UEnQ.js} +1 -1
  224. package/ui-dist/assets/{index-ZzX8P4M5.js → index-B_c6Qzui.js} +1 -1
  225. package/ui-dist/assets/{index-OfGDMJiA.js → index-BhIqb_sb.js} +1 -1
  226. package/ui-dist/assets/{index-CjIeIR2m.js → index-BjGW8ROf.js} +1 -1
  227. package/ui-dist/assets/{index-CfpFS1Tl.js → index-BqpG2FnL.js} +565 -564
  228. package/ui-dist/assets/{index-DDMaBNLI.js → index-C3ghnZSF.js} +1 -1
  229. package/ui-dist/assets/{index-BeNxgoKd.js → index-CB9X8t3F.js} +1 -1
  230. package/ui-dist/assets/{index-fDtlbG8Y.js → index-CcDlhtvT.js} +1 -1
  231. package/ui-dist/assets/{index-Ar0zIwom.js → index-D1w6UIRX.js} +1 -1
  232. package/ui-dist/assets/{index-CB-UdrKN.js → index-DN9Yo10m.js} +1 -1
  233. package/ui-dist/assets/{index-DSZmO4bA.js → index-DP30MjES.js} +1 -1
  234. package/ui-dist/assets/{index-DfzFi2fx.js → index-DXRvkZMv.js} +1 -1
  235. package/ui-dist/assets/{index-DCgbySwf.js → index-Dy-AXx6E.js} +1 -1
  236. package/ui-dist/assets/{infoDiagram-FWYZ7A6U-DUR3sDTd.js → infoDiagram-FWYZ7A6U-R2pgAXHR.js} +1 -1
  237. package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-CcnYT0s0.js → ishikawaDiagram-FXEZZL3T-BJivveWk.js} +1 -1
  238. package/ui-dist/assets/{journeyDiagram-5HDEW3XC-CqfbG8T1.js → journeyDiagram-5HDEW3XC-CLlN_hss.js} +1 -1
  239. package/ui-dist/assets/{kanban-definition-HUTT4EX6-CYwQM9eR.js → kanban-definition-HUTT4EX6-CCRtDa5s.js} +1 -1
  240. package/ui-dist/assets/{layout-B2IlJgLE.js → layout-BgygArKO.js} +1 -1
  241. package/ui-dist/assets/{linear-CcEPcfbO.js → linear-ZTGN1Bax.js} +1 -1
  242. package/ui-dist/assets/{map-Bmhfvlpu.js → map-BheoFUge.js} +1 -1
  243. package/ui-dist/assets/{mermaid.core-T9Q0CuQx.js → mermaid.core-D4HYOaQr.js} +4 -4
  244. package/ui-dist/assets/{mindmap-definition-LN4V7U3C-CRjQlwN4.js → mindmap-definition-LN4V7U3C-B25TPfzP.js} +1 -1
  245. package/ui-dist/assets/{pegDiagram-2B236MQR-DenccdY8.js → pegDiagram-2B236MQR-CWrV5tQE.js} +1 -1
  246. package/ui-dist/assets/{pieDiagram-ENE6RG2P-CPiTrJcY.js → pieDiagram-ENE6RG2P-BQTq4Jp6.js} +1 -1
  247. package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-fKbvGl_-.js → quadrantDiagram-ABIIQ3AL-C5TwZIzz.js} +1 -1
  248. package/ui-dist/assets/{railroadDiagram-RFXS5EU6-D6uOjEmA.js → railroadDiagram-RFXS5EU6--wv7_omb.js} +1 -1
  249. package/ui-dist/assets/{requirementDiagram-TGXJPOKE-CQIQ387O.js → requirementDiagram-TGXJPOKE-D80O_sgV.js} +1 -1
  250. package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-CKrnONJL.js → sankeyDiagram-HTMAVEWB-Cz0SmDPj.js} +1 -1
  251. package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-C3vaOiZO.js → sequenceDiagram-DBY2YBRQ-jmLYUOQ2.js} +1 -1
  252. package/ui-dist/assets/{sizeCapture-X5ZJPWSS-F39EvxY0.js → sizeCapture-X5ZJPWSS-C1FqwoMN.js} +1 -1
  253. package/ui-dist/assets/{stateDiagram-2N3HPSRC-Bq2sVFQq.js → stateDiagram-2N3HPSRC-CZa7Dj75.js} +1 -1
  254. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-CEUECJmS.js +1 -0
  255. package/ui-dist/assets/{swimlanes-5IMT3BWC-CSrygB7m.js → swimlanes-5IMT3BWC-CbHndCqy.js} +2 -2
  256. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-4TgYo_NT.js +8 -0
  257. package/ui-dist/assets/{timeline-definition-FHXFAJF6-DSMMlntv.js → timeline-definition-FHXFAJF6-CFOg8vuA.js} +1 -1
  258. package/ui-dist/assets/{vennDiagram-L72KCM5P-D5XH96zV.js → vennDiagram-L72KCM5P-1dSr5CoN.js} +1 -1
  259. package/ui-dist/assets/{wardleyDiagram-EHGQE667-BXX2RFsU.js → wardleyDiagram-EHGQE667-CAMneCxP.js} +1 -1
  260. package/ui-dist/assets/xterm-M5ke8gUF.js +9 -0
  261. package/ui-dist/assets/{xychartDiagram-FW5EYKEG-awBW2lwG.js → xychartDiagram-FW5EYKEG-DxVyl1x4.js} +1 -1
  262. package/ui-dist/index.html +2 -2
  263. package/ui-dist/assets/channel-M_3GhwHQ.js +0 -1
  264. package/ui-dist/assets/classDiagram-OUVF2IWQ-G44FJFnB.js +0 -1
  265. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-G44FJFnB.js +0 -1
  266. package/ui-dist/assets/index-noipz8bE.css +0 -1
  267. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-B94IPtHC.js +0 -1
  268. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-9WQr2_E9.js +0 -8
@@ -0,0 +1,964 @@
1
+ import crypto from "node:crypto";
2
+ import { execFile } from "node:child_process";
3
+ import { existsSync } from "node:fs";
4
+ import fs from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { promisify } from "node:util";
7
+ import { fileURLToPath } from "node:url";
8
+ const execFileAsync = promisify(execFile);
9
+ /**
10
+ * Node comparator for the v2 backup contract. The archive reader deliberately
11
+ * supports only the uncompressed ZIP dialect emitted by this module. That
12
+ * keeps the reader bounded and makes unsupported compression fail closed rather
13
+ * than silently allowing an unbounded inflate path.
14
+ */
15
+ export const WORKSPACE_BACKUP_V2_VERSION = 2;
16
+ export const WORKSPACE_BACKUP_V2_POLICY_VERSION = "workspace-backup-v2-policy-1";
17
+ export const WORKSPACE_BACKUP_V2_MANIFEST_PATH = ".rudder-backup/manifest-v2.json";
18
+ export const WORKSPACE_BACKUP_V2_MAX_FILE_BYTES = 5 * 1024 * 1024;
19
+ export const WORKSPACE_BACKUP_V2_MAX_TOTAL_BYTES = 100 * 1024 * 1024;
20
+ const WORKSPACE_BACKUP_V2_MAX_ARCHIVE_BYTES = WORKSPACE_BACKUP_V2_MAX_TOTAL_BYTES + 16 * 1024 * 1024;
21
+ const WORKSPACE_BACKUP_V2_MAX_CENTRAL_DIRECTORY_BYTES = 16 * 1024 * 1024;
22
+ const MAX_WARNING_COUNT = 200;
23
+ const EOCD_MIN_BYTES = 22;
24
+ const EOCD_MAX_SCAN_BYTES = 65_557;
25
+ const ZIP_LOCAL_HEADER_BYTES = 30;
26
+ const ZIP_CENTRAL_HEADER_BYTES = 46;
27
+ const NATIVE_PROTOCOL_VERSION = 1;
28
+ const NATIVE_OUTPUT_LIMIT_BYTES = 256 * 1024;
29
+ const NATIVE_DIAGNOSTIC_DETAIL_BYTES = 180;
30
+ const SKIPPED_ENTRY_NAMES = new Set([
31
+ ".DS_Store", ".cache", ".codex", ".config", ".git", ".gstack", ".local",
32
+ ".mintlify", ".npm", ".nvm", ".pnpm-store", ".rudder", ".tmp", ".turbo",
33
+ ".vite", "Library", "node_modules",
34
+ ]);
35
+ export class WorkspaceBackupV2NativeError extends Error {
36
+ diagnostic;
37
+ constructor(diagnostic) {
38
+ super(`Native archive ${diagnostic.category}/${diagnostic.code}: ${diagnostic.detail}`);
39
+ this.name = "WorkspaceBackupV2NativeError";
40
+ this.diagnostic = diagnostic;
41
+ }
42
+ }
43
+ export function workspaceBackupV2NativeDiagnostic(error) {
44
+ if (error instanceof WorkspaceBackupV2NativeError)
45
+ return error.diagnostic;
46
+ return nativeDiagnostic("process", "unexpected_error", error instanceof Error ? error.message : String(error)).diagnostic;
47
+ }
48
+ export function formatWorkspaceBackupV2NativeFallback(error) {
49
+ const diagnostic = workspaceBackupV2NativeDiagnostic(error);
50
+ return `Native archive fallback [${diagnostic.category}/${diagnostic.code}]: ${diagnostic.detail}`;
51
+ }
52
+ function sha256(value) {
53
+ return crypto.createHash("sha256").update(value).digest("hex");
54
+ }
55
+ function crc32(buffer) {
56
+ let value = 0xffffffff;
57
+ for (const byte of buffer) {
58
+ let current = (value ^ byte) & 0xff;
59
+ for (let bit = 0; bit < 8; bit += 1)
60
+ current = (current & 1) ? 0xedb88320 ^ (current >>> 1) : current >>> 1;
61
+ value = (value >>> 8) ^ current;
62
+ }
63
+ return (value ^ 0xffffffff) >>> 0;
64
+ }
65
+ function u16(value) {
66
+ const result = Buffer.allocUnsafe(2);
67
+ result.writeUInt16LE(value & 0xffff, 0);
68
+ return result;
69
+ }
70
+ function u32(value) {
71
+ const result = Buffer.allocUnsafe(4);
72
+ result.writeUInt32LE(value >>> 0, 0);
73
+ return result;
74
+ }
75
+ function dosDateTime(date) {
76
+ const year = Math.max(1980, date.getFullYear());
77
+ return {
78
+ time: (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2),
79
+ date: ((year - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate(),
80
+ };
81
+ }
82
+ function compareEntries(left, right) {
83
+ return left.path < right.path ? -1 : left.path > right.path ? 1 : 0;
84
+ }
85
+ function addWarning(warnings, value) {
86
+ if (warnings.length < MAX_WARNING_COUNT)
87
+ warnings.push(value);
88
+ else if (warnings.length === MAX_WARNING_COUNT)
89
+ warnings.push("Additional backup warnings omitted.");
90
+ }
91
+ function isSkippedEntryName(name) {
92
+ return SKIPPED_ENTRY_NAMES.has(name)
93
+ || name.endsWith("~") || name.endsWith(".swp") || name.endsWith(".swo")
94
+ || name.endsWith(".partial") || name.endsWith(".crdownload") || /\.tmp(?:[-.]|$)/.test(name);
95
+ }
96
+ function portableRelativePath(value) {
97
+ return value.split(path.sep).join("/");
98
+ }
99
+ export function assertSafeWorkspaceBackupV2Path(value) {
100
+ if (!value || value.includes("\0"))
101
+ throw new Error("Backup path must be non-empty and NUL-free");
102
+ const normalized = portableRelativePath(value);
103
+ if (normalized.includes("\\") || normalized.startsWith("/") || normalized.split("/").some((part) => part === "..")) {
104
+ throw new Error("Backup path must be relative and stay inside the workspace");
105
+ }
106
+ const parts = normalized.split("/").filter(Boolean);
107
+ if (parts.some((part) => part === "."))
108
+ throw new Error("Backup path must be canonical");
109
+ return parts.join("/");
110
+ }
111
+ function treeHash(entries) {
112
+ const hash = crypto.createHash("sha256");
113
+ for (const entry of [...entries].sort(compareEntries)) {
114
+ hash.update(entry.path);
115
+ hash.update("\0");
116
+ hash.update(entry.kind);
117
+ hash.update("\0");
118
+ hash.update(String(entry.byteSize));
119
+ hash.update("\0");
120
+ hash.update(entry.sha256 ?? "");
121
+ hash.update("\n");
122
+ }
123
+ return hash.digest("hex");
124
+ }
125
+ async function readBoundedFile(filePath, expectedSize) {
126
+ const handle = await fs.open(filePath, "r");
127
+ const chunks = [];
128
+ let total = 0;
129
+ const chunk = Buffer.allocUnsafe(256 * 1024);
130
+ try {
131
+ while (true) {
132
+ const result = await handle.read(chunk, 0, chunk.byteLength, null);
133
+ if (!result.bytesRead)
134
+ break;
135
+ total += result.bytesRead;
136
+ if (total > WORKSPACE_BACKUP_V2_MAX_FILE_BYTES)
137
+ throw new Error("file exceeds v2 backup limit");
138
+ chunks.push(Buffer.from(chunk.subarray(0, result.bytesRead)));
139
+ }
140
+ }
141
+ finally {
142
+ await handle.close();
143
+ }
144
+ if (total !== expectedSize)
145
+ throw new Error("file changed while creating v2 backup");
146
+ return Buffer.concat(chunks, total);
147
+ }
148
+ export async function walkWorkspaceBackupV2(rootPath) {
149
+ const root = path.resolve(rootPath);
150
+ const entries = [];
151
+ const warnings = [];
152
+ let byteSize = 0;
153
+ const queue = [root];
154
+ while (queue.length) {
155
+ const currentPath = queue.shift();
156
+ const dirents = await fs.readdir(currentPath, { withFileTypes: true });
157
+ dirents.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
158
+ for (const dirent of dirents) {
159
+ const absolutePath = path.join(currentPath, dirent.name);
160
+ const relativePath = assertSafeWorkspaceBackupV2Path(portableRelativePath(path.relative(root, absolutePath)));
161
+ if (isSkippedEntryName(dirent.name)) {
162
+ addWarning(warnings, `Skipped ${relativePath}`);
163
+ continue;
164
+ }
165
+ const stat = await fs.lstat(absolutePath);
166
+ if (stat.isSymbolicLink()) {
167
+ addWarning(warnings, `Skipped symlink ${relativePath}`);
168
+ continue;
169
+ }
170
+ if (stat.isDirectory()) {
171
+ entries.push({ path: relativePath, kind: "directory", byteSize: 0, mtimeMs: stat.mtimeMs, mode: stat.mode, sha256: null });
172
+ queue.push(absolutePath);
173
+ continue;
174
+ }
175
+ if (!stat.isFile()) {
176
+ addWarning(warnings, `Skipped unsupported file ${relativePath}`);
177
+ continue;
178
+ }
179
+ if (stat.size > WORKSPACE_BACKUP_V2_MAX_FILE_BYTES) {
180
+ addWarning(warnings, `Skipped oversized file ${relativePath}`);
181
+ continue;
182
+ }
183
+ if (byteSize + stat.size > WORKSPACE_BACKUP_V2_MAX_TOTAL_BYTES) {
184
+ addWarning(warnings, `Skipped ${relativePath} because the backup size limit was reached`);
185
+ continue;
186
+ }
187
+ const data = await readBoundedFile(absolutePath, stat.size);
188
+ byteSize += data.byteLength;
189
+ entries.push({ path: relativePath, kind: "file", byteSize: data.byteLength, mtimeMs: stat.mtimeMs, mode: stat.mode, sha256: sha256(data) });
190
+ }
191
+ }
192
+ const sorted = entries.sort(compareEntries);
193
+ return { entries: sorted, warnings, fileCount: sorted.filter((entry) => entry.kind === "file").length, byteSize, treeSha256: treeHash(sorted) };
194
+ }
195
+ function zipEntry(pathName, data, mtimeMs, directory, offset) {
196
+ const name = Buffer.from(pathName, "utf8");
197
+ const { time, date } = dosDateTime(new Date(mtimeMs ?? 0));
198
+ const checksum = crc32(data);
199
+ const local = Buffer.concat([u32(0x04034b50), u16(20), u16(0x0800), u16(0), u16(time), u16(date), u32(checksum), u32(data.length), u32(data.length), u16(name.length), u16(0), name, data]);
200
+ const central = Buffer.concat([u32(0x02014b50), u16(20), u16(20), u16(0x0800), u16(0), u16(time), u16(date), u32(checksum), u32(data.length), u32(data.length), u16(name.length), u16(0), u16(0), u16(0), u16(0), u32(directory ? 0x10 : 0), u32(offset), name]);
201
+ return { local, central };
202
+ }
203
+ function rootSegment(rootPath) {
204
+ return path.basename(rootPath).replaceAll("\\", "/").split("/").filter(Boolean).join("-") || "workspace";
205
+ }
206
+ export async function createWorkspaceBackupV2(input) {
207
+ const walked = await walkWorkspaceBackupV2(input.rootPath);
208
+ const manifest = {
209
+ version: WORKSPACE_BACKUP_V2_VERSION,
210
+ policyVersion: WORKSPACE_BACKUP_V2_POLICY_VERSION,
211
+ identity: { orgId: input.orgId, instanceId: input.instanceId, rootPath: path.resolve(input.rootPath) },
212
+ createdAt: (input.createdAt ?? new Date()).toISOString(),
213
+ entries: walked.entries,
214
+ treeSha256: walked.treeSha256,
215
+ warnings: walked.warnings,
216
+ };
217
+ const local = [];
218
+ const central = [];
219
+ let offset = 0;
220
+ const add = (name, data, mtimeMs, directory = false) => {
221
+ const parts = zipEntry(name, data, mtimeMs, directory, offset);
222
+ local.push(parts.local);
223
+ central.push(parts.central);
224
+ offset += parts.local.length;
225
+ };
226
+ const root = rootSegment(input.rootPath);
227
+ add(`${WORKSPACE_BACKUP_V2_MANIFEST_PATH}`, Buffer.from(JSON.stringify(manifest)), new Date(manifest.createdAt).getTime());
228
+ add(`${root}/`, Buffer.alloc(0), new Date(manifest.createdAt).getTime(), true);
229
+ for (const entry of walked.entries) {
230
+ const name = `${root}/${entry.path}${entry.kind === "directory" ? "/" : ""}`;
231
+ const data = entry.kind === "file" ? await readBoundedFile(path.join(input.rootPath, entry.path), entry.byteSize) : Buffer.alloc(0);
232
+ add(name, data, entry.mtimeMs, entry.kind === "directory");
233
+ }
234
+ const directory = Buffer.concat(central);
235
+ const entryCount = 2 + walked.entries.length;
236
+ if (entryCount > 0xffff || directory.length > 0xffffffff || offset > 0xffffffff)
237
+ throw new Error("v2 backup exceeds classic ZIP limits");
238
+ const end = Buffer.concat([u32(0x06054b50), u16(0), u16(0), u16(entryCount), u16(entryCount), u32(directory.length), u32(offset), u16(0)]);
239
+ return { ...walked, manifest, archive: Buffer.concat([...local, directory, end]) };
240
+ }
241
+ /**
242
+ * Contract-equivalent Node comparator for the native writer. It keeps only a
243
+ * bounded per-file Buffer and writes local records sequentially, so the full
244
+ * archive is never materialized in the JS heap.
245
+ */
246
+ export async function createWorkspaceBackupV2File(input) {
247
+ const walked = await walkWorkspaceBackupV2(input.rootPath);
248
+ if (input.additionalWarnings?.length)
249
+ walked.warnings.unshift(...input.additionalWarnings);
250
+ const manifest = {
251
+ version: WORKSPACE_BACKUP_V2_VERSION,
252
+ policyVersion: WORKSPACE_BACKUP_V2_POLICY_VERSION,
253
+ identity: { orgId: input.orgId, instanceId: input.instanceId, rootPath: path.resolve(input.rootPath) },
254
+ createdAt: (input.createdAt ?? new Date()).toISOString(),
255
+ entries: walked.entries,
256
+ treeSha256: walked.treeSha256,
257
+ warnings: walked.warnings,
258
+ };
259
+ const tempPath = `${input.artifactPath}.${crypto.randomUUID()}.tmp`;
260
+ await fs.mkdir(path.dirname(input.artifactPath), { recursive: true });
261
+ if (await fs.stat(input.artifactPath).then(() => true).catch(() => false))
262
+ throw new Error("v2 backup output already exists");
263
+ const handle = await fs.open(tempPath, "wx", 0o600);
264
+ const archiveHash = crypto.createHash("sha256");
265
+ const central = [];
266
+ let offset = 0;
267
+ const write = async (chunk) => {
268
+ let written = 0;
269
+ while (written < chunk.length) {
270
+ const result = await handle.write(chunk, written, chunk.length - written);
271
+ if (!result.bytesWritten)
272
+ throw new Error("Node v2 archive writer made no progress");
273
+ written += result.bytesWritten;
274
+ archiveHash.update(chunk.subarray(written - result.bytesWritten, written));
275
+ }
276
+ };
277
+ try {
278
+ const root = rootSegment(input.rootPath);
279
+ const manifestData = Buffer.from(JSON.stringify(manifest));
280
+ const records = [];
281
+ const writeRecord = async (record) => {
282
+ const parts = zipEntry(record.name, record.data, record.mtimeMs, record.directory, offset);
283
+ const localHeaderSize = parts.local.length - record.data.length;
284
+ await write(parts.local.subarray(0, localHeaderSize));
285
+ if (record.data.length)
286
+ await write(record.data);
287
+ central.push(parts.central);
288
+ offset += parts.local.length;
289
+ records.push({ ...record, data: Buffer.alloc(0) });
290
+ };
291
+ await writeRecord({ name: WORKSPACE_BACKUP_V2_MANIFEST_PATH, data: manifestData, mtimeMs: new Date(manifest.createdAt).getTime(), directory: false });
292
+ await writeRecord({ name: `${root}/`, data: Buffer.alloc(0), mtimeMs: new Date(manifest.createdAt).getTime(), directory: true });
293
+ for (const entry of walked.entries) {
294
+ const data = entry.kind === "file" ? await readBoundedFile(path.join(input.rootPath, entry.path), entry.byteSize) : Buffer.alloc(0);
295
+ await writeRecord({
296
+ name: `${root}/${entry.path}${entry.kind === "directory" ? "/" : ""}`,
297
+ data,
298
+ mtimeMs: entry.mtimeMs,
299
+ directory: entry.kind === "directory",
300
+ });
301
+ }
302
+ const directory = Buffer.concat(central);
303
+ const entryCount = records.length;
304
+ if (entryCount > 0xffff || directory.length > 0xffffffff || offset > 0xffffffff)
305
+ throw new Error("v2 backup exceeds classic ZIP limits");
306
+ await write(directory);
307
+ const end = Buffer.concat([u32(0x06054b50), u16(0), u16(0), u16(entryCount), u16(entryCount), u32(directory.length), u32(offset), u16(0)]);
308
+ await write(end);
309
+ if (offset + directory.length + end.length > WORKSPACE_BACKUP_V2_MAX_ARCHIVE_BYTES)
310
+ throw new Error("v2 backup archive exceeds the bounded archive limit");
311
+ await handle.sync();
312
+ const archiveSha256 = archiveHash.digest("hex");
313
+ await handle.close();
314
+ await input.beforePublish?.();
315
+ await publishNoReplace(tempPath, input.artifactPath, input.publicationOps);
316
+ const stat = await fs.stat(input.artifactPath);
317
+ return { ...walked, artifactPath: input.artifactPath, archiveSha256, compressedSize: stat.size, manifest };
318
+ }
319
+ catch (error) {
320
+ await handle.close().catch(() => undefined);
321
+ await fs.rm(tempPath, { force: true }).catch(() => undefined);
322
+ throw error;
323
+ }
324
+ }
325
+ async function syncParent(filePath) {
326
+ const parent = await fs.open(path.dirname(filePath), "r");
327
+ try {
328
+ await parent.sync();
329
+ }
330
+ finally {
331
+ await parent.close();
332
+ }
333
+ }
334
+ const defaultPublicationOps = {
335
+ link: async (source, destination) => { await fs.link(source, destination); },
336
+ rm: async (filePath) => { await fs.rm(filePath); },
337
+ syncParent,
338
+ };
339
+ async function publishNoReplace(tempPath, finalPath, overrides) {
340
+ const ops = overrides ?? defaultPublicationOps;
341
+ try {
342
+ await ops.link(tempPath, finalPath);
343
+ }
344
+ catch (error) {
345
+ if (error.code === "EEXIST")
346
+ throw nativeDiagnostic("publication", "final_exists", "destination already exists", false);
347
+ throw error;
348
+ }
349
+ try {
350
+ await ops.syncParent(finalPath);
351
+ }
352
+ catch (error) {
353
+ try {
354
+ await ops.rm(finalPath);
355
+ await ops.syncParent(finalPath);
356
+ }
357
+ catch (rollbackError) {
358
+ throw nativeDiagnostic("publication", "publication_recovery_required", rollbackError, false);
359
+ }
360
+ throw nativeDiagnostic("publication", "output_publish_failed", error, false);
361
+ }
362
+ try {
363
+ await ops.rm(tempPath);
364
+ await ops.syncParent(finalPath);
365
+ }
366
+ catch (error) {
367
+ throw nativeDiagnostic("publication", "published_output_cleanup_failed", error, false);
368
+ }
369
+ }
370
+ async function removePublishedForFallback(finalPath, overrides) {
371
+ const ops = overrides ?? defaultPublicationOps;
372
+ try {
373
+ await ops.rm(finalPath);
374
+ await ops.syncParent(finalPath);
375
+ }
376
+ catch (error) {
377
+ throw nativeDiagnostic("publication", "publication_recovery_required", error, false);
378
+ }
379
+ }
380
+ function boundedDiagnosticDetail(value) {
381
+ const text = String(value ?? "unknown").replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim();
382
+ return text.slice(0, NATIVE_DIAGNOSTIC_DETAIL_BYTES) || "unknown";
383
+ }
384
+ function nativeDiagnostic(category, code, detail, fallbackAllowed = category !== "publication") {
385
+ return new WorkspaceBackupV2NativeError({
386
+ category,
387
+ code: boundedDiagnosticDetail(code),
388
+ detail: boundedDiagnosticDetail(detail),
389
+ fallbackAllowed,
390
+ });
391
+ }
392
+ function nativeTarget() {
393
+ if (process.platform === "darwin")
394
+ return process.arch === "arm64" ? "aarch64-apple-darwin" : process.arch === "x64" ? "x86_64-apple-darwin" : null;
395
+ if (process.platform === "linux")
396
+ return process.arch === "arm64" ? "aarch64-unknown-linux-gnu" : process.arch === "x64" ? "x86_64-unknown-linux-gnu" : null;
397
+ if (process.platform === "win32")
398
+ return process.arch === "arm64" ? "aarch64-pc-windows-msvc" : process.arch === "x64" ? "x86_64-pc-windows-msvc" : null;
399
+ return null;
400
+ }
401
+ export function resolveNativeArchiveBinary() {
402
+ const configured = process.env.RUDDER_NATIVE_ARCHIVE_PATH?.trim();
403
+ if (configured)
404
+ return path.resolve(configured);
405
+ const binaryName = process.platform === "win32" ? "rudder-native.exe" : "rudder-native";
406
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
407
+ const target = nativeTarget();
408
+ const electronProcess = process;
409
+ const resourcesPath = process.env.RUDDER_DESKTOP_RESOURCES_PATH?.trim()
410
+ || (typeof electronProcess.resourcesPath === "string" ? electronProcess.resourcesPath : "");
411
+ const candidates = [
412
+ path.resolve(moduleDir, "../../../native/target/debug", binaryName),
413
+ path.resolve(moduleDir, "../../../../native/target/debug", binaryName),
414
+ path.resolve(moduleDir, "../../../native", target ?? "unsupported", binaryName),
415
+ resourcesPath && target ? path.resolve(resourcesPath, "native", target, binaryName) : "",
416
+ ].filter(Boolean);
417
+ return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0];
418
+ }
419
+ async function sha256FileBounded(filePath) {
420
+ const handle = await fs.open(filePath, "r");
421
+ const hash = crypto.createHash("sha256");
422
+ const chunk = Buffer.allocUnsafe(256 * 1024);
423
+ try {
424
+ while (true) {
425
+ const { bytesRead } = await handle.read(chunk, 0, chunk.length, null);
426
+ if (!bytesRead)
427
+ break;
428
+ hash.update(chunk.subarray(0, bytesRead));
429
+ }
430
+ }
431
+ finally {
432
+ await handle.close();
433
+ }
434
+ return hash.digest("hex");
435
+ }
436
+ async function runNativeArchive(binary, args, timeoutMs = Number(process.env.RUDDER_NATIVE_ARCHIVE_TIMEOUT_MS) || 30_000) {
437
+ let result;
438
+ try {
439
+ result = await execFileAsync(binary, args, {
440
+ encoding: "utf8",
441
+ timeout: timeoutMs,
442
+ maxBuffer: NATIVE_OUTPUT_LIMIT_BYTES,
443
+ windowsHide: true,
444
+ });
445
+ }
446
+ catch (error) {
447
+ const maybe = error;
448
+ if (maybe.killed || maybe.signal === "SIGTERM" || maybe.code === "ETIMEDOUT") {
449
+ throw nativeDiagnostic("timeout", "process_timeout", `${timeoutMs}ms`);
450
+ }
451
+ if (maybe.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
452
+ throw nativeDiagnostic("protocol", "output_limit", `${NATIVE_OUTPUT_LIMIT_BYTES} bytes`);
453
+ }
454
+ const stdout = typeof maybe.stdout === "string" ? maybe.stdout : "";
455
+ const stderr = typeof maybe.stderr === "string" ? maybe.stderr : "";
456
+ const lines = stdout.trim().split(/\r?\n/).filter(Boolean);
457
+ if (lines.length === 1) {
458
+ try {
459
+ const parsed = JSON.parse(lines[0]);
460
+ if (parsed && typeof parsed === "object" && typeof parsed.errorCode === "string") {
461
+ const publication = ["published_output_cleanup_failed", "publication_recovery_required"].includes(parsed.errorCode);
462
+ throw nativeDiagnostic(publication ? "publication" : "process", parsed.errorCode, stderr || `exit ${String(maybe.code ?? "unknown")}`, !publication);
463
+ }
464
+ }
465
+ catch (parsedError) {
466
+ if (parsedError instanceof WorkspaceBackupV2NativeError)
467
+ throw parsedError;
468
+ }
469
+ }
470
+ throw nativeDiagnostic("process", "nonzero_exit", `${String(maybe.code ?? "unknown")}: ${stderr}`);
471
+ }
472
+ if (result.stderr.trim())
473
+ throw nativeDiagnostic("protocol", "unexpected_stderr", result.stderr);
474
+ const lines = result.stdout.trim().split(/\r?\n/).filter(Boolean);
475
+ if (lines.length !== 1)
476
+ throw nativeDiagnostic("protocol", "response_line_count", lines.length);
477
+ let parsed;
478
+ try {
479
+ parsed = JSON.parse(lines[0]);
480
+ }
481
+ catch {
482
+ throw nativeDiagnostic("protocol", "malformed_json", lines[0]);
483
+ }
484
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
485
+ throw nativeDiagnostic("protocol", "malformed_envelope", typeof parsed);
486
+ return parsed;
487
+ }
488
+ function requireNativeString(value, label) {
489
+ if (typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value))
490
+ throw nativeDiagnostic("integrity", `invalid_${label.replaceAll(" ", "_")}`, value);
491
+ return value;
492
+ }
493
+ export async function createWorkspaceBackupV2Native(input) {
494
+ const walked = await walkWorkspaceBackupV2(input.rootPath);
495
+ const manifest = {
496
+ version: WORKSPACE_BACKUP_V2_VERSION,
497
+ policyVersion: WORKSPACE_BACKUP_V2_POLICY_VERSION,
498
+ identity: { orgId: input.orgId, instanceId: input.instanceId, rootPath: path.resolve(input.rootPath) },
499
+ createdAt: (input.createdAt ?? new Date()).toISOString(),
500
+ entries: walked.entries,
501
+ treeSha256: walked.treeSha256,
502
+ warnings: walked.warnings,
503
+ };
504
+ const staging = await fs.mkdtemp(path.join(path.dirname(input.artifactPath), ".rudder-native-archive-"));
505
+ const manifestPath = path.join(staging, "manifest-v2.json");
506
+ const planPath = path.join(staging, "create-plan.json");
507
+ const nativeOutput = path.join(staging, "archive.zip");
508
+ try {
509
+ await fs.writeFile(manifestPath, JSON.stringify(manifest), { mode: 0o600 });
510
+ const entries = [
511
+ { kind: "directory", archivePath: `${rootSegment(input.rootPath)}/` },
512
+ ...walked.entries.map((entry) => entry.kind === "directory"
513
+ ? { kind: "directory", archivePath: `${rootSegment(input.rootPath)}/${entry.path}/` }
514
+ : { kind: "file", archivePath: `${rootSegment(input.rootPath)}/${entry.path}`, sourcePath: path.join(input.rootPath, entry.path) }),
515
+ ];
516
+ await fs.writeFile(planPath, JSON.stringify({
517
+ protocolVersion: 1,
518
+ manifestSource: manifestPath,
519
+ treeSha256: manifest.treeSha256,
520
+ entries,
521
+ }), { mode: 0o600 });
522
+ const binary = resolveNativeArchiveBinary();
523
+ const capabilities = await runNativeArchive(binary, ["archive", "capabilities"]);
524
+ if (capabilities.ok !== true || !Array.isArray(capabilities.capabilities) || !capabilities.capabilities.includes("archive.create")) {
525
+ throw nativeDiagnostic("capability", "create_unavailable", JSON.stringify(capabilities.capabilities));
526
+ }
527
+ if (capabilities.protocolVersion !== NATIVE_PROTOCOL_VERSION) {
528
+ throw nativeDiagnostic("protocol", "version_mismatch", `${String(capabilities.protocolVersion)} != ${NATIVE_PROTOCOL_VERSION}`);
529
+ }
530
+ const response = await runNativeArchive(binary, [
531
+ "archive", "create", planPath, nativeOutput,
532
+ String(WORKSPACE_BACKUP_V2_MAX_ARCHIVE_BYTES),
533
+ String(WORKSPACE_BACKUP_V2_MAX_FILE_BYTES),
534
+ String(WORKSPACE_BACKUP_V2_MAX_TOTAL_BYTES),
535
+ ]);
536
+ if (response.ok !== true) {
537
+ const code = typeof response.errorCode === "string" ? response.errorCode : "create_failed";
538
+ const publication = ["published_output_cleanup_failed", "publication_recovery_required"].includes(code);
539
+ throw nativeDiagnostic(publication ? "publication" : "process", code, "create response", !publication);
540
+ }
541
+ if (response.operation !== "create" || response.protocolVersion !== NATIVE_PROTOCOL_VERSION) {
542
+ throw nativeDiagnostic("protocol", "create_envelope_mismatch", `${String(response.operation)}/${String(response.protocolVersion)}`);
543
+ }
544
+ const archiveSha256 = requireNativeString(response.sha256, "archive hash");
545
+ const manifestSha256 = requireNativeString(response.manifestSha256, "manifest hash");
546
+ if (response.treeSha256 !== manifest.treeSha256 || response.manifestSha256 !== sha256(JSON.stringify(manifest))) {
547
+ throw nativeDiagnostic("integrity", "manifest_mismatch", "native evidence differs from TypeScript manifest");
548
+ }
549
+ const stat = await fs.stat(nativeOutput);
550
+ if (!stat.isFile() || stat.size !== response.byteSize || await sha256FileBounded(nativeOutput) !== archiveSha256) {
551
+ throw nativeDiagnostic("integrity", "output_mismatch", "size or SHA-256 differs from native evidence");
552
+ }
553
+ await fs.mkdir(path.dirname(input.artifactPath), { recursive: true });
554
+ if (await fs.stat(input.artifactPath).then(() => true).catch(() => false)) {
555
+ throw nativeDiagnostic("publication", "final_exists", "destination already exists", false);
556
+ }
557
+ await input.beforePublish?.();
558
+ await publishNoReplace(nativeOutput, input.artifactPath, input.publicationOps);
559
+ let index;
560
+ try {
561
+ index = await inspectWorkspaceBackupV2File(input.artifactPath);
562
+ }
563
+ catch (error) {
564
+ try {
565
+ await removePublishedForFallback(input.artifactPath, input.publicationOps);
566
+ }
567
+ catch (cleanupError) {
568
+ if (cleanupError instanceof WorkspaceBackupV2NativeError)
569
+ throw cleanupError;
570
+ throw nativeDiagnostic("publication", "published_output_cleanup_failed", cleanupError, false);
571
+ }
572
+ throw nativeDiagnostic("integrity", "published_archive_invalid", error instanceof Error ? error.message : error);
573
+ }
574
+ if (index.manifest.identity.orgId !== input.orgId || index.manifest.identity.instanceId !== input.instanceId || index.manifest.treeSha256 !== manifest.treeSha256) {
575
+ try {
576
+ await removePublishedForFallback(input.artifactPath, input.publicationOps);
577
+ }
578
+ catch (cleanupError) {
579
+ if (cleanupError instanceof WorkspaceBackupV2NativeError)
580
+ throw cleanupError;
581
+ throw nativeDiagnostic("publication", "published_output_cleanup_failed", cleanupError, false);
582
+ }
583
+ throw nativeDiagnostic("integrity", "published_manifest_mismatch", "published archive identity differs");
584
+ }
585
+ return { ...walked, artifactPath: input.artifactPath, archiveSha256, manifest };
586
+ }
587
+ finally {
588
+ await fs.rm(staging, { recursive: true, force: true });
589
+ }
590
+ }
591
+ function decodeUtf8(value, label) {
592
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(value);
593
+ if (text.includes("\0"))
594
+ throw new Error(`${label} contains NUL`);
595
+ return text;
596
+ }
597
+ function findEocd(archive) {
598
+ const start = Math.max(0, archive.byteLength - EOCD_MAX_SCAN_BYTES);
599
+ for (let offset = archive.byteLength - EOCD_MIN_BYTES; offset >= start; offset -= 1) {
600
+ if (archive[offset] === 0x50 && archive[offset + 1] === 0x4b && archive[offset + 2] === 0x05 && archive[offset + 3] === 0x06) {
601
+ return offset;
602
+ }
603
+ }
604
+ throw new Error("v2 backup ZIP end record is missing");
605
+ }
606
+ function parseZipRecords(archive) {
607
+ if (archive.byteLength > WORKSPACE_BACKUP_V2_MAX_ARCHIVE_BYTES)
608
+ throw new Error("v2 backup archive exceeds the bounded archive limit");
609
+ const eocd = findEocd(archive);
610
+ const commentLength = archive[eocd + 20] | (archive[eocd + 21] << 8);
611
+ if (eocd + EOCD_MIN_BYTES + commentLength !== archive.byteLength)
612
+ throw new Error("v2 backup ZIP has trailing bytes");
613
+ const disk = archive[eocd + 4] | (archive[eocd + 5] << 8);
614
+ const centralDisk = archive[eocd + 6] | (archive[eocd + 7] << 8);
615
+ const entriesOnDisk = archive[eocd + 8] | (archive[eocd + 9] << 8);
616
+ const entryCount = archive[eocd + 10] | (archive[eocd + 11] << 8);
617
+ const centralSize = archive[eocd + 12] | (archive[eocd + 13] << 8) | (archive[eocd + 14] << 16) | (archive[eocd + 15] << 24);
618
+ const centralOffset = archive[eocd + 16] | (archive[eocd + 17] << 8) | (archive[eocd + 18] << 16) | (archive[eocd + 19] << 24);
619
+ if (disk !== 0 || centralDisk !== 0 || entriesOnDisk !== entryCount || centralSize < 0 || centralSize > WORKSPACE_BACKUP_V2_MAX_CENTRAL_DIRECTORY_BYTES) {
620
+ throw new Error("v2 backup ZIP multi-disk or central-directory limits are unsupported");
621
+ }
622
+ if (entryCount === 0xffff || centralSize + centralOffset > eocd)
623
+ throw new Error("v2 backup ZIP uses unsupported ZIP64 or has an invalid central directory");
624
+ const records = [];
625
+ let cursor = centralOffset;
626
+ for (let index = 0; index < entryCount; index += 1) {
627
+ if (cursor + ZIP_CENTRAL_HEADER_BYTES > centralOffset + centralSize || archive[cursor] !== 0x50 || archive[cursor + 1] !== 0x4b || archive[cursor + 2] !== 0x01 || archive[cursor + 3] !== 0x02) {
628
+ throw new Error("v2 backup ZIP central directory is malformed");
629
+ }
630
+ const flags = archive[cursor + 8] | (archive[cursor + 9] << 8);
631
+ const method = archive[cursor + 10] | (archive[cursor + 11] << 8);
632
+ const crc = (archive[cursor + 16] | (archive[cursor + 17] << 8) | (archive[cursor + 18] << 16) | (archive[cursor + 19] << 24)) >>> 0;
633
+ const compressedSize = (archive[cursor + 20] | (archive[cursor + 21] << 8) | (archive[cursor + 22] << 16) | (archive[cursor + 23] << 24)) >>> 0;
634
+ const byteSize = (archive[cursor + 24] | (archive[cursor + 25] << 8) | (archive[cursor + 26] << 16) | (archive[cursor + 27] << 24)) >>> 0;
635
+ const nameLength = archive[cursor + 28] | (archive[cursor + 29] << 8);
636
+ const extraLength = archive[cursor + 30] | (archive[cursor + 31] << 8);
637
+ const commentLength = archive[cursor + 32] | (archive[cursor + 33] << 8);
638
+ const externalAttributes = (archive[cursor + 38] | (archive[cursor + 39] << 8) | (archive[cursor + 40] << 16) | (archive[cursor + 41] << 24)) >>> 0;
639
+ const localOffset = (archive[cursor + 42] | (archive[cursor + 43] << 8) | (archive[cursor + 44] << 16) | (archive[cursor + 45] << 24)) >>> 0;
640
+ const end = cursor + ZIP_CENTRAL_HEADER_BYTES + nameLength + extraLength + commentLength;
641
+ if (end > centralOffset + centralSize)
642
+ throw new Error("v2 backup ZIP central entry exceeds its directory");
643
+ const nameBytes = Buffer.from(archive.subarray(cursor + ZIP_CENTRAL_HEADER_BYTES, cursor + ZIP_CENTRAL_HEADER_BYTES + nameLength));
644
+ const archivePath = decodeUtf8(nameBytes, "v2 backup ZIP path");
645
+ if ((flags & 0x0001) !== 0 || (flags & 0x0008) !== 0 || method !== 0)
646
+ throw new Error(`v2 backup ZIP entry is encrypted, uses a descriptor, or has unsupported compression: ${archivePath}`);
647
+ if (compressedSize !== byteSize || byteSize > WORKSPACE_BACKUP_V2_MAX_FILE_BYTES)
648
+ throw new Error(`v2 backup ZIP entry exceeds safe uncompressed limits: ${archivePath}`);
649
+ const directory = archivePath.endsWith("/");
650
+ if (directory && byteSize !== 0)
651
+ throw new Error(`v2 backup directory has data: ${archivePath}`);
652
+ if (!directory && (externalAttributes & 0x10) !== 0)
653
+ throw new Error(`v2 backup file has directory attributes: ${archivePath}`);
654
+ if (localOffset + ZIP_LOCAL_HEADER_BYTES > centralOffset)
655
+ throw new Error(`v2 backup local entry offset is invalid: ${archivePath}`);
656
+ const local = archive.subarray(localOffset, localOffset + ZIP_LOCAL_HEADER_BYTES);
657
+ if (local[0] !== 0x50 || local[1] !== 0x4b || local[2] !== 0x03 || local[3] !== 0x04)
658
+ throw new Error(`v2 backup local entry is malformed: ${archivePath}`);
659
+ const localFlags = local[6] | (local[7] << 8);
660
+ const localMethod = local[8] | (local[9] << 8);
661
+ const localNameLength = local[26] | (local[27] << 8);
662
+ const localExtraLength = local[28] | (local[29] << 8);
663
+ const dataOffset = localOffset + ZIP_LOCAL_HEADER_BYTES + localNameLength + localExtraLength;
664
+ if (localFlags !== flags || localMethod !== method || !Buffer.from(archive.subarray(localOffset + ZIP_LOCAL_HEADER_BYTES, dataOffset)).equals(nameBytes) || dataOffset + compressedSize > centralOffset) {
665
+ throw new Error(`v2 backup local entry does not match its central directory: ${archivePath}`);
666
+ }
667
+ records.push({ archivePath, kind: directory ? "directory" : "file", compressedSize, byteSize, crc32: crc, dataOffset, flags, method, nameBytes });
668
+ cursor = end;
669
+ }
670
+ if (cursor !== centralOffset + centralSize)
671
+ throw new Error("v2 backup ZIP central directory has trailing records");
672
+ const names = new Set();
673
+ const foldedNames = new Set();
674
+ let totalBytes = 0;
675
+ for (const record of records) {
676
+ if (names.has(record.archivePath))
677
+ throw new Error(`duplicate v2 backup ZIP path: ${record.archivePath}`);
678
+ const folded = record.archivePath.toLowerCase();
679
+ if (foldedNames.has(folded))
680
+ throw new Error(`case-colliding v2 backup ZIP path: ${record.archivePath}`);
681
+ names.add(record.archivePath);
682
+ foldedNames.add(folded);
683
+ totalBytes += record.kind === "file" ? record.byteSize : 0;
684
+ if (totalBytes > WORKSPACE_BACKUP_V2_MAX_TOTAL_BYTES)
685
+ throw new Error("v2 backup ZIP exceeds the total uncompressed byte limit");
686
+ if (record.kind === "file")
687
+ assertSafeWorkspaceBackupV2Path(record.archivePath);
688
+ else if (!assertSafeWorkspaceBackupV2Path(record.archivePath.slice(0, -1)))
689
+ throw new Error(`invalid v2 backup directory path: ${record.archivePath}`);
690
+ }
691
+ return records;
692
+ }
693
+ function parseManifest(value) {
694
+ let manifest;
695
+ try {
696
+ manifest = JSON.parse(value.toString("utf8"));
697
+ }
698
+ catch {
699
+ throw new Error("v2 backup manifest is malformed");
700
+ }
701
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
702
+ throw new Error("v2 backup manifest is malformed");
703
+ const candidate = manifest;
704
+ if (candidate.version !== WORKSPACE_BACKUP_V2_VERSION || candidate.policyVersion !== WORKSPACE_BACKUP_V2_POLICY_VERSION || !Array.isArray(candidate.entries))
705
+ throw new Error("unsupported v2 backup manifest");
706
+ if (!candidate.identity || typeof candidate.identity !== "object" || Array.isArray(candidate.identity))
707
+ throw new Error("v2 backup manifest identity is malformed");
708
+ const identity = candidate.identity;
709
+ if (![identity.orgId, identity.instanceId, identity.rootPath].every((item) => typeof item === "string" && item.length > 0 && !item.includes("\0")))
710
+ throw new Error("v2 backup manifest identity is malformed");
711
+ if (typeof candidate.createdAt !== "string" || Number.isNaN(new Date(candidate.createdAt).getTime()) || !/^[a-f0-9]{64}$/.test(candidate.treeSha256 ?? "") || !Array.isArray(candidate.warnings))
712
+ throw new Error("v2 backup manifest metadata is malformed");
713
+ const entries = candidate.entries.map((entry) => {
714
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
715
+ throw new Error("v2 backup manifest entry is malformed");
716
+ const item = entry;
717
+ if (typeof item.path !== "string" || !["file", "directory"].includes(item.kind ?? "") || !Number.isSafeInteger(item.byteSize) || item.byteSize < 0 || item.byteSize > WORKSPACE_BACKUP_V2_MAX_FILE_BYTES)
718
+ throw new Error("v2 backup manifest entry is malformed");
719
+ assertSafeWorkspaceBackupV2Path(item.path);
720
+ if (item.kind === "directory" && (item.byteSize !== 0 || item.sha256 !== null))
721
+ throw new Error(`v2 backup directory metadata is malformed: ${item.path}`);
722
+ if (item.kind === "file" && (typeof item.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(item.sha256)))
723
+ throw new Error(`v2 backup file metadata is malformed: ${item.path}`);
724
+ return {
725
+ path: item.path,
726
+ kind: item.kind,
727
+ byteSize: item.byteSize,
728
+ mtimeMs: typeof item.mtimeMs === "number" || item.mtimeMs === null ? item.mtimeMs : null,
729
+ mode: typeof item.mode === "number" || item.mode === null ? item.mode : null,
730
+ sha256: item.sha256 ?? null,
731
+ };
732
+ });
733
+ const paths = new Set();
734
+ const foldedPaths = new Set();
735
+ for (const entry of entries) {
736
+ if (paths.has(entry.path))
737
+ throw new Error(`duplicate v2 backup manifest path: ${entry.path}`);
738
+ const folded = entry.path.toLowerCase();
739
+ if (foldedPaths.has(folded))
740
+ throw new Error(`case-colliding v2 backup manifest path: ${entry.path}`);
741
+ paths.add(entry.path);
742
+ foldedPaths.add(folded);
743
+ }
744
+ const result = { version: WORKSPACE_BACKUP_V2_VERSION, policyVersion: WORKSPACE_BACKUP_V2_POLICY_VERSION, identity: { orgId: identity.orgId, instanceId: identity.instanceId, rootPath: identity.rootPath }, createdAt: candidate.createdAt, entries, treeSha256: candidate.treeSha256, warnings: candidate.warnings.filter((warning) => typeof warning === "string") };
745
+ if (treeHash(result.entries) !== result.treeSha256)
746
+ throw new Error("v2 backup tree checksum mismatch");
747
+ return result;
748
+ }
749
+ function validateArchiveAgainstManifest(records, manifest, readEntry) {
750
+ const byName = new Map(records.map((record) => [record.archivePath, record]));
751
+ const root = rootSegment(manifest.identity.rootPath);
752
+ const expected = new Set([WORKSPACE_BACKUP_V2_MANIFEST_PATH, `${root}/`]);
753
+ const entries = new Map();
754
+ const manifestRecord = byName.get(WORKSPACE_BACKUP_V2_MANIFEST_PATH);
755
+ if (!manifestRecord || manifestRecord.kind !== "file")
756
+ throw new Error("v2 backup manifest is missing");
757
+ for (const entry of manifest.entries) {
758
+ const archivePath = `${root}/${entry.path}${entry.kind === "directory" ? "/" : ""}`;
759
+ expected.add(archivePath);
760
+ const record = byName.get(archivePath);
761
+ if (!record || record.kind !== entry.kind || record.byteSize !== entry.byteSize)
762
+ throw new Error(`v2 backup archive entry mismatch: ${entry.path}`);
763
+ if (entry.kind === "file" && readEntry) {
764
+ const data = readEntry(record);
765
+ if (sha256(data) !== entry.sha256)
766
+ throw new Error(`v2 backup checksum mismatch: ${entry.path}`);
767
+ }
768
+ entries.set(entry.path, record);
769
+ }
770
+ for (const record of records)
771
+ if (!expected.has(record.archivePath))
772
+ throw new Error(`unlisted v2 backup archive entry: ${record.archivePath}`);
773
+ return { archiveSize: 0, manifest, manifestEntry: manifestRecord, entries };
774
+ }
775
+ function readBufferEntry(archive, record) {
776
+ const data = Buffer.from(archive.subarray(record.dataOffset, record.dataOffset + record.byteSize));
777
+ if (crc32(data) !== record.crc32)
778
+ throw new Error(`v2 backup checksum mismatch: ${record.archivePath}`);
779
+ return data;
780
+ }
781
+ export function inspectWorkspaceBackupV2(archive) {
782
+ const records = parseZipRecords(archive);
783
+ const manifestRecord = records.find((record) => record.archivePath === WORKSPACE_BACKUP_V2_MANIFEST_PATH);
784
+ if (!manifestRecord)
785
+ throw new Error("v2 backup manifest is missing");
786
+ const manifest = parseManifest(readBufferEntry(archive, manifestRecord));
787
+ validateArchiveAgainstManifest(records, manifest, (record) => readBufferEntry(archive, record));
788
+ const files = new Map();
789
+ for (const entry of manifest.entries)
790
+ if (entry.kind === "file")
791
+ files.set(entry.path, readBufferEntry(archive, records.find((record) => record.archivePath === `${rootSegment(manifest.identity.rootPath)}/${entry.path}`)));
792
+ return { manifest, files };
793
+ }
794
+ async function readAt(handle, offset, length) {
795
+ const result = Buffer.alloc(length);
796
+ let position = 0;
797
+ while (position < length) {
798
+ const read = await handle.read(result, position, length - position, offset + position);
799
+ if (!read.bytesRead)
800
+ throw new Error("v2 backup ZIP ended before the requested range");
801
+ position += read.bytesRead;
802
+ }
803
+ return result;
804
+ }
805
+ function parseCentralDirectory(central, centralOffset, centralSize, entryCount) {
806
+ const records = [];
807
+ let cursor = 0;
808
+ for (let index = 0; index < entryCount; index += 1) {
809
+ if (cursor + ZIP_CENTRAL_HEADER_BYTES > central.length || central[cursor] !== 0x50 || central[cursor + 1] !== 0x4b || central[cursor + 2] !== 0x01 || central[cursor + 3] !== 0x02) {
810
+ throw new Error("v2 backup ZIP central directory is malformed");
811
+ }
812
+ const flags = central[cursor + 8] | (central[cursor + 9] << 8);
813
+ const method = central[cursor + 10] | (central[cursor + 11] << 8);
814
+ const crc = (central[cursor + 16] | (central[cursor + 17] << 8) | (central[cursor + 18] << 16) | (central[cursor + 19] << 24)) >>> 0;
815
+ const compressedSize = (central[cursor + 20] | (central[cursor + 21] << 8) | (central[cursor + 22] << 16) | (central[cursor + 23] << 24)) >>> 0;
816
+ const byteSize = (central[cursor + 24] | (central[cursor + 25] << 8) | (central[cursor + 26] << 16) | (central[cursor + 27] << 24)) >>> 0;
817
+ const nameLength = central[cursor + 28] | (central[cursor + 29] << 8);
818
+ const extraLength = central[cursor + 30] | (central[cursor + 31] << 8);
819
+ const commentLength = central[cursor + 32] | (central[cursor + 33] << 8);
820
+ const externalAttributes = (central[cursor + 38] | (central[cursor + 39] << 8) | (central[cursor + 40] << 16) | (central[cursor + 41] << 24)) >>> 0;
821
+ const localOffset = (central[cursor + 42] | (central[cursor + 43] << 8) | (central[cursor + 44] << 16) | (central[cursor + 45] << 24)) >>> 0;
822
+ const end = cursor + ZIP_CENTRAL_HEADER_BYTES + nameLength + extraLength + commentLength;
823
+ if (end > central.length)
824
+ throw new Error("v2 backup ZIP central entry exceeds its directory");
825
+ const nameBytes = Buffer.from(central.subarray(cursor + ZIP_CENTRAL_HEADER_BYTES, cursor + ZIP_CENTRAL_HEADER_BYTES + nameLength));
826
+ const archivePath = decodeUtf8(nameBytes, "v2 backup ZIP path");
827
+ if ((flags & 0x0001) !== 0 || (flags & 0x0008) !== 0 || method !== 0)
828
+ throw new Error(`v2 backup ZIP entry is encrypted, uses a descriptor, or has unsupported compression: ${archivePath}`);
829
+ if (compressedSize !== byteSize || byteSize > WORKSPACE_BACKUP_V2_MAX_FILE_BYTES)
830
+ throw new Error(`v2 backup ZIP entry exceeds safe uncompressed limits: ${archivePath}`);
831
+ const directory = archivePath.endsWith("/");
832
+ if (directory && byteSize !== 0)
833
+ throw new Error(`v2 backup directory has data: ${archivePath}`);
834
+ if (!directory && (externalAttributes & 0x10) !== 0)
835
+ throw new Error(`v2 backup file has directory attributes: ${archivePath}`);
836
+ if (localOffset + ZIP_LOCAL_HEADER_BYTES > centralOffset)
837
+ throw new Error(`v2 backup local entry offset is invalid: ${archivePath}`);
838
+ records.push({ archivePath, kind: directory ? "directory" : "file", compressedSize, byteSize, crc32: crc, dataOffset: localOffset, flags, method, nameBytes });
839
+ cursor = end;
840
+ }
841
+ if (cursor !== central.length)
842
+ throw new Error("v2 backup ZIP central directory has trailing records");
843
+ const names = new Set();
844
+ const foldedNames = new Set();
845
+ let totalBytes = 0;
846
+ for (const record of records) {
847
+ if (names.has(record.archivePath))
848
+ throw new Error(`duplicate v2 backup ZIP path: ${record.archivePath}`);
849
+ const folded = record.archivePath.toLowerCase();
850
+ if (foldedNames.has(folded))
851
+ throw new Error(`case-colliding v2 backup ZIP path: ${record.archivePath}`);
852
+ names.add(record.archivePath);
853
+ foldedNames.add(folded);
854
+ totalBytes += record.kind === "file" ? record.byteSize : 0;
855
+ if (totalBytes > WORKSPACE_BACKUP_V2_MAX_TOTAL_BYTES)
856
+ throw new Error("v2 backup ZIP exceeds the total uncompressed byte limit");
857
+ if (record.kind === "file")
858
+ assertSafeWorkspaceBackupV2Path(record.archivePath);
859
+ else
860
+ assertSafeWorkspaceBackupV2Path(record.archivePath.slice(0, -1));
861
+ }
862
+ return records;
863
+ }
864
+ async function parseZipRecordsFile(handle, archiveSize) {
865
+ if (archiveSize > WORKSPACE_BACKUP_V2_MAX_ARCHIVE_BYTES)
866
+ throw new Error("v2 backup archive exceeds the bounded archive limit");
867
+ const tailSize = Math.min(archiveSize, EOCD_MAX_SCAN_BYTES);
868
+ const tail = await readAt(handle, archiveSize - tailSize, tailSize);
869
+ const eocdOffset = findEocd(tail);
870
+ const absoluteEocd = archiveSize - tailSize + eocdOffset;
871
+ const commentLength = tail[eocdOffset + 20] | (tail[eocdOffset + 21] << 8);
872
+ if (absoluteEocd + EOCD_MIN_BYTES + commentLength !== archiveSize)
873
+ throw new Error("v2 backup ZIP has trailing bytes");
874
+ const entryCount = tail[eocdOffset + 10] | (tail[eocdOffset + 11] << 8);
875
+ const entriesOnDisk = tail[eocdOffset + 8] | (tail[eocdOffset + 9] << 8);
876
+ const disk = tail[eocdOffset + 4] | (tail[eocdOffset + 5] << 8);
877
+ const centralDisk = tail[eocdOffset + 6] | (tail[eocdOffset + 7] << 8);
878
+ const centralSize = (tail[eocdOffset + 12] | (tail[eocdOffset + 13] << 8) | (tail[eocdOffset + 14] << 16) | (tail[eocdOffset + 15] << 24)) >>> 0;
879
+ const centralOffset = (tail[eocdOffset + 16] | (tail[eocdOffset + 17] << 8) | (tail[eocdOffset + 18] << 16) | (tail[eocdOffset + 19] << 24)) >>> 0;
880
+ if (disk !== 0 || centralDisk !== 0 || entriesOnDisk !== entryCount || entryCount === 0xffff || centralSize > WORKSPACE_BACKUP_V2_MAX_CENTRAL_DIRECTORY_BYTES || centralOffset + centralSize > absoluteEocd)
881
+ throw new Error("v2 backup ZIP central directory is unsupported or invalid");
882
+ const central = await readAt(handle, centralOffset, centralSize);
883
+ const records = parseCentralDirectory(central, centralOffset, centralSize, entryCount);
884
+ for (const record of records) {
885
+ const local = await readAt(handle, record.dataOffset, ZIP_LOCAL_HEADER_BYTES);
886
+ if (local[0] !== 0x50 || local[1] !== 0x4b || local[2] !== 0x03 || local[3] !== 0x04)
887
+ throw new Error(`v2 backup local entry is malformed: ${record.archivePath}`);
888
+ const localFlags = local[6] | (local[7] << 8);
889
+ const localMethod = local[8] | (local[9] << 8);
890
+ const localNameLength = local[26] | (local[27] << 8);
891
+ const localExtraLength = local[28] | (local[29] << 8);
892
+ const name = await readAt(handle, record.dataOffset + ZIP_LOCAL_HEADER_BYTES, localNameLength + localExtraLength);
893
+ const dataOffset = record.dataOffset + ZIP_LOCAL_HEADER_BYTES + localNameLength + localExtraLength;
894
+ if (localFlags !== record.flags || localMethod !== record.method || !name.subarray(0, localNameLength).equals(record.nameBytes) || dataOffset + record.compressedSize > centralOffset) {
895
+ throw new Error(`v2 backup local entry does not match its central directory: ${record.archivePath}`);
896
+ }
897
+ record.dataOffset = dataOffset;
898
+ }
899
+ return records;
900
+ }
901
+ // Parse a file-backed archive without reading its local data records. The
902
+ // central directory is the only unbounded metadata surface and is capped above.
903
+ async function inspectWorkspaceBackupV2FileInternal(filePath) {
904
+ const stat = await fs.stat(filePath);
905
+ if (!stat.isFile())
906
+ throw new Error("v2 backup artifact is not a regular file");
907
+ const handle = await fs.open(filePath, "r");
908
+ try {
909
+ const records = await parseZipRecordsFile(handle, stat.size);
910
+ const manifestRecord = records.find((record) => record.archivePath === WORKSPACE_BACKUP_V2_MANIFEST_PATH);
911
+ if (!manifestRecord)
912
+ throw new Error("v2 backup manifest is missing");
913
+ const manifest = parseManifest(await readZipFileEntry(handle, manifestRecord));
914
+ const index = validateArchiveAgainstManifest(records, manifest);
915
+ // Manifest metadata is validated above. Per-file bytes are verified only on
916
+ // target read or restore, keeping list/browse bounded by the central dir.
917
+ for (const entry of manifest.entries) {
918
+ const record = index.entries.get(entry.path);
919
+ if (record.byteSize !== entry.byteSize)
920
+ throw new Error(`v2 backup archive entry mismatch: ${entry.path}`);
921
+ }
922
+ return { ...index, archiveSize: stat.size };
923
+ }
924
+ finally {
925
+ await handle.close();
926
+ }
927
+ }
928
+ async function readZipFileEntry(handle, record) {
929
+ if (record.byteSize > WORKSPACE_BACKUP_V2_MAX_FILE_BYTES)
930
+ throw new Error(`v2 backup entry exceeds safe read limit: ${record.archivePath}`);
931
+ const data = await readAt(handle, record.dataOffset, record.byteSize);
932
+ if (crc32(data) !== record.crc32)
933
+ throw new Error(`v2 backup checksum mismatch: ${record.archivePath}`);
934
+ return data;
935
+ }
936
+ export async function inspectWorkspaceBackupV2File(filePath) {
937
+ const index = await inspectWorkspaceBackupV2FileInternal(filePath);
938
+ const handle = await fs.open(filePath, "r");
939
+ try {
940
+ await readZipFileEntry(handle, index.manifestEntry);
941
+ return index;
942
+ }
943
+ finally {
944
+ await handle.close();
945
+ }
946
+ }
947
+ export async function readWorkspaceBackupV2File(filePath, index, relativePath) {
948
+ const normalized = assertSafeWorkspaceBackupV2Path(relativePath);
949
+ const record = index.entries.get(normalized);
950
+ if (!record || record.kind !== "file")
951
+ throw new Error(`v2 backup file not found: ${normalized}`);
952
+ const handle = await fs.open(filePath, "r");
953
+ try {
954
+ const data = await readZipFileEntry(handle, record);
955
+ const manifestEntry = index.manifest.entries.find((entry) => entry.path === normalized);
956
+ if (!manifestEntry || sha256(data) !== manifestEntry.sha256)
957
+ throw new Error(`v2 backup checksum mismatch: ${normalized}`);
958
+ return data;
959
+ }
960
+ finally {
961
+ await handle.close();
962
+ }
963
+ }
964
+ //# sourceMappingURL=workspace-backup-v2.js.map