@rubytech/create-maxy-code 0.1.489 → 0.1.491

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 (221) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/docs/superpowers/plans/2026-07-22-task-1910-two-way-portal-exchange.md +1716 -0
  3. package/payload/platform/docs/superpowers/specs/2026-07-22-task-1910-two-way-portal-exchange-design.md +103 -0
  4. package/payload/platform/lib/storage-broker/dist/audit.d.ts +29 -1
  5. package/payload/platform/lib/storage-broker/dist/audit.d.ts.map +1 -1
  6. package/payload/platform/lib/storage-broker/dist/audit.js +25 -2
  7. package/payload/platform/lib/storage-broker/dist/audit.js.map +1 -1
  8. package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +13 -0
  9. package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
  10. package/payload/platform/lib/storage-broker/dist/cf-exec.js +22 -0
  11. package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
  12. package/payload/platform/lib/storage-broker/src/audit.ts +59 -1
  13. package/payload/platform/lib/storage-broker/src/cf-exec.ts +48 -0
  14. package/payload/platform/plugins/admin/skills/agent-builder/references/agent-pattern.md +34 -5
  15. package/payload/platform/plugins/business-assistant/skills/e-sign/SKILL.md +28 -8
  16. package/payload/platform/plugins/cloudflare/bin/portal-index-push.mjs +133 -35
  17. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-delete-withdraw.test.ts +95 -0
  18. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-push.test.ts +141 -9
  19. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-moved-download.test.ts +83 -0
  20. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-schema-shape.test.ts +48 -0
  21. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-sign.test.ts +62 -0
  22. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-target.test.ts +103 -0
  23. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-upload-multipart.test.ts +211 -0
  24. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-upload-target.test.ts +168 -0
  25. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-uploads-listing.test.ts +138 -0
  26. package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +111 -4
  27. package/payload/platform/plugins/cloudflare/mcp/__tests__/upload-route.test.ts +14 -13
  28. package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +52 -14
  29. package/payload/platform/plugins/cloudflare/skills/data-portal/schema.sql +70 -7
  30. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/portal-sign.mjs +74 -0
  31. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/target.ts +91 -0
  32. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +23 -0
  33. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/delete.ts +45 -0
  34. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +59 -4
  35. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +58 -9
  36. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/upload.ts +358 -16
  37. package/payload/platform/plugins/cloudflare/skills/data-portal/template/index.html +9 -1
  38. package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.css +45 -0
  39. package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.js +216 -20
  40. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +5 -122
  41. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  42. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
  43. package/payload/platform/templates/agents/passive/IDENTITY.md +6 -0
  44. package/payload/platform/templates/agents/public/IDENTITY.md +6 -0
  45. package/payload/platform/templates/specialists/agents/citation-auditor.md +6 -0
  46. package/payload/platform/templates/specialists/agents/coding-assistant.md +6 -0
  47. package/payload/platform/templates/specialists/agents/compiled-truth-rewriter.md +6 -0
  48. package/payload/platform/templates/specialists/agents/content-producer.md +6 -0
  49. package/payload/platform/templates/specialists/agents/data-manager.md +6 -0
  50. package/payload/platform/templates/specialists/agents/database-operator.md +6 -0
  51. package/payload/platform/templates/specialists/agents/librarian.md +6 -0
  52. package/payload/platform/templates/specialists/agents/personal-assistant.md +6 -0
  53. package/payload/platform/templates/specialists/agents/project-manager.md +6 -0
  54. package/payload/platform/templates/specialists/agents/public-session-reviewer.md +6 -0
  55. package/payload/platform/templates/specialists/agents/research-assistant.md +7 -1
  56. package/payload/platform/templates/specialists/agents/typed-edge-classifier.md +6 -0
  57. package/payload/premium-plugins/writer-craft/agents/writer-craft--manuscript-reviewer.md +6 -0
  58. package/payload/server/{chunk-UFXRUKXO.js → chunk-KZFE3MOY.js} +49 -3
  59. package/payload/server/public/activity.html +5 -6
  60. package/payload/server/public/assets/AdminLoginScreens-DgVFsHtd.js +1 -0
  61. package/payload/server/public/assets/AdminShell-BwNqwb4k.js +2 -0
  62. package/payload/server/public/assets/activity-2DL2DwLr.js +1 -0
  63. package/payload/server/public/assets/admin-COe0dT2U.js +1 -0
  64. package/payload/server/public/assets/{arc-D-QzyndF.js → arc-CFMJyMm-.js} +1 -1
  65. package/payload/server/public/assets/architecture-YZFGNWBL-Cypt5qHL.js +1 -0
  66. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-W7f0Us4S.js → architectureDiagram-Q4EWVU46-CiSSgdL8.js} +1 -1
  67. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-CfPS4FXg.js → blockDiagram-DXYQGD6D-_sXtIW5c.js} +1 -1
  68. package/payload/server/public/assets/{bot-DnuyRsLS.js → bot-CpBuDQlF.js} +1 -1
  69. package/payload/server/public/assets/{browser-q4EFqKvR.js → browser-CSApuZrv.js} +1 -1
  70. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-Dq60cLVI.js → c4Diagram-AHTNJAMY-Bh7OAMXs.js} +1 -1
  71. package/payload/server/public/assets/{calendar-C-q9kWmA.js → calendar-DlvwZnS7.js} +1 -1
  72. package/payload/server/public/assets/channel-BMyXE4zc.js +1 -0
  73. package/payload/server/public/assets/chat-DWvYv9jq.js +1 -0
  74. package/payload/server/public/assets/chevron-down-Dnuuw6Q2.js +1 -0
  75. package/payload/server/public/assets/chevron-left-CC4zmWOr.js +1 -0
  76. package/payload/server/public/assets/chevron-right-CG_lHTx_.js +1 -0
  77. package/payload/server/public/assets/{chunk-2KRD3SAO-CXDjuDN0.js → chunk-2KRD3SAO-D4fktAwW.js} +1 -1
  78. package/payload/server/public/assets/{chunk-336JU56O-926ox0tj.js → chunk-336JU56O-CcQoKH2G.js} +2 -2
  79. package/payload/server/public/assets/chunk-426QAEUC-Xu6JKP4d.js +1 -0
  80. package/payload/server/public/assets/{chunk-4BX2VUAB-C2nrPCfN.js → chunk-4BX2VUAB-C13Wz3zS.js} +1 -1
  81. package/payload/server/public/assets/{chunk-4TB4RGXK-CuW3FOR6.js → chunk-4TB4RGXK-BL48D7gN.js} +1 -1
  82. package/payload/server/public/assets/{chunk-55IACEB6-B4_-In90.js → chunk-55IACEB6-TmOKdy8a.js} +1 -1
  83. package/payload/server/public/assets/{chunk-5FUZZQ4R-DnYGr9CP.js → chunk-5FUZZQ4R-DZ_jSF6N.js} +1 -1
  84. package/payload/server/public/assets/{chunk-5PVQY5BW-BYCOi9bf.js → chunk-5PVQY5BW-De4G1vfV.js} +1 -1
  85. package/payload/server/public/assets/{chunk-67CJDMHE-CBxsC4JP.js → chunk-67CJDMHE-Cif9Uu5C.js} +1 -1
  86. package/payload/server/public/assets/{chunk-7N4EOEYR-CAACaqFr.js → chunk-7N4EOEYR-WjTHCXnd.js} +1 -1
  87. package/payload/server/public/assets/{chunk-AA7GKIK3-BoamZdS6.js → chunk-AA7GKIK3-CwCfdQmb.js} +1 -1
  88. package/payload/server/public/assets/{chunk-BSJP7CBP-CHk82eFy.js → chunk-BSJP7CBP-9A67yXwT.js} +1 -1
  89. package/payload/server/public/assets/{chunk-CIAEETIT-DdeW9zxl.js → chunk-CIAEETIT-DPMiWSBi.js} +1 -1
  90. package/payload/server/public/assets/{chunk-EDXVE4YY-DJqSRv2w.js → chunk-EDXVE4YY-C2f_Qvx-.js} +1 -1
  91. package/payload/server/public/assets/{chunk-ENJZ2VHE-BRi4BwM5.js → chunk-ENJZ2VHE-B-Q9EbSQ.js} +1 -1
  92. package/payload/server/public/assets/{chunk-FMBD7UC4-OJp2CtUE.js → chunk-FMBD7UC4-Bmt_ZRlV.js} +1 -1
  93. package/payload/server/public/assets/{chunk-FOC6F5B3-BUAQ6spG.js → chunk-FOC6F5B3-DfGJGxpX.js} +1 -1
  94. package/payload/server/public/assets/{chunk-ICPOFSXX-DeRggD-Q.js → chunk-ICPOFSXX-Wyym1Q8K.js} +2 -2
  95. package/payload/server/public/assets/{chunk-K5T4RW27-EHiXZ8Py.js → chunk-K5T4RW27-DMpoaqw2.js} +1 -1
  96. package/payload/server/public/assets/{chunk-KGLVRYIC-tOajPcpn.js → chunk-KGLVRYIC-DYUFgri2.js} +1 -1
  97. package/payload/server/public/assets/{chunk-LIHQZDEY-uwO6oHIU.js → chunk-LIHQZDEY-x3LmeKmx.js} +1 -1
  98. package/payload/server/public/assets/{chunk-ORNJ4GCN-Dpa1_BCA.js → chunk-ORNJ4GCN-BeuNAWkW.js} +1 -1
  99. package/payload/server/public/assets/{chunk-OYMX7WX6-ChdDqaQL.js → chunk-OYMX7WX6-BUWVAfQA.js} +1 -1
  100. package/payload/server/public/assets/chunk-QZHKN3VN-7_kS3RBy.js +1 -0
  101. package/payload/server/public/assets/{chunk-U2HBQHQK-5JgWosPv.js → chunk-U2HBQHQK-DKS5adQo.js} +1 -1
  102. package/payload/server/public/assets/{chunk-X2U36JSP-BGJqysB2.js → chunk-X2U36JSP-CT_UM4IN.js} +1 -1
  103. package/payload/server/public/assets/{chunk-XPW4576I-GERE2ekx.js → chunk-XPW4576I-ByFnN4JO.js} +1 -1
  104. package/payload/server/public/assets/{chunk-YZCP3GAM-BskqX1V5.js → chunk-YZCP3GAM-DCetC20J.js} +1 -1
  105. package/payload/server/public/assets/{chunk-ZZ45TVLE-DG1iY-L9.js → chunk-ZZ45TVLE-Bh1QIv8N.js} +1 -1
  106. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DJqbNcuC.js +1 -0
  107. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-CGglqfQA.js +1 -0
  108. package/payload/server/public/assets/clone-BiTMTzp_.js +1 -0
  109. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-C8Rl4ykh.js → cose-bilkent-S5V4N54A-DgLEaApT.js} +1 -1
  110. package/payload/server/public/assets/{dagre-KV5264BT-C0YrJPF3.js → dagre-KV5264BT-BfkUPrEV.js} +1 -1
  111. package/payload/server/public/assets/{dagre-DTlv-FFy.js → dagre-unhMIyVo.js} +1 -1
  112. package/payload/server/public/assets/data-CJbC_cVy.js +1 -0
  113. package/payload/server/public/assets/{diagram-5BDNPKRD-Dfk5oepI.js → diagram-5BDNPKRD-BjjwcTD9.js} +1 -1
  114. package/payload/server/public/assets/{diagram-G4DWMVQ6-QRj55DiB.js → diagram-G4DWMVQ6-BbgWtVSS.js} +1 -1
  115. package/payload/server/public/assets/{diagram-MMDJMWI5-BAPKsohU.js → diagram-MMDJMWI5-DyAnkWGy.js} +1 -1
  116. package/payload/server/public/assets/{diagram-TYMM5635-C3iZXPkC.js → diagram-TYMM5635-SAMMoLxq.js} +1 -1
  117. package/payload/server/public/assets/{erDiagram-SMLLAGMA-DIbI7g_5.js → erDiagram-SMLLAGMA-Cvo-5-fD.js} +1 -1
  118. package/payload/server/public/assets/{flatten-DlAJeN0L.js → flatten-BXzaHgtL.js} +1 -1
  119. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-l3eJJr8R.js → flowDiagram-DWJPFMVM-y3o2Ycld.js} +1 -1
  120. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-aaEzHxIq.js → ganttDiagram-T4ZO3ILL-BEm_CQaK.js} +1 -1
  121. package/payload/server/public/assets/gitGraph-7Q5UKJZL-C_-XjxLd.js +1 -0
  122. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-Di-1eg_m.js → gitGraphDiagram-UUTBAWPF-CqmOxS7F.js} +1 -1
  123. package/payload/server/public/assets/{graph-Cyh6FWaV.js → graph-kaxS9a_7.js} +2 -2
  124. package/payload/server/public/assets/{graph-labels-CUQtXFKW.js → graph-labels-GgejRtgV.js} +1 -1
  125. package/payload/server/public/assets/{graphlib-D5awC2yp.js → graphlib-_HfbQcBf.js} +1 -1
  126. package/payload/server/public/assets/info-OMHHGYJF-BH8yDCfL.js +1 -0
  127. package/payload/server/public/assets/infoDiagram-42DDH7IO-DyDXyJ6U.js +2 -0
  128. package/payload/server/public/assets/{isEmpty-Dh1dF-tM.js → isEmpty-uB1dEu_Z.js} +1 -1
  129. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-CLOd_cNf.js → ishikawaDiagram-UXIWVN3A-C_d0vcZC.js} +1 -1
  130. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-cGyfW6Tg.js → journeyDiagram-VCZTEJTY-DsjRBqlW.js} +1 -1
  131. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-CRTKWPlh.js → kanban-definition-6JOO6SKY-BwtK6LS-.js} +1 -1
  132. package/payload/server/public/assets/{line-B8Brm0TD.js → line-szBCFOLJ.js} +1 -1
  133. package/payload/server/public/assets/{linear-BHnSKXBe.js → linear-X3O7lwK3.js} +1 -1
  134. package/payload/server/public/assets/{maximize-2-k3Fu5hdP.js → maximize-2-CPq3nwZA.js} +1 -1
  135. package/payload/server/public/assets/{mermaid-parser.core-BtNpdQct.js → mermaid-parser.core-BavvMAhU.js} +2 -2
  136. package/payload/server/public/assets/{mermaid.core-BY87anQZ.js → mermaid.core-Br2F9Hdm.js} +3 -3
  137. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-rUSv9qN1.js → mindmap-definition-QFDTVHPH-4qlwXoJ-.js} +1 -1
  138. package/payload/server/public/assets/operator-CldXXilj.js +1 -0
  139. package/payload/server/public/assets/{ordinal-Bnl5GL-x.js → ordinal-CZY9laxg.js} +1 -1
  140. package/payload/server/public/assets/packet-4T2RLAQJ-Bajcns4r.js +1 -0
  141. package/payload/server/public/assets/{page-Cno8NNMk.js → page-BE6DkJP9.js} +1 -1
  142. package/payload/server/public/assets/{page-DhwPG5Zf.js → page-BIwdWXnv.js} +3 -3
  143. package/payload/server/public/assets/{pdf-render-CrpAi4Ba.js → pdf-render-H884L3EI.js} +1 -1
  144. package/payload/server/public/assets/pie-ZZUOXDRM-D7hjM5w1.js +1 -0
  145. package/payload/server/public/assets/{pieDiagram-DEJITSTG-Cjn9IP-C.js → pieDiagram-DEJITSTG-BbXryIg0.js} +1 -1
  146. package/payload/server/public/assets/{public-BJ5Oo2Fw.js → public-BKQEQGPE.js} +1 -1
  147. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-BkSHkSsn.js → quadrantDiagram-34T5L4WZ-BHLjcvh3.js} +1 -1
  148. package/payload/server/public/assets/radar-PYXPWWZC-B46d5x7a.js +1 -0
  149. package/payload/server/public/assets/{reduce-Bp5oNw1n.js → reduce-BGQzAenn.js} +1 -1
  150. package/payload/server/public/assets/{requirementDiagram-MS252O5E-hLwgyB-F.js → requirementDiagram-MS252O5E-BXaVO0lk.js} +1 -1
  151. package/payload/server/public/assets/{rotate-ccw-D-ov5jM4.js → rotate-ccw-DieZzl10.js} +1 -1
  152. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-D6Dl1RIa.js → sankeyDiagram-XADWPNL6-CmN1s17Q.js} +1 -1
  153. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-j0QBqkbL.js → sequenceDiagram-FGHM5R23-DC08e5NY.js} +1 -1
  154. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-TvEBNsuV.js → stateDiagram-FHFEXIEX-DnoQMuOW.js} +1 -1
  155. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CrstH34p.js +1 -0
  156. package/payload/server/public/assets/{tasks-DHMxTZVD.js → tasks-DYyCGz8W.js} +1 -1
  157. package/payload/server/public/assets/{time-entry-format-EmV5lz6o.js → time-entry-format-CC_9jFuR.js} +1 -1
  158. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BfivOP-j.js → timeline-definition-GMOUNBTQ-eTn3uRaH.js} +1 -1
  159. package/payload/server/public/assets/treeView-SZITEDCU-3XQh5xQ1.js +1 -0
  160. package/payload/server/public/assets/treemap-W4RFUUIX-DwR-_l5z.js +1 -0
  161. package/payload/server/public/assets/{triangle-alert-CivyPfAL.js → triangle-alert-7mQfCQWP.js} +1 -1
  162. package/payload/server/public/assets/{useCopyFeedback-hbnPS-r9.js → useCopyFeedback-os7N0yCx.js} +1 -1
  163. package/payload/server/public/assets/useSubAccountSwitcher-Bgfqig1V.css +1 -0
  164. package/payload/server/public/assets/{useVoiceRecorder-QN9_GqB_.js → useVoiceRecorder-ZFwt5i6d.js} +2 -2
  165. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-Bvfw9aFJ.js → vennDiagram-DHZGUBPP-DNx6o_ZQ.js} +1 -1
  166. package/payload/server/public/assets/wardley-RL74JXVD-DZp9oCGv.js +1 -0
  167. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-C9h04G6b.js → wardleyDiagram-NUSXRM2D-BZOchqF3.js} +1 -1
  168. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-BMvi2m2q.js → xychartDiagram-5P7HB3ND-ZBEGH6ZI.js} +1 -1
  169. package/payload/server/public/browser.html +4 -4
  170. package/payload/server/public/calendar.html +6 -6
  171. package/payload/server/public/chat.html +14 -14
  172. package/payload/server/public/data.html +11 -11
  173. package/payload/server/public/graph.html +10 -11
  174. package/payload/server/public/index.html +16 -17
  175. package/payload/server/public/operator.html +15 -16
  176. package/payload/server/public/public.html +14 -14
  177. package/payload/server/public/tasks.html +6 -6
  178. package/payload/server/server.js +1488 -895
  179. package/payload/server/{src-YJF3UJNR.js → src-UYDFMWF4.js} +1 -1
  180. package/payload/platform/lib/active-rules/package-lock.json +0 -12
  181. package/payload/server/public/assets/AdminLoginScreens-Dic_fPt6.js +0 -1
  182. package/payload/server/public/assets/AdminShell-JN7rZ8aG.js +0 -2
  183. package/payload/server/public/assets/Checkbox-CB74kINA.js +0 -1
  184. package/payload/server/public/assets/activity-DbfpHg3i.js +0 -1
  185. package/payload/server/public/assets/admin-DvQPTxqe.js +0 -1
  186. package/payload/server/public/assets/architecture-YZFGNWBL-CGvD4r9F.js +0 -1
  187. package/payload/server/public/assets/channel-Bpg-ADC0.js +0 -1
  188. package/payload/server/public/assets/chat-DcefIV9q.js +0 -1
  189. package/payload/server/public/assets/chevron-down-Blma_CKl.js +0 -1
  190. package/payload/server/public/assets/chevron-left-xeYDxFiC.js +0 -1
  191. package/payload/server/public/assets/chevron-right-BonbtjqJ.js +0 -1
  192. package/payload/server/public/assets/chunk-426QAEUC-1poG7Nd3.js +0 -1
  193. package/payload/server/public/assets/chunk-QZHKN3VN-GN1tIO8J.js +0 -1
  194. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DMGVq8Jv.js +0 -1
  195. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DfddT8UV.js +0 -1
  196. package/payload/server/public/assets/clone-Cc9vFIEk.js +0 -1
  197. package/payload/server/public/assets/data-Bu-5SUmR.js +0 -1
  198. package/payload/server/public/assets/gitGraph-7Q5UKJZL-DtjXE0UI.js +0 -1
  199. package/payload/server/public/assets/info-OMHHGYJF-CmArhGYf.js +0 -1
  200. package/payload/server/public/assets/infoDiagram-42DDH7IO-utWz7oZy.js +0 -2
  201. package/payload/server/public/assets/operator-8NVpNLeD.js +0 -1
  202. package/payload/server/public/assets/packet-4T2RLAQJ-BTfK0Zw4.js +0 -1
  203. package/payload/server/public/assets/pie-ZZUOXDRM-BvtPeMQY.js +0 -1
  204. package/payload/server/public/assets/radar-PYXPWWZC-D2UXvQgn.js +0 -1
  205. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-Ur4eM6rb.js +0 -1
  206. package/payload/server/public/assets/treeView-SZITEDCU-0dE7FvM2.js +0 -1
  207. package/payload/server/public/assets/treemap-W4RFUUIX-BrwlXpr5.js +0 -1
  208. package/payload/server/public/assets/useSubAccountSwitcher-kwhG-aJj.css +0 -1
  209. package/payload/server/public/assets/wardley-RL74JXVD-CO9HdfJ-.js +0 -1
  210. /package/payload/server/public/assets/{_baseFor-BBkmqPEk.js → _baseFor-CMWvz0uA.js} +0 -0
  211. /package/payload/server/public/assets/{array-Bjtb2C_5.js → array-Cny_NT6T.js} +0 -0
  212. /package/payload/server/public/assets/{cytoscape.esm-B1dQ664k.js → cytoscape.esm-De0CCmB2.js} +0 -0
  213. /package/payload/server/public/assets/{defaultLocale-iwYA7ne8.js → defaultLocale-BahRht8U.js} +0 -0
  214. /package/payload/server/public/assets/{dist-CLmrZbY7.js → dist-C9D_xz81.js} +0 -0
  215. /package/payload/server/public/assets/{init-CxnBAgcf.js → init-DoIvjoJ_.js} +0 -0
  216. /package/payload/server/public/assets/{katex-DojHoLIr.js → katex-Bfn1OZEl.js} +0 -0
  217. /package/payload/server/public/assets/{path-JZiojYzQ.js → path-CTezU8L5.js} +0 -0
  218. /package/payload/server/public/assets/{preload-helper-CT1Z6Pdu.js → preload-helper-D_sPnjeL.js} +0 -0
  219. /package/payload/server/public/assets/{rough.esm-D5NinLFK.js → rough.esm-BJo4pvXu.js} +0 -0
  220. /package/payload/server/public/assets/{src-DQ0MSD6q.js → src-BTKk8KAG.js} +0 -0
  221. /package/payload/server/public/assets/{useSubAccountSwitcher-DotTrvX3.js → useSubAccountSwitcher-Q941cZYV.js} +0 -0
@@ -0,0 +1,91 @@
1
+ // Where an upload may land (Task 1910).
2
+ //
3
+ // Before this, an upload had no destination at all: the key was a flat
4
+ // `ownerId/filename` and the client could not say which folder they meant. This
5
+ // module is the one definition of a legal target on the portal side.
6
+ //
7
+ // It is NOT the security boundary. The device re-derives the account's exposed
8
+ // folder set itself and refuses anything outside it, exactly as the download
9
+ // path already does. Both checks exist for the same reason: if this end is ever
10
+ // wrong — a stale index, a bug here, a compromised Pages project — the device
11
+ // is what contains it. A parity test pins the two rules to the same answers.
12
+
13
+ import type { D1Database } from './types'
14
+ import { grantAllows } from './session'
15
+ import { ownerPrefix } from './authorize'
16
+
17
+ /**
18
+ * One path segment of a legal target: not empty, not a traversal token, and
19
+ * carrying no separator of either flavour.
20
+ *
21
+ * Rejected outright rather than normalised, the same rule and the same reason
22
+ * as authorizeKey — a normaliser is one more thing that can be wrong, and no
23
+ * legitimate folder name contains any of these.
24
+ *
25
+ * Deliberately identical to the device's own safeSegment in
26
+ * portal-uploads-pull.ts. The two are separate implementations on purpose (no
27
+ * import crosses that boundary) and the parity test is what keeps them honest.
28
+ */
29
+ export function isSafeTargetSegment(s: string): boolean {
30
+ if (!s || s === '.' || s === '..') return false
31
+ return !s.includes('/') && !s.includes('\\') && !s.includes('\0')
32
+ }
33
+
34
+ export type TargetCheck = { ok: true } | { ok: false; reason: 'segment' | 'grant' | 'index' }
35
+
36
+ /**
37
+ * Whether this person may upload into `targetPath`.
38
+ *
39
+ * Three clauses, in the order that costs least: every segment is safe, the
40
+ * person's grant admits the top-level name, and the account's published index
41
+ * carries a folder row for the exact path.
42
+ *
43
+ * That last clause is the whole permission model: you can only drop where you
44
+ * can see. The legal target set is exactly the set of folders already rendered
45
+ * to this person, so no new concept is introduced and no folder can be named
46
+ * that the client could not already open.
47
+ *
48
+ * The empty target is always legal and costs no query. It is the default
49
+ * uploads area, which is deliberately never in the index.
50
+ *
51
+ * The three reasons are distinct in the log because they mean different things
52
+ * to an operator: `segment` is a malformed request, `grant` is a person seeing
53
+ * less than their account exposes, and `index` is usually a folder that exists
54
+ * on the device but has not been published yet.
55
+ */
56
+ export async function checkTarget(
57
+ db: D1Database,
58
+ accountId: string,
59
+ grant: string[],
60
+ targetPath: string,
61
+ ): Promise<TargetCheck> {
62
+ if (targetPath === '') return { ok: true }
63
+ if (!targetPath.split('/').every(isSafeTargetSegment)) return { ok: false, reason: 'segment' }
64
+ if (!grantAllows(grant, targetPath)) return { ok: false, reason: 'grant' }
65
+ const row = await db
66
+ .prepare(
67
+ `SELECT d.relPath AS relPath
68
+ FROM directory d
69
+ JOIN directory_state s
70
+ ON s.accountId = d.accountId AND s.currentGeneration = d.generation
71
+ WHERE d.accountId = ? AND d.relPath = ? AND d.isDir = 1`,
72
+ )
73
+ .bind(accountId, targetPath)
74
+ .first<{ relPath: string }>()
75
+ return row ? { ok: true } : { ok: false, reason: 'index' }
76
+ }
77
+
78
+ /**
79
+ * `ownerId/targetPath/filename`, collapsing to `ownerId/filename` when
80
+ * untargeted.
81
+ *
82
+ * The target is IN the key because `objectKey` is UNIQUE. With a flat key the
83
+ * same filename dropped into two folders would collide: the upsert would
84
+ * overwrite the first object, reset its ingested flag, and the first file would
85
+ * never reach the device at all.
86
+ */
87
+ export function uploadKey(ownerId: string, targetPath: string, filename: string): string {
88
+ return targetPath
89
+ ? `${ownerPrefix(ownerId)}${targetPath}/${filename}`
90
+ : `${ownerPrefix(ownerId)}${filename}`
91
+ }
@@ -24,11 +24,34 @@ export interface R2ObjectBody {
24
24
  size: number
25
25
  }
26
26
 
27
+ /** One completed part, as R2 returns it and as `complete` wants it back. */
28
+ export interface R2UploadedPart {
29
+ partNumber: number
30
+ etag: string
31
+ }
32
+
33
+ /**
34
+ * An open multipart session (Task 1910).
35
+ *
36
+ * The client slices a large file and sends parts, because an upload body is
37
+ * read wholly into the Function's memory: one 1 GiB request would OOM the
38
+ * isolate, and an OOM emits no [data-portal] line at all. Parts keep the
39
+ * existing per-request bound and let a large file respect it repeatedly.
40
+ */
41
+ export interface R2MultipartUpload {
42
+ uploadId: string
43
+ uploadPart: (partNumber: number, value: ArrayBuffer | Uint8Array) => Promise<R2UploadedPart>
44
+ complete: (parts: R2UploadedPart[]) => Promise<{ size?: number } | null>
45
+ abort: () => Promise<void>
46
+ }
47
+
27
48
  export interface R2Bucket {
28
49
  put: (key: string, value: ArrayBuffer | Uint8Array) => Promise<unknown>
29
50
  get: (key: string) => Promise<R2ObjectBody | null>
30
51
  delete: (key: string) => Promise<void>
31
52
  head: (key: string) => Promise<{ size: number } | null>
53
+ createMultipartUpload: (key: string) => Promise<R2MultipartUpload>
54
+ resumeMultipartUpload: (key: string, uploadId: string) => R2MultipartUpload
32
55
  }
33
56
 
34
57
  export interface PortalEnv {
@@ -22,6 +22,51 @@ export async function processDelete(
22
22
  }
23
23
  const owner = q(session.ownerId)
24
24
 
25
+ // What the client is asking to remove decides HOW it is removed, so the row
26
+ // is read before anything is written (Task 1910).
27
+ const row = await env.DB.prepare('SELECT devicePath FROM manifest WHERE objectKey = ?')
28
+ .bind(key)
29
+ .first<{ devicePath: string }>()
30
+ if (!row) {
31
+ log(`[data-portal] op=delete owner=${owner} key=${q(key)} result=absent`)
32
+ return { status: 404, payload: { ok: false, error: 'not found' } }
33
+ }
34
+
35
+ // A moved file's bytes are on the device and its R2 object is already gone.
36
+ // Deleting here would report success while the client's file stayed on the
37
+ // device forever. Instead the row is marked, and the pull removes the device
38
+ // file, confirms it is absent, and only then removes the row — so a
39
+ // withdrawal completes on BOTH sides or on neither.
40
+ //
41
+ // `pending: true` is not decoration: the client is told the file is on its
42
+ // way out rather than gone, because it will still be there for up to a
43
+ // minute and saying otherwise would be a lie the next refresh exposes.
44
+ if (row.devicePath) {
45
+ try {
46
+ await env.DB.prepare('UPDATE manifest SET deleted = 1 WHERE objectKey = ?').bind(key).run()
47
+ } catch (err) {
48
+ log(
49
+ `[data-portal] op=withdraw owner=${owner} key=${q(key)} ` +
50
+ `result=failed err="${(err as Error).message}"`,
51
+ )
52
+ return { status: 502, payload: { ok: false, error: 'withdraw failed' } }
53
+ }
54
+ // Re-read rather than assume the UPDATE landed: an unmarked row is a
55
+ // withdrawal the pull will never see, and nothing else would report it.
56
+ const marked = await env.DB.prepare('SELECT deleted FROM manifest WHERE objectKey = ?')
57
+ .bind(key)
58
+ .first<{ deleted: number }>()
59
+ const ok = marked?.deleted === 1
60
+ log(
61
+ `[data-portal] op=withdraw owner=${owner} key=${q(key)} devicePath=${q(row.devicePath)} ` +
62
+ `ms=${now() - startedMs} result=${ok ? 'marked' : 'not-marked'}`,
63
+ )
64
+ return {
65
+ status: ok ? 200 : 500,
66
+ payload: { ok, pending: true },
67
+ }
68
+ }
69
+
25
70
  // Both writes are wrapped and each emits its own line, mirroring upload's
26
71
  // steps 3-5. Unwrapped, a throw here escapes the handler, Pages returns a
27
72
  // bare 500, and NOTHING is logged — and if the R2 delete succeeded before the
@@ -2,10 +2,13 @@ import type { Handler, Logger, PortalEnv, R2ObjectBody } from './_lib/types'
2
2
  import { grantAllows, readSessionCookie, resolveSession } from './_lib/session'
3
3
  import { q } from './_lib/log'
4
4
  import { authorizeKey } from './_lib/authorize'
5
- import { buildSignedUrl, SIGNED_TTL_MS } from './_lib/portal-sign.mjs'
5
+ import { buildOwnUploadUrl, buildSignedUrl, SIGNED_TTL_MS } from './_lib/portal-sign.mjs'
6
6
 
7
7
  interface DownloadResult extends Handler {
8
8
  body?: R2ObjectBody | null
9
+ /** Set when the row has moved: the bytes are on the device, so the caller
10
+ * 302s to a signed own-upload link instead of streaming R2. */
11
+ url?: string
9
12
  }
10
13
 
11
14
  export async function processDownload(
@@ -25,9 +28,49 @@ export async function processDownload(
25
28
  log(`[data-portal] op=download owner=${q(session.ownerId)} result=denied reason=not-owner`)
26
29
  return { status: 403, payload: { ok: false, error: 'denied' } }
27
30
  }
31
+ // Where the bytes are decides how they are served (Task 1910). A moved row's
32
+ // R2 object has been deleted, so reading it here would 404 the client's own
33
+ // upload the moment the device collected it.
34
+ const row = await env.DB.prepare('SELECT devicePath FROM manifest WHERE objectKey = ?')
35
+ .bind(key)
36
+ .first<{ devicePath: string }>()
37
+ if (!row) {
38
+ log(`[data-portal] op=download owner=${q(session.ownerId)} result=absent`)
39
+ return { status: 404, payload: { ok: false, error: 'not found' } }
40
+ }
41
+
42
+ if (row.devicePath) {
43
+ const secret = env.PORTAL_FETCH_SECRET
44
+ const origin = env.PORTAL_INSTALL_ORIGIN
45
+ if (!secret || !origin) {
46
+ // Reported as offline rather than a server error, the same judgement the
47
+ // indexed path makes: from the client's side the file genuinely is
48
+ // unreachable, and the operator's signal is this line, not a 500.
49
+ log(`[data-portal] op=download owner=${q(session.ownerId)} result=unconfigured`)
50
+ return { status: 503, payload: { ok: false, error: 'device-offline' } }
51
+ }
52
+ const expiresAt = nowMs + SIGNED_TTL_MS
53
+ const url = await buildOwnUploadUrl(
54
+ origin,
55
+ secret,
56
+ session.accountId,
57
+ session.ownerId,
58
+ row.devicePath,
59
+ expiresAt,
60
+ )
61
+ log(
62
+ `[data-portal] op=download owner=${q(session.ownerId)} key=${q(key)} ` +
63
+ `devicePath=${q(row.devicePath)} result=moved`,
64
+ )
65
+ return { status: 200, payload: { ok: true, url }, url }
66
+ }
67
+
28
68
  const obj = await env.BUCKET.get(key)
29
69
  if (!obj) {
30
- log(`[data-portal] op=download owner=${q(session.ownerId)} result=absent`)
70
+ // The row says the bytes are in R2 and they are not. That is a phantom row,
71
+ // which emits nothing at request time anywhere else — the audit's
72
+ // reconcile is what counts them, and this line is what dates one.
73
+ log(`[data-portal] op=download owner=${q(session.ownerId)} result=absent reason=no-object`)
31
74
  return { status: 404, payload: { ok: false, error: 'not found' } }
32
75
  }
33
76
  log(`[data-portal] op=download owner=${q(session.ownerId)} result=ok bytes=${obj.size ?? 'na'}`)
@@ -84,7 +127,13 @@ export async function processIndexedDownload(
84
127
  }
85
128
 
86
129
  const row = await env.DB.prepare(
87
- 'SELECT relPath, isDir FROM directory WHERE accountId = ? AND relPath = ?',
130
+ // Through the generation pointer, like every other reader: a row staged by
131
+ // a push in flight must not be servable before the flip blesses it.
132
+ `SELECT d.relPath AS relPath, d.isDir AS isDir
133
+ FROM directory d
134
+ JOIN directory_state s
135
+ ON s.accountId = d.accountId AND s.currentGeneration = d.generation
136
+ WHERE d.accountId = ? AND d.relPath = ?`,
88
137
  )
89
138
  .bind(session.accountId, relPath)
90
139
  .first<{ relPath: string; isDir: number }>()
@@ -176,13 +225,19 @@ export async function onRequestGet(context: PagesContext): Promise<Response> {
176
225
  }
177
226
 
178
227
  const key = params.get('key') ?? ''
179
- const { status, payload, body } = await processDownload(
228
+ const { status, payload, body, url } = await processDownload(
180
229
  sessionId,
181
230
  key,
182
231
  context.env,
183
232
  (line) => console.log(line),
184
233
  Date.now(),
185
234
  )
235
+ // A moved row has no bytes here to stream. Both the click path and a drag-out
236
+ // follow a 302 to the device, so this needs no redirect=1 discriminator the
237
+ // way the indexed path does: there is no JSON alternative to fall back to.
238
+ if (status === 200 && url) {
239
+ return Response.redirect(url, 302)
240
+ }
186
241
  if (status === 200 && body) {
187
242
  return new Response(body.body, {
188
243
  status: 200,
@@ -80,6 +80,33 @@ export function entriesUnder(rows: DirRow[], prefix: string): DirEntry[] {
80
80
  return [...dirList, ...files]
81
81
  }
82
82
 
83
+ /**
84
+ * Whether an upload row belongs in the client's own uploads panel (Task 1910).
85
+ *
86
+ * One rule covers both halves of the move: a row is listed while its bytes are
87
+ * still in R2, and after the move ONLY when the file is not reachable in the
88
+ * folder tree.
89
+ *
90
+ * So a targeted file leaves the panel and appears in the folder it was dropped
91
+ * into, and an untargeted file — which lands in `uploads/`, never exposed —
92
+ * stays in the panel and downloads through the device. Nothing is listed twice
93
+ * and nothing becomes unreachable.
94
+ *
95
+ * A withdrawn row is hidden while the device removal is pending. That is a
96
+ * brief state, not a tombstone filter: the row is deleted outright once the
97
+ * device file is confirmed absent.
98
+ */
99
+ export function shouldListUpload(
100
+ row: { devicePath: string; deleted: number },
101
+ visibleTops: Set<string>,
102
+ ): boolean {
103
+ if (row.deleted) return false
104
+ if (!row.devicePath) return true
105
+ // The whole top segment, never a prefix — `job` must not satisfy `jobs`, the
106
+ // same rule grantAllows follows for the same reason.
107
+ return !visibleTops.has(row.devicePath.split('/')[0])
108
+ }
109
+
83
110
  export async function processFiles(
84
111
  sessionId: string,
85
112
  env: PortalEnv,
@@ -94,34 +121,56 @@ export async function processFiles(
94
121
  }
95
122
  // Scoped by ownerId, not by key prefix: the owner is the authority.
96
123
  const rows = await env.DB.prepare(
97
- 'SELECT fileId, filename, objectKey, size, uploadedAt, ingested FROM manifest WHERE ownerId = ? ORDER BY uploadedAt DESC',
124
+ 'SELECT fileId, filename, objectKey, size, uploadedAt, ingested, targetPath, devicePath, deleted ' +
125
+ 'FROM manifest WHERE ownerId = ? ORDER BY uploadedAt DESC',
98
126
  )
99
127
  .bind(session.ownerId)
100
128
  .all<Record<string, unknown>>()
101
- const files = rows.results ?? []
129
+ const allFiles = rows.results ?? []
102
130
 
103
- // The account's published folder index, scoped by accountId. No account bound
104
- // means no folders: an enrolment predating that column lands here, and the
105
- // empty `account=` field in the log line is what says so.
131
+ // The account's published folder index, scoped by accountId and read through
132
+ // the generation pointer so a push in flight is never half-visible. No
133
+ // account bound means no folders: an enrolment predating that column lands
134
+ // here, and the empty `account=` field in the log line is what says so.
106
135
  let entries: DirEntry[] = []
107
136
  const grant = session.folders
137
+ // The tops the client can actually browse. Built from the SAME grant-filtered
138
+ // rows the tree is built from, so the uploads panel and the tree can never
139
+ // disagree about what is reachable.
140
+ const visibleTops = new Set<string>()
108
141
  if (session.accountId) {
109
142
  const dir = await env.DB.prepare(
110
- 'SELECT relPath, sizeBytes, modifiedAt, isDir FROM directory WHERE accountId = ?',
143
+ `SELECT d.relPath AS relPath, d.sizeBytes AS sizeBytes, d.modifiedAt AS modifiedAt,
144
+ d.isDir AS isDir
145
+ FROM directory d
146
+ JOIN directory_state s
147
+ ON s.accountId = d.accountId AND s.currentGeneration = d.generation
148
+ WHERE d.accountId = ?`,
111
149
  )
112
150
  .bind(session.accountId)
113
151
  .all<DirRow>()
114
152
  // The grant narrows below the account's exposed set: keep only rows the
115
153
  // grant admits. An empty grant admits everything, which is the full set and
116
154
  // today's behaviour.
117
- const rows = (dir.results ?? []).filter((row) => grantAllows(grant, row.relPath))
118
- entries = entriesUnder(rows, prefix)
155
+ const granted = (dir.results ?? []).filter((row) => grantAllows(grant, row.relPath))
156
+ for (const row of granted) visibleTops.add(row.relPath.split('/')[0])
157
+ entries = entriesUnder(granted, prefix)
119
158
  }
120
159
 
160
+ // Filtered AFTER the directory read, because the rule needs to know what the
161
+ // client can reach. A moved file is not gone; it is listed where it lives.
162
+ const files = allFiles.filter((f) =>
163
+ shouldListUpload(
164
+ { devicePath: String(f.devicePath ?? ''), deleted: Number(f.deleted ?? 0) },
165
+ visibleTops,
166
+ ),
167
+ )
168
+
121
169
  log(
122
170
  `[data-portal] op=list owner=${q(session.ownerId)} account=${q(session.accountId)} ` +
123
171
  `granted=${grant.length === 0 ? 'full' : grant.length} ` +
124
- `prefix=${q(prefix)} files=${files.length} entries=${entries.length} result=ok`,
172
+ `prefix=${q(prefix)} files=${files.length} moved=${allFiles.length - files.length} ` +
173
+ `entries=${entries.length} result=ok`,
125
174
  )
126
175
  return { status: 200, payload: { ok: true, files, entries } }
127
176
  }