@rubytech/create-maxy-code 0.1.478 → 0.1.480

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 (252) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1789-reseat-channel-row-fork.md +1244 -0
  3. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1818-loop-gate-app-routes.md +462 -0
  4. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1819-top-level-label-allowlist.md +321 -0
  5. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1829-platform-ui-typecheck.md +830 -0
  6. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1831-data-portal-class-derived-allowlist.md +2177 -0
  7. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1789-reseat-channel-row-fork-design.md +201 -0
  8. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1818-loop-gate-app-routes-design.md +217 -0
  9. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1819-top-level-label-allowlist-design.md +80 -0
  10. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1829-platform-ui-typecheck-design.md +163 -0
  11. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1831-data-portal-class-derived-allowlist-design.md +163 -0
  12. package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
  13. package/payload/platform/lib/graph-style/dist/index.js +22 -0
  14. package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
  15. package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +6 -0
  16. package/payload/platform/lib/graph-style/src/index.ts +20 -0
  17. package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js +155 -12
  18. package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js.map +1 -1
  19. package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +3 -0
  20. package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
  21. package/payload/platform/lib/storage-broker/dist/cf-exec.js +68 -28
  22. package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
  23. package/payload/platform/lib/storage-broker/src/__tests__/cf-exec.test.ts +194 -16
  24. package/payload/platform/lib/storage-broker/src/cf-exec.ts +74 -29
  25. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +13 -1
  26. package/payload/platform/plugins/cloudflare/PLUGIN.md +26 -1
  27. package/payload/platform/plugins/cloudflare/bin/d1-http.mjs +80 -0
  28. package/payload/platform/plugins/cloudflare/bin/portal-enrol.mjs +17 -2
  29. package/payload/platform/plugins/cloudflare/bin/portal-index-push.mjs +357 -0
  30. package/payload/platform/plugins/cloudflare/bin/schema-exposed-dirs.mjs +103 -0
  31. package/payload/platform/plugins/cloudflare/mcp/__tests__/authorize.test.ts +2 -2
  32. package/payload/platform/plugins/cloudflare/mcp/__tests__/d1-http.test.ts +96 -0
  33. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-directory-listing.test.ts +120 -0
  34. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-enrol.test.ts +59 -4
  35. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-push.test.ts +176 -0
  36. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-state-contract.test.ts +75 -0
  37. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-indexed-download.test.ts +131 -0
  38. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-sign.test.ts +68 -0
  39. package/payload/platform/plugins/cloudflare/mcp/__tests__/schema-exposed-dirs.test.ts +88 -0
  40. package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +39 -0
  41. package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +68 -1
  42. package/payload/platform/plugins/cloudflare/skills/data-portal/schema.sql +30 -1
  43. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/portal-sign.mjs +87 -0
  44. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/session.ts +8 -4
  45. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +10 -0
  46. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +107 -1
  47. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +82 -2
  48. package/payload/platform/plugins/cloudflare/skills/data-portal/template/index.html +9 -0
  49. package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.css +56 -0
  50. package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.js +98 -2
  51. package/payload/platform/plugins/cloudflare/skills/data-portal/template/wrangler.toml +12 -0
  52. package/payload/platform/plugins/docs/references/cloudflare.md +12 -0
  53. package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js +41 -1
  54. package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js.map +1 -1
  55. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js +249 -2
  56. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js.map +1 -1
  57. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js +127 -3
  58. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js.map +1 -1
  59. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts +17 -0
  60. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts.map +1 -1
  61. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js +26 -1
  62. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js.map +1 -1
  63. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts +37 -1
  64. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts.map +1 -1
  65. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js +126 -5
  66. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js.map +1 -1
  67. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts +18 -0
  68. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts.map +1 -1
  69. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +123 -18
  70. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -1
  71. package/payload/platform/plugins/outlook/references/auth.md +45 -2
  72. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.d.ts +2 -0
  73. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.d.ts.map +1 -0
  74. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.js +66 -0
  75. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.js.map +1 -0
  76. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.d.ts +41 -0
  77. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.d.ts.map +1 -0
  78. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.js +75 -0
  79. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.js.map +1 -0
  80. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +122 -0
  81. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  82. package/payload/server/{chunk-3XLLTG6R.js → chunk-REWXLAPC.js} +30 -10
  83. package/payload/server/package.json +1 -1
  84. package/payload/server/public/activity.html +7 -6
  85. package/payload/server/public/assets/{AdminLoginScreens-DR5eLF-a.js → AdminLoginScreens-DXYJWXaX.js} +1 -1
  86. package/payload/server/public/assets/AdminShell-UDgNN4CA.js +2 -0
  87. package/payload/server/public/assets/Checkbox-C08Ulc_a.js +1 -0
  88. package/payload/server/public/assets/activity-06gsRDVv.js +1 -0
  89. package/payload/server/public/assets/admin-BhiHyORH.js +1 -0
  90. package/payload/server/public/assets/{arc-BrHWN2Kr.js → arc-BHXuzTwg.js} +1 -1
  91. package/payload/server/public/assets/architecture-YZFGNWBL-Ct5q6ks1.js +1 -0
  92. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-Clt9NOr0.js → architectureDiagram-Q4EWVU46-DsN_vxau.js} +1 -1
  93. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-BOe_jmXi.js → blockDiagram-DXYQGD6D-CvNat1uB.js} +1 -1
  94. package/payload/server/public/assets/browser-Cz4IKS47.js +1 -0
  95. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-B64FaKMV.js → c4Diagram-AHTNJAMY-E9yqTPTW.js} +1 -1
  96. package/payload/server/public/assets/calendar-D0xtNqH8.js +1 -0
  97. package/payload/server/public/assets/channel-PE9vBKEQ.js +1 -0
  98. package/payload/server/public/assets/chat-DwLJ830n.js +1 -0
  99. package/payload/server/public/assets/chevron-left-Cv-TFuEf.js +1 -0
  100. package/payload/server/public/assets/{chunk-2KRD3SAO-BGAUfWvM.js → chunk-2KRD3SAO-DpzI_0EY.js} +1 -1
  101. package/payload/server/public/assets/{chunk-336JU56O-PFi6uQZ0.js → chunk-336JU56O-eR_I9Eu4.js} +2 -2
  102. package/payload/server/public/assets/chunk-426QAEUC-BDNZtJsz.js +1 -0
  103. package/payload/server/public/assets/{chunk-4BX2VUAB-CkxXXCiF.js → chunk-4BX2VUAB-D-6xk1t8.js} +1 -1
  104. package/payload/server/public/assets/{chunk-4TB4RGXK-B-7tN4IA.js → chunk-4TB4RGXK-BkqUcFpL.js} +1 -1
  105. package/payload/server/public/assets/{chunk-55IACEB6-CBkNmqrt.js → chunk-55IACEB6-ChCl2pSP.js} +1 -1
  106. package/payload/server/public/assets/{chunk-5FUZZQ4R-wa9ujn7i.js → chunk-5FUZZQ4R-DasGsg54.js} +1 -1
  107. package/payload/server/public/assets/{chunk-5PVQY5BW-DIk-xBaE.js → chunk-5PVQY5BW-DXlHUX1h.js} +1 -1
  108. package/payload/server/public/assets/{chunk-67CJDMHE-BbMV1lgW.js → chunk-67CJDMHE-BZfVpTsE.js} +1 -1
  109. package/payload/server/public/assets/{chunk-7N4EOEYR-C49Mv-nQ.js → chunk-7N4EOEYR-C6U5TIgT.js} +1 -1
  110. package/payload/server/public/assets/{chunk-AA7GKIK3-Dy_pJOp_.js → chunk-AA7GKIK3-CA7cVDmw.js} +1 -1
  111. package/payload/server/public/assets/{chunk-BSJP7CBP-Ce-OmPqn.js → chunk-BSJP7CBP-DxN2GC3p.js} +1 -1
  112. package/payload/server/public/assets/{chunk-CIAEETIT-tnjq-APC.js → chunk-CIAEETIT-C0-4wA8y.js} +1 -1
  113. package/payload/server/public/assets/{chunk-EDXVE4YY-Bvv_deZ2.js → chunk-EDXVE4YY--kyb1rLc.js} +1 -1
  114. package/payload/server/public/assets/{chunk-ENJZ2VHE-CmxCPKyU.js → chunk-ENJZ2VHE-FBN6hR7V.js} +1 -1
  115. package/payload/server/public/assets/{chunk-FMBD7UC4-Top6y08S.js → chunk-FMBD7UC4-Lpge4M5g.js} +1 -1
  116. package/payload/server/public/assets/{chunk-FOC6F5B3-B9LPNCUS.js → chunk-FOC6F5B3-CM1rwOou.js} +1 -1
  117. package/payload/server/public/assets/{chunk-ICPOFSXX-Bi-_c351.js → chunk-ICPOFSXX-JdYLY3yu.js} +2 -2
  118. package/payload/server/public/assets/{chunk-K5T4RW27-DAnRM04h.js → chunk-K5T4RW27-DCUblGBr.js} +1 -1
  119. package/payload/server/public/assets/{chunk-KGLVRYIC-C0QYcbLe.js → chunk-KGLVRYIC-BP_cide2.js} +1 -1
  120. package/payload/server/public/assets/{chunk-LIHQZDEY-BjvA5ykt.js → chunk-LIHQZDEY-DjMa1lOl.js} +1 -1
  121. package/payload/server/public/assets/{chunk-ORNJ4GCN-Cs46Gj7z.js → chunk-ORNJ4GCN-D51eJAoV.js} +1 -1
  122. package/payload/server/public/assets/{chunk-OYMX7WX6-BBpimab5.js → chunk-OYMX7WX6-4hWIO544.js} +1 -1
  123. package/payload/server/public/assets/chunk-QZHKN3VN-B_Hris8x.js +1 -0
  124. package/payload/server/public/assets/{chunk-U2HBQHQK-CQUSBHoK.js → chunk-U2HBQHQK-BwdQf9cS.js} +1 -1
  125. package/payload/server/public/assets/{chunk-X2U36JSP-Dm8TevG0.js → chunk-X2U36JSP-DmDxwLOg.js} +1 -1
  126. package/payload/server/public/assets/{chunk-XPW4576I-B10QuxYb.js → chunk-XPW4576I-JQyAB7ND.js} +1 -1
  127. package/payload/server/public/assets/{chunk-YZCP3GAM-vaGsu4Wk.js → chunk-YZCP3GAM-BonLdCzI.js} +1 -1
  128. package/payload/server/public/assets/{chunk-ZZ45TVLE-D1Qi22bL.js → chunk-ZZ45TVLE-BXKB2wSk.js} +1 -1
  129. package/payload/server/public/assets/classDiagram-6PBFFD2Q-D8Q05iMI.js +1 -0
  130. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-Dm3hAxfm.js +1 -0
  131. package/payload/server/public/assets/clone-C-wOwvpY.js +1 -0
  132. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-CRDvLRXs.js → cose-bilkent-S5V4N54A-BDnd4Blw.js} +1 -1
  133. package/payload/server/public/assets/{dagre-CEmqJTt3.js → dagre-BmexzuER.js} +1 -1
  134. package/payload/server/public/assets/{dagre-KV5264BT-LXMTo7Fp.js → dagre-KV5264BT-D4WVuwPj.js} +1 -1
  135. package/payload/server/public/assets/data-DUDJfnjI.js +1 -0
  136. package/payload/server/public/assets/{diagram-5BDNPKRD-zyis6nPt.js → diagram-5BDNPKRD-Bbf4mT8a.js} +1 -1
  137. package/payload/server/public/assets/{diagram-G4DWMVQ6-BiU0XAAD.js → diagram-G4DWMVQ6-BpqVTUmx.js} +1 -1
  138. package/payload/server/public/assets/{diagram-MMDJMWI5-rGFiz1SX.js → diagram-MMDJMWI5-DY-k5k6r.js} +1 -1
  139. package/payload/server/public/assets/{diagram-TYMM5635-B0wak1ZJ.js → diagram-TYMM5635-CXHxwacS.js} +1 -1
  140. package/payload/server/public/assets/{erDiagram-SMLLAGMA-D8AUxLyX.js → erDiagram-SMLLAGMA-BNKEc3l8.js} +1 -1
  141. package/payload/server/public/assets/{flatten-naJ5PJ4f.js → flatten-jn8bZgcI.js} +1 -1
  142. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BtdUf68b.js → flowDiagram-DWJPFMVM-CbAeuJsi.js} +1 -1
  143. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-C-lkzDmf.js → ganttDiagram-T4ZO3ILL-CeeNnL4l.js} +1 -1
  144. package/payload/server/public/assets/gitGraph-7Q5UKJZL-BBHsY16R.js +1 -0
  145. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-DRUKumq2.js → gitGraphDiagram-UUTBAWPF-DG8RCtfg.js} +1 -1
  146. package/payload/server/public/assets/{graph-DRloNic4.js → graph-BAoktBcx.js} +19 -19
  147. package/payload/server/public/assets/graph-labels-LeIhl7ZG.js +1 -0
  148. package/payload/server/public/assets/{graphlib-zRsaoFEC.js → graphlib-BkDExPQi.js} +1 -1
  149. package/payload/server/public/assets/info-OMHHGYJF-C1j5I_zx.js +1 -0
  150. package/payload/server/public/assets/infoDiagram-42DDH7IO-ZWkwHWEu.js +2 -0
  151. package/payload/server/public/assets/{isEmpty-Bnpg7b8f.js → isEmpty-Dkk6_L-B.js} +1 -1
  152. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-BguMgoR8.js → ishikawaDiagram-UXIWVN3A-D_iMHV64.js} +1 -1
  153. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-CXhyK1n-.js → journeyDiagram-VCZTEJTY-BvPRpulx.js} +1 -1
  154. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-C0UCWVx-.js → kanban-definition-6JOO6SKY-CkWaXWo2.js} +1 -1
  155. package/payload/server/public/assets/{line--uhS9YDE.js → line-BGQT9unx.js} +1 -1
  156. package/payload/server/public/assets/{linear-CAOWPodQ.js → linear-DzrYI8-8.js} +1 -1
  157. package/payload/server/public/assets/{maximize-2-CzDaBugs.js → maximize-2-2MgPSwlx.js} +1 -1
  158. package/payload/server/public/assets/{mermaid-parser.core-D6kPx7vc.js → mermaid-parser.core-DrMY7FMe.js} +2 -2
  159. package/payload/server/public/assets/{mermaid.core-CTd4DS6O.js → mermaid.core-BWdf6fZ7.js} +3 -3
  160. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-_AyvyG8C.js → mindmap-definition-QFDTVHPH-CgqM9h97.js} +1 -1
  161. package/payload/server/public/assets/operator-CZifmNhp.js +1 -0
  162. package/payload/server/public/assets/{ordinal-Wx4m9zHk.js → ordinal-DuJYM7p1.js} +1 -1
  163. package/payload/server/public/assets/packet-4T2RLAQJ-CGiTbe0N.js +1 -0
  164. package/payload/server/public/assets/{page-ef-6rRPL.js → page-B4kllGd_.js} +6 -6
  165. package/payload/server/public/assets/page-DbfE7QwJ.js +1 -0
  166. package/payload/server/public/assets/{pdf-render-CeX6d2v7.js → pdf-render-riHufgrn.js} +1 -1
  167. package/payload/server/public/assets/pie-ZZUOXDRM-BrK023dE.js +1 -0
  168. package/payload/server/public/assets/{pieDiagram-DEJITSTG-6TsUDkkQ.js → pieDiagram-DEJITSTG-BeUGoYS7.js} +1 -1
  169. package/payload/server/public/assets/public-VtmyYz8E.js +1 -0
  170. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CWw9gOI4.js → quadrantDiagram-34T5L4WZ-CX82WgKy.js} +1 -1
  171. package/payload/server/public/assets/radar-PYXPWWZC-p7NRamXX.js +1 -0
  172. package/payload/server/public/assets/{reduce-BUmc8RDR.js → reduce-COyotL2V.js} +1 -1
  173. package/payload/server/public/assets/{requirementDiagram-MS252O5E-DCOlxNvx.js → requirementDiagram-MS252O5E-CzqebQXj.js} +1 -1
  174. package/payload/server/public/assets/{rotate-ccw-DXEKUhXP.js → rotate-ccw-DaZdB2cz.js} +1 -1
  175. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-CW4Xbir3.js → sankeyDiagram-XADWPNL6-CaHi1yzU.js} +1 -1
  176. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-vhb_pBLG.js → sequenceDiagram-FGHM5R23-BF2AeiKW.js} +1 -1
  177. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-Cb1WExEX.js → stateDiagram-FHFEXIEX-DtUCA8Ih.js} +1 -1
  178. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-a4FvB3L8.js +1 -0
  179. package/payload/server/public/assets/tasks-CvqVDbfO.js +1 -0
  180. package/payload/server/public/assets/{time-entry-format-CsYAjplS.js → time-entry-format-TD-PPEIu.js} +1 -1
  181. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BAHTf_AF.js → timeline-definition-GMOUNBTQ-D3Q1YrKY.js} +1 -1
  182. package/payload/server/public/assets/treeView-SZITEDCU-CoWk8VoG.js +1 -0
  183. package/payload/server/public/assets/treemap-W4RFUUIX-BhWeGj8Y.js +1 -0
  184. package/payload/server/public/assets/{triangle-alert-0lSBIUg-.js → triangle-alert-BaLhVNnn.js} +1 -1
  185. package/payload/server/public/assets/useCopyFeedback-BhjPzRa3.js +1 -0
  186. package/payload/server/public/assets/useSubAccountSwitcher-CWRpVt1-.css +1 -0
  187. package/payload/server/public/assets/{useSubAccountSwitcher-BZnzc5C3.js → useSubAccountSwitcher-HEVLb6Z9.js} +2 -2
  188. package/payload/server/public/assets/useVoiceRecorder-CfGgj3vm.js +2 -0
  189. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-DtEttvW4.js → vennDiagram-DHZGUBPP-DnjzQDGf.js} +1 -1
  190. package/payload/server/public/assets/wardley-RL74JXVD-C0q3Hqng.js +1 -0
  191. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-K06y6CVJ.js → wardleyDiagram-NUSXRM2D-T3WHrUvi.js} +1 -1
  192. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-D66c40lp.js → xychartDiagram-5P7HB3ND-2Efyv4h2.js} +1 -1
  193. package/payload/server/public/brand-defaults.css +1 -0
  194. package/payload/server/public/browser.html +4 -5
  195. package/payload/server/public/calendar.html +5 -6
  196. package/payload/server/public/chat.html +12 -13
  197. package/payload/server/public/data.html +11 -12
  198. package/payload/server/public/graph.html +10 -11
  199. package/payload/server/public/index.html +14 -15
  200. package/payload/server/public/operator.html +14 -15
  201. package/payload/server/public/privacy.html +1 -0
  202. package/payload/server/public/public.html +12 -13
  203. package/payload/server/public/tasks.html +7 -5
  204. package/payload/server/public/vnc-popout.html +1 -0
  205. package/payload/server/server.js +833 -389
  206. package/payload/server/{src-XQ63FPRE.js → src-JJ6PHO5I.js} +1 -1
  207. package/payload/server/public/assets/AdminShell-D9zaUxPd.js +0 -2
  208. package/payload/server/public/assets/Checkbox-DcbbAIe5.js +0 -1
  209. package/payload/server/public/assets/activity-C9P5NjEZ.js +0 -1
  210. package/payload/server/public/assets/admin-D2KLZnQW.js +0 -1
  211. package/payload/server/public/assets/architecture-YZFGNWBL-CZQzGRSM.js +0 -1
  212. package/payload/server/public/assets/browser-CDWqEiCD.js +0 -1
  213. package/payload/server/public/assets/calendar-BG7fOpbB.js +0 -1
  214. package/payload/server/public/assets/channel-zDUAN9Ks.js +0 -1
  215. package/payload/server/public/assets/chat-RyH-WYQC.js +0 -1
  216. package/payload/server/public/assets/chevron-left-DmXCr6nB.js +0 -1
  217. package/payload/server/public/assets/chunk-426QAEUC-iJnqNRug.js +0 -1
  218. package/payload/server/public/assets/chunk-QZHKN3VN-Qlc2PFAj.js +0 -1
  219. package/payload/server/public/assets/classDiagram-6PBFFD2Q-CFsKL52p.js +0 -1
  220. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BWBUUCiO.js +0 -1
  221. package/payload/server/public/assets/clone-h5XhkDUL.js +0 -1
  222. package/payload/server/public/assets/data-BaY_LkLR.js +0 -1
  223. package/payload/server/public/assets/gitGraph-7Q5UKJZL-CBZPQJLj.js +0 -1
  224. package/payload/server/public/assets/graph-labels-DD-GqS4v.js +0 -1
  225. package/payload/server/public/assets/info-OMHHGYJF-1vIoX-kP.js +0 -1
  226. package/payload/server/public/assets/infoDiagram-42DDH7IO-KHcXRaan.js +0 -2
  227. package/payload/server/public/assets/operator-BZeeyait.js +0 -1
  228. package/payload/server/public/assets/packet-4T2RLAQJ-CTU0gCGb.js +0 -1
  229. package/payload/server/public/assets/page-B0obcg3b.js +0 -1
  230. package/payload/server/public/assets/pie-ZZUOXDRM-D-EEHxhC.js +0 -1
  231. package/payload/server/public/assets/public-9D3MMOkC.js +0 -1
  232. package/payload/server/public/assets/radar-PYXPWWZC-DHXATZGB.js +0 -1
  233. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CxOIKvXh.js +0 -1
  234. package/payload/server/public/assets/tasks-BB0L7e5a.js +0 -1
  235. package/payload/server/public/assets/treeView-SZITEDCU-CC7ouMFd.js +0 -1
  236. package/payload/server/public/assets/treemap-W4RFUUIX-CaCg27im.js +0 -1
  237. package/payload/server/public/assets/useCopyFeedback-jTBQ2h0V.js +0 -1
  238. package/payload/server/public/assets/useSelectionMode-BooArySZ.js +0 -1
  239. package/payload/server/public/assets/useSubAccountSwitcher-D9knHOKq.css +0 -1
  240. package/payload/server/public/assets/useVoiceRecorder-PUT8q_sm.js +0 -2
  241. package/payload/server/public/assets/wardley-RL74JXVD-bMPsqQZ7.js +0 -1
  242. /package/payload/server/public/assets/{_baseFor-k8hYPe-U.js → _baseFor-Bjj9It3Q.js} +0 -0
  243. /package/payload/server/public/assets/{array-BFDiaBgf.js → array-BrrxEo12.js} +0 -0
  244. /package/payload/server/public/assets/{cytoscape.esm-Ch0xptA4.js → cytoscape.esm-CTWJxUxo.js} +0 -0
  245. /package/payload/server/public/assets/{defaultLocale-ZeknFqNe.js → defaultLocale-CFDbRV4k.js} +0 -0
  246. /package/payload/server/public/assets/{dist-B2N0v8hZ.js → dist-BATfy7Sn.js} +0 -0
  247. /package/payload/server/public/assets/{init-0VJVrkRJ.js → init-dXCzGgLP.js} +0 -0
  248. /package/payload/server/public/assets/{katex-Br7bH10N.js → katex-CHA1JFYJ.js} +0 -0
  249. /package/payload/server/public/assets/{path-INs8XTPH.js → path-CCRpdXhK.js} +0 -0
  250. /package/payload/server/public/assets/{preload-helper-D_sPnjeL.js → preload-helper-CH6UZRzu.js} +0 -0
  251. /package/payload/server/public/assets/{rough.esm-DU9bZLvZ.js → rough.esm-Bud4g4fh.js} +0 -0
  252. /package/payload/server/public/assets/{src-DHSQDYx-.js → src-DNdBRXXx.js} +0 -0
@@ -10,7 +10,7 @@
10
10
  * Aligns with `feedback_no_stdout_parsing_for_control_flow.md`.
11
11
  */
12
12
  import { Client, ResponseType } from "@microsoft/microsoft-graph-client";
13
- import { refreshAccessToken } from "../auth/device-flow.js";
13
+ import { refreshAccessToken, TokenRefreshError } from "../auth/device-flow.js";
14
14
  import { log } from "./log.js";
15
15
  /** Pull (statusCode, code, headers) from whatever shape the SDK threw. */
16
16
  function normalizeGraphError(err) {
@@ -123,6 +123,10 @@ export function createGraphClient(config) {
123
123
  },
124
124
  });
125
125
  }
126
+ /** How long a process waits for a peer's in-flight refresh before giving up.
127
+ * Bounded because the caller is a live tool call, not a background job. */
128
+ const REFRESH_LOCK_WAIT_MS = 10 * 1000;
129
+ const REFRESH_LOCK_POLL_MS = 100;
126
130
  /**
127
131
  * Return a usable access token, refreshing via the stored refresh token when
128
132
  * needed. With `forceRefresh`, skip the local `needsRefresh()` fast-path and
@@ -143,31 +147,130 @@ export async function getOrRefreshAccessToken(config, forceRefresh = false) {
143
147
  if (!forceRefresh && !tokenStore.needsRefresh()) {
144
148
  return blob.accessToken;
145
149
  }
146
- const oldExpSec = Math.floor(blob.accessTokenExpiry / 1000);
150
+ // One refresh per mailbox at a time, across processes. Two winners would each
151
+ // receive their own token pair from Microsoft and the later write would
152
+ // silently displace the earlier one.
153
+ if (!tokenStore.tryAcquireRefreshLock()) {
154
+ log({ event: "token-refresh-deferred", account: accountId });
155
+ return waitForPeerRefresh(config, blob.accessToken, REFRESH_LOCK_WAIT_MS, REFRESH_LOCK_POLL_MS);
156
+ }
157
+ // The outer try exists only to release the lock. Terminal guards below sit in
158
+ // it but OUTSIDE the inner catch on purpose: handleRefreshFailure classifies
159
+ // refresh failures, and routing an auth-required guard through it would
160
+ // relabel a mailbox that needs re-registering as "retry shortly".
147
161
  try {
148
- const refreshed = await refreshAccessToken({
149
- clientId,
150
- tenantId,
151
- refreshToken: blob.refreshToken,
152
- });
153
- const updated = tokenStore.store(refreshed.access_token, refreshed.refresh_token ?? blob.refreshToken, refreshed.expires_in, { graphUserId: blob.graphUserId, mail: blob.mail, scopes: blob.scopes });
154
- log({
155
- event: "token-refreshed",
156
- account: accountId,
157
- oldExpSec,
158
- newExpSec: Math.floor(updated.accessTokenExpiry / 1000),
159
- });
160
- return updated.accessToken;
162
+ // A peer may have refreshed between our needsRefresh() check above and our
163
+ // acquire just now. Re-read before spending a network call, so we neither
164
+ // refresh redundantly nor send a refresh token that peer has superseded.
165
+ const current = tokenStore.reload() ?? blob;
166
+ if (!current.refreshToken) {
167
+ throw new Error(`auth-required: Outlook refresh token expired for account=${accountId}; run outlook-account-register.`);
168
+ }
169
+ if (!forceRefresh && !tokenStore.needsRefresh()) {
170
+ log({ event: "token-refresh-superseded", account: accountId });
171
+ return current.accessToken;
172
+ }
173
+ const oldExpSec = Math.floor(current.accessTokenExpiry / 1000);
174
+ try {
175
+ const refreshed = await refreshAccessToken({
176
+ clientId,
177
+ tenantId,
178
+ refreshToken: current.refreshToken,
179
+ });
180
+ const updated = tokenStore.store(refreshed.access_token, refreshed.refresh_token ?? current.refreshToken, refreshed.expires_in, { graphUserId: current.graphUserId, mail: current.mail, scopes: current.scopes });
181
+ log({
182
+ event: "token-refreshed",
183
+ account: accountId,
184
+ oldExpSec,
185
+ newExpSec: Math.floor(updated.accessTokenExpiry / 1000),
186
+ });
187
+ return updated.accessToken;
188
+ }
189
+ catch (err) {
190
+ throw handleRefreshFailure(err, tokenStore, accountId);
191
+ }
192
+ }
193
+ finally {
194
+ tokenStore.releaseRefreshLock();
161
195
  }
162
- catch (err) {
196
+ }
197
+ /** The one retryable-refresh error. Single source so the three sites that
198
+ * surface it cannot drift apart. */
199
+ function retryableRefreshError(accountId) {
200
+ return new Error(`Outlook token refresh temporarily unavailable for account=${accountId}; credentials kept, retry shortly.`);
201
+ }
202
+ /**
203
+ * Wait for the peer holding the refresh lock to publish a new access token.
204
+ *
205
+ * Keys on the access token *changing*, not on `needsRefresh()` going false.
206
+ * Under `forceRefresh` — the 401 retry after a server-side revocation — the
207
+ * stored token is locally un-expired, so a freshness check would hand back the
208
+ * very token Graph just rejected. Task 1480 exists because that short-circuit
209
+ * burned the 401 retry once already.
210
+ *
211
+ * Never refreshes on its own. Issuing a second refresh is the exact race the
212
+ * lock exists to remove.
213
+ */
214
+ async function waitForPeerRefresh(config, previousAccessToken, waitMs, pollMs) {
215
+ const { tokenStore, accountId } = config;
216
+ const deadline = Date.now() + waitMs;
217
+ while (Date.now() < deadline) {
218
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
219
+ const fresh = tokenStore.reload();
220
+ if (fresh && fresh.accessToken !== previousAccessToken) {
221
+ log({ event: "token-refresh-peer-observed", account: accountId });
222
+ return fresh.accessToken;
223
+ }
224
+ if (!tokenStore.refreshLockHeld()) {
225
+ // The lock is free. Re-read once more before calling it a failure: the
226
+ // peer can store its token and release in the gap between the reload
227
+ // above and this check, and reporting "unavailable" while a valid fresh
228
+ // token sits on disk would fail the caller for no reason.
229
+ const settled = tokenStore.reload();
230
+ if (settled && settled.accessToken !== previousAccessToken) {
231
+ log({ event: "token-refresh-peer-observed", account: accountId });
232
+ return settled.accessToken;
233
+ }
234
+ log({ event: "token-refresh-peer-failed", account: accountId });
235
+ throw retryableRefreshError(accountId);
236
+ }
237
+ }
238
+ log({ event: "token-refresh-peer-timeout", account: accountId, waitMs });
239
+ throw retryableRefreshError(accountId);
240
+ }
241
+ /**
242
+ * Decide whether a failed refresh means the credentials are dead or merely that
243
+ * this attempt did not land, and return the error to throw.
244
+ *
245
+ * Only `invalid_grant` — a refresh token that is revoked, expired, or already
246
+ * consumed — clears the store. Every other failure (network down, Microsoft
247
+ * 5xx, a local write error) leaves the tokens alone. Clearing on all of them
248
+ * was destroying healthy mailboxes over transient faults and forcing an
249
+ * operator to re-register a mailbox that was fine a moment earlier.
250
+ */
251
+ function handleRefreshFailure(err, tokenStore, accountId) {
252
+ const azure = err instanceof TokenRefreshError ? err.azure : null;
253
+ if (azure && azure.error === "invalid_grant") {
163
254
  log({
164
- event: "token-refresh-failed",
255
+ event: "token-refresh-revoked",
165
256
  account: accountId,
257
+ oauthError: azure.error,
258
+ suberror: azure.suberror,
259
+ aadsts: azure.aadsts,
260
+ correlationId: azure.correlationId,
166
261
  reason: err.message,
167
262
  });
168
263
  tokenStore.clear();
169
- throw new Error(`Outlook token refresh failed for account=${accountId}; re-auth required.`);
264
+ return new Error(`Outlook token refresh failed for account=${accountId}; re-auth required.`);
170
265
  }
266
+ log({
267
+ event: "token-refresh-retryable",
268
+ account: accountId,
269
+ oauthError: azure?.error ?? null,
270
+ httpStatus: azure?.httpStatus ?? null,
271
+ reason: err.message,
272
+ });
273
+ return retryableRefreshError(accountId);
171
274
  }
172
275
  /**
173
276
  * Run a Graph call with classification, single retry on `auth` (refresh +
@@ -320,4 +423,6 @@ export async function patchGraph(config, path, body, opts = { tool: "graph" }) {
320
423
  export async function deleteGraph(config, path, opts = { tool: "graph" }) {
321
424
  return writeGraph(config, "delete", path, undefined, opts);
322
425
  }
426
+ /** Exported for tests only. */
427
+ export const _internals = { waitForPeerRefresh };
323
428
  //# sourceMappingURL=graph-client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"graph-client.js","sourceRoot":"","sources":["../../src/lib/graph-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"graph-client.js","sourceRoot":"","sources":["../../src/lib/graph-client.ts"],"names":[],"mappings":"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"}
@@ -136,7 +136,10 @@ The JSON blob shape:
136
136
  }
137
137
  ```
138
138
 
139
- Atomic write: temp + rename. Either old or new tokens, never partial.
139
+ Atomic write: temp + rename. Either old or new tokens, never partial. The temp
140
+ name embeds the writer's pid, so two processes writing the same mailbox never
141
+ share a temp path. A shared name meant one writer renamed the temp away and the
142
+ other's rename then failed with `ENOENT`.
140
143
 
141
144
  ## Mailbox selection
142
145
 
@@ -150,4 +153,44 @@ local stores (no network) and flags any whose blob cannot be read.
150
153
 
151
154
  ## Refresh
152
155
 
153
- Triggered automatically when the access token is within `5 minutes` of expiry (`refreshThresholdMs`). Failure → terminal `token-refresh-failed`, tokens cleared, operator must re-register. Refresh tokens last 90 days.
156
+ Triggered automatically when the access token is within `5 minutes` of expiry
157
+ (`refreshThresholdMs`). Refresh tokens last 90 days.
158
+
159
+ **Only a revoked grant clears the tokens.** A refresh failure whose Azure body
160
+ carries `error=invalid_grant` — the refresh token revoked, expired, or already
161
+ consumed — logs `token-refresh-revoked`, clears the mailbox, and requires
162
+ `outlook-account-register`. Every other failure (network down, Microsoft 5xx, a
163
+ local write error) logs `token-refresh-retryable`, keeps the tokens, and
164
+ surfaces as retryable. Clearing on all of them used to destroy healthy mailboxes
165
+ over transient faults.
166
+
167
+ **One refresh per mailbox at a time, across processes.** Two Outlook MCP
168
+ processes on one account — the admin PTY conversation and a passive-channel
169
+ spawn, say — can decide the same mailbox needs refreshing at the same moment. A
170
+ lockfile at `<tokens.enc>.lock` admits one. The other waits up to 10 seconds for
171
+ the winner's token and never issues a refresh of its own, so whether Azure
172
+ invalidates a rotated refresh token never becomes load-bearing. A lock whose
173
+ holder process is dead, or which is older than 60 seconds, is stolen; that bound
174
+ sits well inside the 5-minute refresh window, so a stolen lock still leaves time
175
+ to refresh before the token actually expires.
176
+
177
+ The waiter keys on the access token *changing*, not on it merely being
178
+ un-expired. Under the 401 retry the stored token is locally fresh but Graph has
179
+ already rejected it, so a freshness check would hand back the bad token — the
180
+ failure this design exists to prevent.
181
+
182
+ Log events, for grepping a refresh incident:
183
+
184
+ | Event | Meaning |
185
+ |---|---|
186
+ | `token-refreshed` | This process refreshed successfully. |
187
+ | `token-refresh-revoked` | `invalid_grant`. Tokens cleared, re-registration required. |
188
+ | `token-refresh-retryable` | Transient failure. Tokens kept. |
189
+ | `token-refresh-deferred` | A peer held the lock; this process is waiting. |
190
+ | `token-refresh-superseded` | A peer refreshed first; this process adopted its token without a network call. |
191
+ | `token-refresh-peer-observed` | The waiter got the peer's new token. |
192
+ | `token-refresh-peer-failed` | The peer released the lock without publishing a token. |
193
+ | `token-refresh-peer-timeout` | The waiter gave up after 10 seconds. |
194
+
195
+ "Credentials wiped" and "transient write race" are separate events by design.
196
+ Only `token-refresh-revoked` means a mailbox actually needs re-registering.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=portal-index-audit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portal-index-audit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/portal-index-audit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,66 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { auditPortalIndex, formatPortalIndexAudit, STALE_AFTER_INTERVALS, } from "../lib/portal-index-audit.js";
3
+ const HOUR = 3_600_000;
4
+ const NOW = 1_800_000_000_000;
5
+ const fresh = (ms) => ({
6
+ "acc-a": { lastPushMs: NOW - ms, rows: 41, exposed: ["output", "quotes"] },
7
+ });
8
+ describe("auditPortalIndex", () => {
9
+ it("reports a recently pushed account as healthy", () => {
10
+ const r = auditPortalIndex(["acc-a"], fresh(HOUR - 1), NOW, HOUR);
11
+ expect(r.stale).toBe(0);
12
+ expect(r.neverRan).toBe(0);
13
+ expect(r.accounts[0].ageMin).toBe(59);
14
+ expect(r.accounts[0].rows).toBe(41);
15
+ });
16
+ it("tolerates one missed cycle, flags two", () => {
17
+ // One miss is ordinary (a slow push, a device asleep at the tick). The
18
+ // threshold has to be loose enough that firing means something.
19
+ expect(auditPortalIndex(["acc-a"], fresh(HOUR * 1.5), NOW, HOUR).stale).toBe(0);
20
+ expect(auditPortalIndex(["acc-a"], fresh(HOUR * STALE_AFTER_INTERVALS + 1), NOW, HOUR).stale).toBe(1);
21
+ });
22
+ // The signature of the failure this module exists for: the spawn never fires,
23
+ // so no state is ever written and no other line is ever emitted.
24
+ it("reports an enrolled account with no state as never-ran, not as stale", () => {
25
+ const r = auditPortalIndex(["acc-a"], {}, NOW, HOUR);
26
+ expect(r.neverRan).toBe(1);
27
+ expect(r.stale).toBe(0);
28
+ expect(r.accounts[0].ageMin).toBeNull();
29
+ });
30
+ it("ignores accounts that have no portal", () => {
31
+ const r = auditPortalIndex([], fresh(HOUR * 99), NOW, HOUR);
32
+ expect(r.accounts).toEqual([]);
33
+ expect(r.stale).toBe(0);
34
+ });
35
+ it("survives a malformed state entry rather than throwing", () => {
36
+ const bad = { "acc-a": { rows: 1, exposed: [] } };
37
+ expect(auditPortalIndex(["acc-a"], bad, NOW, HOUR).neverRan).toBe(1);
38
+ });
39
+ it("never reports a negative age when a clock moves backwards", () => {
40
+ const r = auditPortalIndex(["acc-a"], fresh(-HOUR), NOW, HOUR);
41
+ expect(r.accounts[0].ageMin).toBe(0);
42
+ expect(r.accounts[0].stale).toBe(false);
43
+ });
44
+ });
45
+ describe("formatPortalIndexAudit", () => {
46
+ it("emits a summary line even when everything is healthy", () => {
47
+ // An audit that goes quiet on success is indistinguishable from an audit
48
+ // that stopped running — the exact defect this module closes.
49
+ const lines = formatPortalIndexAudit(auditPortalIndex(["acc-a"], fresh(60_000), NOW, HOUR));
50
+ expect(lines).toHaveLength(1);
51
+ expect(lines[0]).toBe("[portal-index] op=audit accounts=1 stale=0 never-ran=0");
52
+ });
53
+ it("names a stale account with its age and exposed set", () => {
54
+ const lines = formatPortalIndexAudit(auditPortalIndex(["acc-a"], fresh(HOUR * 3), NOW, HOUR));
55
+ expect(lines[0]).toContain("account=acc-a");
56
+ expect(lines[0]).toContain("ageMin=180");
57
+ expect(lines[0]).toContain("exposed=output,quotes");
58
+ expect(lines[0]).toContain("result=stale");
59
+ });
60
+ it("distinguishes never-ran from stale in the line itself", () => {
61
+ const lines = formatPortalIndexAudit(auditPortalIndex(["acc-a"], {}, NOW, HOUR));
62
+ expect(lines[0]).toContain("result=never-ran");
63
+ expect(lines[0]).toContain("rows=0");
64
+ });
65
+ });
66
+ //# sourceMappingURL=portal-index-audit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portal-index-audit.test.js","sourceRoot":"","sources":["../../src/__tests__/portal-index-audit.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ export interface PortalIndexEntry {
2
+ lastPushMs: number;
3
+ rows: number;
4
+ exposed: string[];
5
+ }
6
+ export type PortalIndexState = Record<string, PortalIndexEntry>;
7
+ export interface PortalAccountAudit {
8
+ accountId: string;
9
+ /** No state entry at all: enrolled for a portal, never successfully pushed. */
10
+ neverRan: boolean;
11
+ /** Whole minutes since the last verified push; null when it never ran. */
12
+ ageMin: number | null;
13
+ rows: number | null;
14
+ exposed: string[];
15
+ stale: boolean;
16
+ }
17
+ export interface PortalIndexAuditResult {
18
+ accounts: PortalAccountAudit[];
19
+ stale: number;
20
+ neverRan: number;
21
+ }
22
+ /**
23
+ * Two intervals, not one: a single missed cycle is ordinary (a slow push, a
24
+ * device asleep at the tick). Two consecutive misses is a pattern, and the
25
+ * threshold has to be loose enough that the line means something when it fires.
26
+ */
27
+ export declare const STALE_AFTER_INTERVALS = 2;
28
+ /**
29
+ * @param portalAccountIds accounts carrying a data-portal.json — the enrolment
30
+ * signal. An account absent from this list has no portal and is not audited.
31
+ * @param state the push's own success record, keyed by accountId.
32
+ * @param nowMs
33
+ * @param intervalMs the push's cadence.
34
+ */
35
+ export declare function auditPortalIndex(portalAccountIds: string[], state: PortalIndexState, nowMs: number, intervalMs: number): PortalIndexAuditResult;
36
+ /** One line per account needing attention, plus one summary line that fires
37
+ * even when everything is healthy — an audit that goes quiet when it finds
38
+ * nothing is indistinguishable from an audit that stopped running, which is
39
+ * the exact defect this whole module exists to close. */
40
+ export declare function formatPortalIndexAudit(result: PortalIndexAuditResult): string[];
41
+ //# sourceMappingURL=portal-index-audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portal-index-audit.d.ts","sourceRoot":"","sources":["../../src/lib/portal-index-audit.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;AAE/D,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,+EAA+E;IAC/E,QAAQ,EAAE,OAAO,CAAA;IACjB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,KAAK,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,kBAAkB,EAAE,CAAA;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,IAAI,CAAA;AAEtC;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,gBAAgB,EAAE,MAAM,EAAE,EAC1B,KAAK,EAAE,gBAAgB,EACvB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GACjB,sBAAsB,CA0BxB;AAED;;;0DAG0D;AAC1D,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,EAAE,CAgB/E"}
@@ -0,0 +1,75 @@
1
+ // Standing check: is each portal-serving account's folder index still being
2
+ // refreshed?
3
+ //
4
+ // Every other signal in the index push is event-driven — the script logs when
5
+ // it runs. So the one failure it cannot report is its own absence: if the
6
+ // hourly spawn never fires, there is no line at all, the client's folder tree
7
+ // silently freezes at whatever it last held, and silence is indistinguishable
8
+ // from a clean run.
9
+ //
10
+ // This audit therefore must NOT depend on the thing it audits. It runs
11
+ // in-process inside the heartbeat rather than as a spawned child, and it reads
12
+ // only local files — the state the push writes on success, and the presence of
13
+ // a data-portal.json. No D1, no Cloudflare credential, no second spawn that
14
+ // could fail the same way.
15
+ //
16
+ // The push writes state ONLY after a verified write, so a stale entry means the
17
+ // push stopped succeeding, not merely that it stopped logging.
18
+ /**
19
+ * Two intervals, not one: a single missed cycle is ordinary (a slow push, a
20
+ * device asleep at the tick). Two consecutive misses is a pattern, and the
21
+ * threshold has to be loose enough that the line means something when it fires.
22
+ */
23
+ export const STALE_AFTER_INTERVALS = 2;
24
+ /**
25
+ * @param portalAccountIds accounts carrying a data-portal.json — the enrolment
26
+ * signal. An account absent from this list has no portal and is not audited.
27
+ * @param state the push's own success record, keyed by accountId.
28
+ * @param nowMs
29
+ * @param intervalMs the push's cadence.
30
+ */
31
+ export function auditPortalIndex(portalAccountIds, state, nowMs, intervalMs) {
32
+ const threshold = intervalMs * STALE_AFTER_INTERVALS;
33
+ const accounts = portalAccountIds.map((accountId) => {
34
+ const entry = state[accountId];
35
+ if (!entry || typeof entry.lastPushMs !== "number") {
36
+ // Never-ran is reported as its own condition rather than as infinite
37
+ // staleness: the remedies differ (a first run vs a broken cycle), and a
38
+ // brand-new portal must not look like a regression on its first tick.
39
+ return { accountId, neverRan: true, ageMin: null, rows: null, exposed: [], stale: false };
40
+ }
41
+ const ageMs = Math.max(0, nowMs - entry.lastPushMs);
42
+ return {
43
+ accountId,
44
+ neverRan: false,
45
+ ageMin: Math.floor(ageMs / 60_000),
46
+ rows: typeof entry.rows === "number" ? entry.rows : null,
47
+ exposed: Array.isArray(entry.exposed) ? entry.exposed : [],
48
+ stale: ageMs > threshold,
49
+ };
50
+ });
51
+ return {
52
+ accounts,
53
+ stale: accounts.filter((a) => a.stale).length,
54
+ neverRan: accounts.filter((a) => a.neverRan).length,
55
+ };
56
+ }
57
+ /** One line per account needing attention, plus one summary line that fires
58
+ * even when everything is healthy — an audit that goes quiet when it finds
59
+ * nothing is indistinguishable from an audit that stopped running, which is
60
+ * the exact defect this whole module exists to close. */
61
+ export function formatPortalIndexAudit(result) {
62
+ const lines = [];
63
+ for (const a of result.accounts) {
64
+ if (a.neverRan) {
65
+ lines.push(`[portal-index] op=audit account=${a.accountId} rows=0 result=never-ran`);
66
+ }
67
+ else if (a.stale) {
68
+ lines.push(`[portal-index] op=audit account=${a.accountId} rows=${a.rows} ageMin=${a.ageMin} ` +
69
+ `exposed=${a.exposed.join(",") || "none"} result=stale`);
70
+ }
71
+ }
72
+ lines.push(`[portal-index] op=audit accounts=${result.accounts.length} stale=${result.stale} never-ran=${result.neverRan}`);
73
+ return lines;
74
+ }
75
+ //# sourceMappingURL=portal-index-audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portal-index-audit.js","sourceRoot":"","sources":["../../src/lib/portal-index-audit.ts"],"names":[],"mappings":"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"}
@@ -28,6 +28,7 @@ import cronParser from "cron-parser";
28
28
  const { parseExpression } = cronParser;
29
29
  import { chooseDispatchMode, dispatchAgentTurn, runStandingAudit, auditSelfCancels } from "./agent-turn-dispatch.js";
30
30
  import { spawn } from "node:child_process";
31
+ import { auditPortalIndex, formatPortalIndexAudit, } from "../lib/portal-index-audit.js";
31
32
  import { shouldRunSentSweep, SENT_SWEEP_INTERVAL_MS, } from "../lib/sent-sweep-throttle.js";
32
33
  // ---------------------------------------------------------------------------
33
34
  // Brand-aware config directory — reads configDir from brand.json stamped at install time.
@@ -492,6 +493,110 @@ function maybeRunSentSweep(platformRoot) {
492
493
  });
493
494
  child.unref();
494
495
  }
496
+ const PORTAL_INDEX_STATE_FILE = resolve(CONFIG_DIR, "portal-index-push-state.json");
497
+ const PORTAL_INDEX_INTERVAL_MS = 60 * 60 * 1000;
498
+ /**
499
+ * Publish each portal-serving account's deliverable folder index, at most hourly.
500
+ *
501
+ * Spawned rather than imported, for the same reason as the sent-copy sweep: the
502
+ * script lives in the cloudflare plugin and talks to Cloudflare's API, which does
503
+ * not belong inside the scheduling build.
504
+ *
505
+ * The script decides WHICH accounts have portals (a `data-portal.json` is the
506
+ * signal) and which folders each one exposes. Nothing about that is configured
507
+ * here, so this stays a spawn on a timer.
508
+ *
509
+ * State is written before the spawn, not after it completes: a push that hangs
510
+ * must not cause a fresh one to start behind it every minute.
511
+ */
512
+ function maybeRunPortalIndexPush(platformRoot) {
513
+ let prev;
514
+ try {
515
+ prev = JSON.parse(readFileSync(PORTAL_INDEX_STATE_FILE, "utf8"));
516
+ }
517
+ catch {
518
+ prev = undefined;
519
+ }
520
+ const now = Date.now();
521
+ if (prev?.lastRunMs && now - prev.lastRunMs < PORTAL_INDEX_INTERVAL_MS)
522
+ return;
523
+ const script = resolve(platformRoot, "plugins/cloudflare/bin/portal-index-push.mjs");
524
+ if (!existsSync(script)) {
525
+ console.error(`[portal-index] skipped: ${script} not found`);
526
+ return;
527
+ }
528
+ try {
529
+ writeFileSync(PORTAL_INDEX_STATE_FILE, JSON.stringify({ lastRunMs: now }));
530
+ }
531
+ catch (err) {
532
+ console.error(`[portal-index] state write failed: ${err instanceof Error ? err.message : String(err)}`);
533
+ return;
534
+ }
535
+ const child = spawn(process.execPath, [script, "--all-accounts"], {
536
+ env: { ...process.env, PLATFORM_ROOT: platformRoot },
537
+ stdio: ["ignore", "inherit", "inherit"],
538
+ detached: true,
539
+ });
540
+ child.unref();
541
+ }
542
+ const PORTAL_AUDIT_STATE_FILE = resolve(CONFIG_DIR, "portal-index-audit-state.json");
543
+ /**
544
+ * Standing check: is each portal-serving account's index still being refreshed?
545
+ *
546
+ * Runs IN-PROCESS, not as a spawn, and reads only local files. That is the
547
+ * whole point — every other signal in the push is event-driven, so the one
548
+ * failure none of them can report is the push not running at all. An audit that
549
+ * went through the same spawn, or needed the same Cloudflare credential, would
550
+ * be blind to exactly the case it exists for.
551
+ *
552
+ * Throttled to the push's own cadence so the summary line stays readable in a
553
+ * log that already carries a line a minute.
554
+ */
555
+ function auditPortalIndexPush(platformRoot) {
556
+ let prev;
557
+ try {
558
+ prev = JSON.parse(readFileSync(PORTAL_AUDIT_STATE_FILE, "utf8"));
559
+ }
560
+ catch {
561
+ prev = undefined;
562
+ }
563
+ const now = Date.now();
564
+ if (prev?.lastRunMs && now - prev.lastRunMs < PORTAL_INDEX_INTERVAL_MS)
565
+ return;
566
+ const dataRoot = resolve(platformRoot, "..", "data");
567
+ const accountsDir = join(dataRoot, "accounts");
568
+ if (!existsSync(accountsDir))
569
+ return;
570
+ // A data-portal.json is the enrolment signal — the same one the push uses, so
571
+ // the two cannot disagree about which accounts are in scope.
572
+ let portalAccountIds = [];
573
+ try {
574
+ portalAccountIds = readdirSync(accountsDir).filter((d) => existsSync(join(accountsDir, d, "data-portal.json")));
575
+ }
576
+ catch {
577
+ return;
578
+ }
579
+ if (portalAccountIds.length === 0)
580
+ return;
581
+ let state = {};
582
+ try {
583
+ state = JSON.parse(readFileSync(join(dataRoot, "portal-index-state.json"), "utf8"));
584
+ }
585
+ catch {
586
+ // Absent state with portals enrolled is itself the never-ran signal, so an
587
+ // empty object is the correct input here rather than a reason to bail.
588
+ state = {};
589
+ }
590
+ for (const line of formatPortalIndexAudit(auditPortalIndex(portalAccountIds, state, now, PORTAL_INDEX_INTERVAL_MS))) {
591
+ console.error(line);
592
+ }
593
+ try {
594
+ writeFileSync(PORTAL_AUDIT_STATE_FILE, JSON.stringify({ lastRunMs: now }));
595
+ }
596
+ catch (err) {
597
+ console.error(`[portal-index] op=audit-state-write-failed err="${err instanceof Error ? err.message : String(err)}"`);
598
+ }
599
+ }
495
600
  async function main() {
496
601
  if (!acquireLock()) {
497
602
  console.error("[check-due] another instance running, exiting");
@@ -672,6 +777,23 @@ async function main() {
672
777
  catch (err) {
673
778
  console.error(`[email-sent-sweep] spawn failed: ${err instanceof Error ? err.message : String(err)}`);
674
779
  }
780
+ // --- Data-portal folder index (hourly) ---
781
+ // Guarded on the same principle: an optional hourly publish must not cost
782
+ // the every-minute dispatcher its completion line.
783
+ try {
784
+ maybeRunPortalIndexPush(process.env.PLATFORM_ROOT);
785
+ }
786
+ catch (err) {
787
+ console.error(`[portal-index] spawn failed: ${err instanceof Error ? err.message : String(err)}`);
788
+ }
789
+ // Deliberately AFTER the spawn and independent of whether it succeeded:
790
+ // this is the check that speaks when the push does not.
791
+ try {
792
+ auditPortalIndexPush(process.env.PLATFORM_ROOT);
793
+ }
794
+ catch (err) {
795
+ console.error(`[portal-index] op=audit-failed err="${err instanceof Error ? err.message : String(err)}"`);
796
+ }
675
797
  console.error(`[check-due] done: ${processed} processed, ${dispatched} dispatched, ${errors} errors`);
676
798
  }
677
799
  finally {