@rubytech/create-maxy-code 0.1.488 → 0.1.490

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 (282) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/docs/superpowers/plans/2026-07-22-task-1899-reconcile-report-only.md +748 -0
  3. package/payload/platform/docs/superpowers/plans/2026-07-22-task-1910-two-way-portal-exchange.md +1716 -0
  4. package/payload/platform/docs/superpowers/specs/2026-07-22-task-1899-reconcile-report-only-design.md +121 -0
  5. package/payload/platform/docs/superpowers/specs/2026-07-22-task-1910-two-way-portal-exchange-design.md +103 -0
  6. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js +51 -0
  7. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js.map +1 -1
  8. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts +2 -2
  9. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts.map +1 -1
  10. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js +20 -4
  11. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js.map +1 -1
  12. package/payload/platform/lib/graph-mcp/src/__tests__/schema-cypher-parser.test.ts +55 -0
  13. package/payload/platform/lib/graph-mcp/src/schema-cypher-parser.ts +19 -4
  14. package/payload/platform/lib/graph-style/dist/index.d.ts +24 -0
  15. package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
  16. package/payload/platform/lib/graph-style/dist/index.js +25 -1
  17. package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
  18. package/payload/platform/lib/graph-style/src/__tests__/additional-base-labels.test.ts +22 -0
  19. package/payload/platform/lib/graph-style/src/index.ts +27 -0
  20. package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js +15 -0
  21. package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js.map +1 -1
  22. package/payload/platform/lib/storage-broker/dist/audit.d.ts +34 -1
  23. package/payload/platform/lib/storage-broker/dist/audit.d.ts.map +1 -1
  24. package/payload/platform/lib/storage-broker/dist/audit.js +28 -4
  25. package/payload/platform/lib/storage-broker/dist/audit.js.map +1 -1
  26. package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +13 -0
  27. package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
  28. package/payload/platform/lib/storage-broker/dist/cf-exec.js +22 -0
  29. package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
  30. package/payload/platform/lib/storage-broker/src/__tests__/audit.test.ts +21 -0
  31. package/payload/platform/lib/storage-broker/src/audit.ts +67 -3
  32. package/payload/platform/lib/storage-broker/src/cf-exec.ts +48 -0
  33. package/payload/platform/neo4j/schema.cypher +105 -0
  34. package/payload/platform/plugins/admin/skills/agent-builder/references/agent-pattern.md +34 -5
  35. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +8 -2
  36. package/payload/platform/plugins/cloudflare/PLUGIN.md +22 -2
  37. package/payload/platform/plugins/cloudflare/bin/portal-index-push.mjs +161 -41
  38. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-delete-withdraw.test.ts +95 -0
  39. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-directory-listing.test.ts +75 -13
  40. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-push.test.ts +216 -16
  41. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-indexed-download.test.ts +38 -2
  42. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-moved-download.test.ts +83 -0
  43. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-schema-shape.test.ts +48 -0
  44. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-sign.test.ts +62 -0
  45. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-target.test.ts +103 -0
  46. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-upload-multipart.test.ts +211 -0
  47. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-upload-target.test.ts +168 -0
  48. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-uploads-listing.test.ts +138 -0
  49. package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +179 -0
  50. package/payload/platform/plugins/cloudflare/mcp/__tests__/upload-route.test.ts +14 -13
  51. package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +87 -13
  52. package/payload/platform/plugins/cloudflare/skills/data-portal/schema.sql +87 -9
  53. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/portal-sign.mjs +74 -0
  54. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/target.ts +91 -0
  55. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +23 -0
  56. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/delete.ts +45 -0
  57. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +72 -5
  58. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +80 -13
  59. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/upload.ts +358 -16
  60. package/payload/platform/plugins/cloudflare/skills/data-portal/template/index.html +12 -1
  61. package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.css +45 -0
  62. package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.js +258 -21
  63. package/payload/platform/plugins/docs/references/calendar-booking.md +5 -1
  64. package/payload/platform/plugins/docs/references/internals.md +2 -0
  65. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js +61 -31
  66. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js.map +1 -1
  67. package/payload/platform/plugins/scheduling/PLUGIN.md +1 -1
  68. package/payload/platform/plugins/scheduling/mcp/dist/index.js +2 -2
  69. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  70. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js +70 -5
  71. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js.map +1 -1
  72. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.js +31 -6
  73. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.js.map +1 -1
  74. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +31 -4
  75. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
  76. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +18 -7
  77. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
  78. package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.d.ts +15 -6
  79. package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.d.ts.map +1 -1
  80. package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.js +24 -11
  81. package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.js.map +1 -1
  82. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +5 -122
  83. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  84. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +84 -5
  85. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
  86. package/payload/platform/scripts/__tests__/quarantine-restore.test.sh +143 -0
  87. package/payload/platform/scripts/quarantine-restore.mjs +175 -0
  88. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts +24 -7
  89. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts.map +1 -1
  90. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js +52 -74
  91. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js.map +1 -1
  92. package/payload/platform/services/claude-session-manager/dist/index.js +11 -8
  93. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  94. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +32 -0
  95. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  96. package/payload/platform/services/whatsapp-channel/dist/notification.js +32 -2
  97. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  98. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +6 -1
  99. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
  100. package/payload/platform/services/whatsapp-channel/dist/targets.js +6 -0
  101. package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
  102. package/payload/platform/templates/account-schema/SCHEMA.md +7 -4
  103. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
  104. package/payload/platform/templates/agents/passive/IDENTITY.md +6 -0
  105. package/payload/platform/templates/agents/public/IDENTITY.md +6 -0
  106. package/payload/platform/templates/specialists/agents/citation-auditor.md +6 -0
  107. package/payload/platform/templates/specialists/agents/coding-assistant.md +6 -0
  108. package/payload/platform/templates/specialists/agents/compiled-truth-rewriter.md +6 -0
  109. package/payload/platform/templates/specialists/agents/content-producer.md +6 -0
  110. package/payload/platform/templates/specialists/agents/data-manager.md +6 -0
  111. package/payload/platform/templates/specialists/agents/database-operator.md +6 -0
  112. package/payload/platform/templates/specialists/agents/librarian.md +6 -0
  113. package/payload/platform/templates/specialists/agents/personal-assistant.md +6 -0
  114. package/payload/platform/templates/specialists/agents/project-manager.md +6 -0
  115. package/payload/platform/templates/specialists/agents/public-session-reviewer.md +6 -0
  116. package/payload/platform/templates/specialists/agents/research-assistant.md +7 -1
  117. package/payload/platform/templates/specialists/agents/typed-edge-classifier.md +6 -0
  118. package/payload/premium-plugins/writer-craft/agents/writer-craft--manuscript-reviewer.md +6 -0
  119. package/payload/server/{chunk-REWXLAPC.js → chunk-KZFE3MOY.js} +52 -4
  120. package/payload/server/public/activity.html +5 -6
  121. package/payload/server/public/assets/AdminLoginScreens-DgVFsHtd.js +1 -0
  122. package/payload/server/public/assets/AdminShell-BwNqwb4k.js +2 -0
  123. package/payload/server/public/assets/activity-2DL2DwLr.js +1 -0
  124. package/payload/server/public/assets/admin-COe0dT2U.js +1 -0
  125. package/payload/server/public/assets/{arc-D-QzyndF.js → arc-CFMJyMm-.js} +1 -1
  126. package/payload/server/public/assets/architecture-YZFGNWBL-Cypt5qHL.js +1 -0
  127. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-W7f0Us4S.js → architectureDiagram-Q4EWVU46-CiSSgdL8.js} +1 -1
  128. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-CfPS4FXg.js → blockDiagram-DXYQGD6D-_sXtIW5c.js} +1 -1
  129. package/payload/server/public/assets/{bot-DnuyRsLS.js → bot-CpBuDQlF.js} +1 -1
  130. package/payload/server/public/assets/{browser-q4EFqKvR.js → browser-CSApuZrv.js} +1 -1
  131. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-Dq60cLVI.js → c4Diagram-AHTNJAMY-Bh7OAMXs.js} +1 -1
  132. package/payload/server/public/assets/{calendar-C-q9kWmA.js → calendar-DlvwZnS7.js} +1 -1
  133. package/payload/server/public/assets/channel-BMyXE4zc.js +1 -0
  134. package/payload/server/public/assets/chat-DWvYv9jq.js +1 -0
  135. package/payload/server/public/assets/chevron-down-Dnuuw6Q2.js +1 -0
  136. package/payload/server/public/assets/chevron-left-CC4zmWOr.js +1 -0
  137. package/payload/server/public/assets/chevron-right-CG_lHTx_.js +1 -0
  138. package/payload/server/public/assets/{chunk-2KRD3SAO-CXDjuDN0.js → chunk-2KRD3SAO-D4fktAwW.js} +1 -1
  139. package/payload/server/public/assets/{chunk-336JU56O-926ox0tj.js → chunk-336JU56O-CcQoKH2G.js} +2 -2
  140. package/payload/server/public/assets/chunk-426QAEUC-Xu6JKP4d.js +1 -0
  141. package/payload/server/public/assets/{chunk-4BX2VUAB-C2nrPCfN.js → chunk-4BX2VUAB-C13Wz3zS.js} +1 -1
  142. package/payload/server/public/assets/{chunk-4TB4RGXK-CuW3FOR6.js → chunk-4TB4RGXK-BL48D7gN.js} +1 -1
  143. package/payload/server/public/assets/{chunk-55IACEB6-B4_-In90.js → chunk-55IACEB6-TmOKdy8a.js} +1 -1
  144. package/payload/server/public/assets/{chunk-5FUZZQ4R-DnYGr9CP.js → chunk-5FUZZQ4R-DZ_jSF6N.js} +1 -1
  145. package/payload/server/public/assets/{chunk-5PVQY5BW-BYCOi9bf.js → chunk-5PVQY5BW-De4G1vfV.js} +1 -1
  146. package/payload/server/public/assets/{chunk-67CJDMHE-CBxsC4JP.js → chunk-67CJDMHE-Cif9Uu5C.js} +1 -1
  147. package/payload/server/public/assets/{chunk-7N4EOEYR-CAACaqFr.js → chunk-7N4EOEYR-WjTHCXnd.js} +1 -1
  148. package/payload/server/public/assets/{chunk-AA7GKIK3-BoamZdS6.js → chunk-AA7GKIK3-CwCfdQmb.js} +1 -1
  149. package/payload/server/public/assets/{chunk-BSJP7CBP-CHk82eFy.js → chunk-BSJP7CBP-9A67yXwT.js} +1 -1
  150. package/payload/server/public/assets/{chunk-CIAEETIT-DdeW9zxl.js → chunk-CIAEETIT-DPMiWSBi.js} +1 -1
  151. package/payload/server/public/assets/{chunk-EDXVE4YY-DJqSRv2w.js → chunk-EDXVE4YY-C2f_Qvx-.js} +1 -1
  152. package/payload/server/public/assets/{chunk-ENJZ2VHE-BRi4BwM5.js → chunk-ENJZ2VHE-B-Q9EbSQ.js} +1 -1
  153. package/payload/server/public/assets/{chunk-FMBD7UC4-OJp2CtUE.js → chunk-FMBD7UC4-Bmt_ZRlV.js} +1 -1
  154. package/payload/server/public/assets/{chunk-FOC6F5B3-BUAQ6spG.js → chunk-FOC6F5B3-DfGJGxpX.js} +1 -1
  155. package/payload/server/public/assets/{chunk-ICPOFSXX-DeRggD-Q.js → chunk-ICPOFSXX-Wyym1Q8K.js} +2 -2
  156. package/payload/server/public/assets/{chunk-K5T4RW27-EHiXZ8Py.js → chunk-K5T4RW27-DMpoaqw2.js} +1 -1
  157. package/payload/server/public/assets/{chunk-KGLVRYIC-tOajPcpn.js → chunk-KGLVRYIC-DYUFgri2.js} +1 -1
  158. package/payload/server/public/assets/{chunk-LIHQZDEY-uwO6oHIU.js → chunk-LIHQZDEY-x3LmeKmx.js} +1 -1
  159. package/payload/server/public/assets/{chunk-ORNJ4GCN-Dpa1_BCA.js → chunk-ORNJ4GCN-BeuNAWkW.js} +1 -1
  160. package/payload/server/public/assets/{chunk-OYMX7WX6-ChdDqaQL.js → chunk-OYMX7WX6-BUWVAfQA.js} +1 -1
  161. package/payload/server/public/assets/chunk-QZHKN3VN-7_kS3RBy.js +1 -0
  162. package/payload/server/public/assets/{chunk-U2HBQHQK-5JgWosPv.js → chunk-U2HBQHQK-DKS5adQo.js} +1 -1
  163. package/payload/server/public/assets/{chunk-X2U36JSP-BGJqysB2.js → chunk-X2U36JSP-CT_UM4IN.js} +1 -1
  164. package/payload/server/public/assets/{chunk-XPW4576I-GERE2ekx.js → chunk-XPW4576I-ByFnN4JO.js} +1 -1
  165. package/payload/server/public/assets/{chunk-YZCP3GAM-BskqX1V5.js → chunk-YZCP3GAM-DCetC20J.js} +1 -1
  166. package/payload/server/public/assets/{chunk-ZZ45TVLE-DG1iY-L9.js → chunk-ZZ45TVLE-Bh1QIv8N.js} +1 -1
  167. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DJqbNcuC.js +1 -0
  168. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-CGglqfQA.js +1 -0
  169. package/payload/server/public/assets/clone-BiTMTzp_.js +1 -0
  170. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-C8Rl4ykh.js → cose-bilkent-S5V4N54A-DgLEaApT.js} +1 -1
  171. package/payload/server/public/assets/{dagre-KV5264BT-C0YrJPF3.js → dagre-KV5264BT-BfkUPrEV.js} +1 -1
  172. package/payload/server/public/assets/{dagre-DTlv-FFy.js → dagre-unhMIyVo.js} +1 -1
  173. package/payload/server/public/assets/data-CJbC_cVy.js +1 -0
  174. package/payload/server/public/assets/{diagram-5BDNPKRD-Dfk5oepI.js → diagram-5BDNPKRD-BjjwcTD9.js} +1 -1
  175. package/payload/server/public/assets/{diagram-G4DWMVQ6-QRj55DiB.js → diagram-G4DWMVQ6-BbgWtVSS.js} +1 -1
  176. package/payload/server/public/assets/{diagram-MMDJMWI5-BAPKsohU.js → diagram-MMDJMWI5-DyAnkWGy.js} +1 -1
  177. package/payload/server/public/assets/{diagram-TYMM5635-C3iZXPkC.js → diagram-TYMM5635-SAMMoLxq.js} +1 -1
  178. package/payload/server/public/assets/{erDiagram-SMLLAGMA-DIbI7g_5.js → erDiagram-SMLLAGMA-Cvo-5-fD.js} +1 -1
  179. package/payload/server/public/assets/{flatten-DlAJeN0L.js → flatten-BXzaHgtL.js} +1 -1
  180. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-l3eJJr8R.js → flowDiagram-DWJPFMVM-y3o2Ycld.js} +1 -1
  181. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-aaEzHxIq.js → ganttDiagram-T4ZO3ILL-BEm_CQaK.js} +1 -1
  182. package/payload/server/public/assets/gitGraph-7Q5UKJZL-C_-XjxLd.js +1 -0
  183. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-Di-1eg_m.js → gitGraphDiagram-UUTBAWPF-CqmOxS7F.js} +1 -1
  184. package/payload/server/public/assets/{graph-CNgt7ygi.js → graph-kaxS9a_7.js} +2 -2
  185. package/payload/server/public/assets/graph-labels-GgejRtgV.js +1 -0
  186. package/payload/server/public/assets/{graphlib-D5awC2yp.js → graphlib-_HfbQcBf.js} +1 -1
  187. package/payload/server/public/assets/info-OMHHGYJF-BH8yDCfL.js +1 -0
  188. package/payload/server/public/assets/infoDiagram-42DDH7IO-DyDXyJ6U.js +2 -0
  189. package/payload/server/public/assets/{isEmpty-Dh1dF-tM.js → isEmpty-uB1dEu_Z.js} +1 -1
  190. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-CLOd_cNf.js → ishikawaDiagram-UXIWVN3A-C_d0vcZC.js} +1 -1
  191. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-cGyfW6Tg.js → journeyDiagram-VCZTEJTY-DsjRBqlW.js} +1 -1
  192. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-CRTKWPlh.js → kanban-definition-6JOO6SKY-BwtK6LS-.js} +1 -1
  193. package/payload/server/public/assets/{line-B8Brm0TD.js → line-szBCFOLJ.js} +1 -1
  194. package/payload/server/public/assets/{linear-BHnSKXBe.js → linear-X3O7lwK3.js} +1 -1
  195. package/payload/server/public/assets/{maximize-2-k3Fu5hdP.js → maximize-2-CPq3nwZA.js} +1 -1
  196. package/payload/server/public/assets/{mermaid-parser.core-BtNpdQct.js → mermaid-parser.core-BavvMAhU.js} +2 -2
  197. package/payload/server/public/assets/{mermaid.core-BY87anQZ.js → mermaid.core-Br2F9Hdm.js} +3 -3
  198. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-rUSv9qN1.js → mindmap-definition-QFDTVHPH-4qlwXoJ-.js} +1 -1
  199. package/payload/server/public/assets/operator-CldXXilj.js +1 -0
  200. package/payload/server/public/assets/{ordinal-Bnl5GL-x.js → ordinal-CZY9laxg.js} +1 -1
  201. package/payload/server/public/assets/packet-4T2RLAQJ-Bajcns4r.js +1 -0
  202. package/payload/server/public/assets/{page-Bvx0A2jB.js → page-BE6DkJP9.js} +1 -1
  203. package/payload/server/public/assets/{page-DhwPG5Zf.js → page-BIwdWXnv.js} +3 -3
  204. package/payload/server/public/assets/{pdf-render-CrpAi4Ba.js → pdf-render-H884L3EI.js} +1 -1
  205. package/payload/server/public/assets/pie-ZZUOXDRM-D7hjM5w1.js +1 -0
  206. package/payload/server/public/assets/{pieDiagram-DEJITSTG-Cjn9IP-C.js → pieDiagram-DEJITSTG-BbXryIg0.js} +1 -1
  207. package/payload/server/public/assets/{public-BJ5Oo2Fw.js → public-BKQEQGPE.js} +1 -1
  208. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-BkSHkSsn.js → quadrantDiagram-34T5L4WZ-BHLjcvh3.js} +1 -1
  209. package/payload/server/public/assets/radar-PYXPWWZC-B46d5x7a.js +1 -0
  210. package/payload/server/public/assets/{reduce-Bp5oNw1n.js → reduce-BGQzAenn.js} +1 -1
  211. package/payload/server/public/assets/{requirementDiagram-MS252O5E-hLwgyB-F.js → requirementDiagram-MS252O5E-BXaVO0lk.js} +1 -1
  212. package/payload/server/public/assets/{rotate-ccw-D-ov5jM4.js → rotate-ccw-DieZzl10.js} +1 -1
  213. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-D6Dl1RIa.js → sankeyDiagram-XADWPNL6-CmN1s17Q.js} +1 -1
  214. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-j0QBqkbL.js → sequenceDiagram-FGHM5R23-DC08e5NY.js} +1 -1
  215. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-TvEBNsuV.js → stateDiagram-FHFEXIEX-DnoQMuOW.js} +1 -1
  216. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CrstH34p.js +1 -0
  217. package/payload/server/public/assets/{tasks-DHMxTZVD.js → tasks-DYyCGz8W.js} +1 -1
  218. package/payload/server/public/assets/{time-entry-format-EmV5lz6o.js → time-entry-format-CC_9jFuR.js} +1 -1
  219. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BfivOP-j.js → timeline-definition-GMOUNBTQ-eTn3uRaH.js} +1 -1
  220. package/payload/server/public/assets/treeView-SZITEDCU-3XQh5xQ1.js +1 -0
  221. package/payload/server/public/assets/treemap-W4RFUUIX-DwR-_l5z.js +1 -0
  222. package/payload/server/public/assets/{triangle-alert-CivyPfAL.js → triangle-alert-7mQfCQWP.js} +1 -1
  223. package/payload/server/public/assets/{useCopyFeedback-hbnPS-r9.js → useCopyFeedback-os7N0yCx.js} +1 -1
  224. package/payload/server/public/assets/useSubAccountSwitcher-Bgfqig1V.css +1 -0
  225. package/payload/server/public/assets/{useVoiceRecorder-QN9_GqB_.js → useVoiceRecorder-ZFwt5i6d.js} +2 -2
  226. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-Bvfw9aFJ.js → vennDiagram-DHZGUBPP-DNx6o_ZQ.js} +1 -1
  227. package/payload/server/public/assets/wardley-RL74JXVD-DZp9oCGv.js +1 -0
  228. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-C9h04G6b.js → wardleyDiagram-NUSXRM2D-BZOchqF3.js} +1 -1
  229. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-BMvi2m2q.js → xychartDiagram-5P7HB3ND-ZBEGH6ZI.js} +1 -1
  230. package/payload/server/public/browser.html +4 -4
  231. package/payload/server/public/calendar.html +6 -6
  232. package/payload/server/public/chat.html +14 -14
  233. package/payload/server/public/data.html +11 -11
  234. package/payload/server/public/graph.html +10 -11
  235. package/payload/server/public/index.html +16 -17
  236. package/payload/server/public/operator.html +15 -16
  237. package/payload/server/public/public.html +14 -14
  238. package/payload/server/public/tasks.html +6 -6
  239. package/payload/server/server.js +1836 -998
  240. package/payload/server/{src-JJ6PHO5I.js → src-UYDFMWF4.js} +1 -1
  241. package/payload/server/public/assets/AdminLoginScreens-Dic_fPt6.js +0 -1
  242. package/payload/server/public/assets/AdminShell-JN7rZ8aG.js +0 -2
  243. package/payload/server/public/assets/Checkbox-CB74kINA.js +0 -1
  244. package/payload/server/public/assets/activity-DbfpHg3i.js +0 -1
  245. package/payload/server/public/assets/admin-CThbXaYH.js +0 -1
  246. package/payload/server/public/assets/architecture-YZFGNWBL-CGvD4r9F.js +0 -1
  247. package/payload/server/public/assets/channel-Bpg-ADC0.js +0 -1
  248. package/payload/server/public/assets/chat-DcefIV9q.js +0 -1
  249. package/payload/server/public/assets/chevron-down-Blma_CKl.js +0 -1
  250. package/payload/server/public/assets/chevron-left-xeYDxFiC.js +0 -1
  251. package/payload/server/public/assets/chevron-right-BonbtjqJ.js +0 -1
  252. package/payload/server/public/assets/chunk-426QAEUC-1poG7Nd3.js +0 -1
  253. package/payload/server/public/assets/chunk-QZHKN3VN-GN1tIO8J.js +0 -1
  254. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DMGVq8Jv.js +0 -1
  255. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DfddT8UV.js +0 -1
  256. package/payload/server/public/assets/clone-Cc9vFIEk.js +0 -1
  257. package/payload/server/public/assets/data-D5dp6nj6.js +0 -1
  258. package/payload/server/public/assets/gitGraph-7Q5UKJZL-DtjXE0UI.js +0 -1
  259. package/payload/server/public/assets/graph-labels-DA48HrRR.js +0 -1
  260. package/payload/server/public/assets/info-OMHHGYJF-CmArhGYf.js +0 -1
  261. package/payload/server/public/assets/infoDiagram-42DDH7IO-utWz7oZy.js +0 -2
  262. package/payload/server/public/assets/operator-8NVpNLeD.js +0 -1
  263. package/payload/server/public/assets/packet-4T2RLAQJ-BTfK0Zw4.js +0 -1
  264. package/payload/server/public/assets/pie-ZZUOXDRM-BvtPeMQY.js +0 -1
  265. package/payload/server/public/assets/radar-PYXPWWZC-D2UXvQgn.js +0 -1
  266. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-Ur4eM6rb.js +0 -1
  267. package/payload/server/public/assets/treeView-SZITEDCU-0dE7FvM2.js +0 -1
  268. package/payload/server/public/assets/treemap-W4RFUUIX-BrwlXpr5.js +0 -1
  269. package/payload/server/public/assets/useSubAccountSwitcher-kwhG-aJj.css +0 -1
  270. package/payload/server/public/assets/wardley-RL74JXVD-CO9HdfJ-.js +0 -1
  271. /package/payload/server/public/assets/{_baseFor-BBkmqPEk.js → _baseFor-CMWvz0uA.js} +0 -0
  272. /package/payload/server/public/assets/{array-Bjtb2C_5.js → array-Cny_NT6T.js} +0 -0
  273. /package/payload/server/public/assets/{cytoscape.esm-B1dQ664k.js → cytoscape.esm-De0CCmB2.js} +0 -0
  274. /package/payload/server/public/assets/{defaultLocale-iwYA7ne8.js → defaultLocale-BahRht8U.js} +0 -0
  275. /package/payload/server/public/assets/{dist-CLmrZbY7.js → dist-C9D_xz81.js} +0 -0
  276. /package/payload/server/public/assets/{init-CxnBAgcf.js → init-DoIvjoJ_.js} +0 -0
  277. /package/payload/server/public/assets/{katex-DojHoLIr.js → katex-Bfn1OZEl.js} +0 -0
  278. /package/payload/server/public/assets/{path-JZiojYzQ.js → path-CTezU8L5.js} +0 -0
  279. /package/payload/server/public/assets/{preload-helper-CT1Z6Pdu.js → preload-helper-D_sPnjeL.js} +0 -0
  280. /package/payload/server/public/assets/{rough.esm-D5NinLFK.js → rough.esm-BJo4pvXu.js} +0 -0
  281. /package/payload/server/public/assets/{src-DQ0MSD6q.js → src-BTKk8KAG.js} +0 -0
  282. /package/payload/server/public/assets/{useSubAccountSwitcher-DotTrvX3.js → useSubAccountSwitcher-Q941cZYV.js} +0 -0
@@ -20,6 +20,7 @@
20
20
  var crumbsEl = document.getElementById('dp-crumbs');
21
21
  var entriesEl = document.getElementById('dp-entries');
22
22
  var upEl = document.getElementById('dp-up');
23
+ var refreshEl = document.getElementById('dp-refresh');
23
24
  var uploadOpenEl = document.getElementById('dp-upload-open');
24
25
  var modalEl = document.getElementById('dp-modal');
25
26
  var modalCloseEl = document.getElementById('dp-modal-close');
@@ -106,11 +107,23 @@
106
107
 
107
108
  var a = document.createElement('a');
108
109
  a.className = 'dp-file-name';
109
- a.href = API + '/api/download?key=' + encodeURIComponent(f.objectKey);
110
+ var href = API + '/api/download?key=' + encodeURIComponent(f.objectKey);
111
+ a.href = href;
110
112
  // Full name on hover, since the middle of a long name is clipped away.
111
113
  a.title = f.filename;
112
114
  var p = nameParts(f.filename);
113
115
  a.appendChild(nameNode(p.main, p.ext));
116
+ // Drag out to the file system, the same affordance the device-published
117
+ // rows already have. An <a> is draggable by default but hands over its
118
+ // href as a link; DownloadURL is what makes the OS write real bytes.
119
+ a.addEventListener('dragstart', function (ev) {
120
+ var abs = new URL(href, location.href).href;
121
+ ev.dataTransfer.setData(
122
+ 'DownloadURL',
123
+ 'application/octet-stream:' + f.filename + ':' + abs
124
+ );
125
+ ev.dataTransfer.setData('text/uri-list', abs);
126
+ });
114
127
  li.appendChild(a);
115
128
 
116
129
  var del = document.createElement('button');
@@ -221,6 +234,28 @@
221
234
  openShared(e.relPath);
222
235
  }
223
236
  });
237
+ if (e.kind === 'directory') {
238
+ /* Drop a file straight onto the folder it belongs in. This is the
239
+ interaction the whole two-way exchange exists for: no modal, no
240
+ picker, no guessing where it went. The row highlights so the target
241
+ is unambiguous while the pointer is over it. */
242
+ li.addEventListener('dragover', function (ev) {
243
+ ev.preventDefault();
244
+ li.classList.add('dp-drop-target');
245
+ });
246
+ li.addEventListener('dragleave', function (ev) {
247
+ if (!li.contains(ev.relatedTarget)) { li.classList.remove('dp-drop-target'); }
248
+ });
249
+ li.addEventListener('drop', function (ev) {
250
+ ev.preventDefault();
251
+ // Marked so the listing's own handler does not ALSO upload it into
252
+ // the folder that is merely open — one drop is one upload.
253
+ ev.dpHandled = true;
254
+ li.classList.remove('dp-drop-target');
255
+ var dt = ev.dataTransfer;
256
+ if (dt && dt.files && dt.files.length) { uploadMany(dt.files, e.relPath); }
257
+ });
258
+ }
224
259
  if (e.kind !== 'directory') {
225
260
  b.draggable = true;
226
261
  b.addEventListener('dragstart', function (ev) {
@@ -269,13 +304,22 @@
269
304
  });
270
305
  }
271
306
 
307
+ /* Resolves true when the list was re-rendered, false when it was not. The
308
+ boolean exists for the refresh control: this function catches its own
309
+ failure, so the promise resolves either way, and a caller that cleared the
310
+ status on resolve would erase the message written a moment earlier. The
311
+ `!d.ok` branch used to return silently, which left a failed re-list
312
+ indistinguishable from an unchanged one. */
272
313
  function refresh() {
273
314
  return api('/api/files?prefix=' + encodeURIComponent(prefix))
274
315
  .then(function (r) {
275
316
  return r.json();
276
317
  })
277
318
  .then(function (d) {
278
- if (!d.ok) return;
319
+ if (!d.ok) {
320
+ say('Could not load your files.');
321
+ return false;
322
+ }
279
323
  render(d.files || []);
280
324
  var entries = d.entries || [];
281
325
  // Hidden at the root only: once inside a folder the section stays put,
@@ -283,9 +327,11 @@
283
327
  // vanishing.
284
328
  sharedEl.hidden = prefix === '' && entries.length === 0;
285
329
  renderEntries(entries);
330
+ return true;
286
331
  })
287
332
  .catch(function () {
288
333
  say('Could not load your files.');
334
+ return false;
289
335
  });
290
336
  }
291
337
 
@@ -300,8 +346,15 @@
300
346
  return r.json();
301
347
  })
302
348
  .then(function (d) {
303
- say(d.ok ? 'Deleted.' : 'Could not delete that.');
304
- return refresh();
349
+ var done = d.ok ? 'Deleted.' : 'Could not delete that.';
350
+ say(done);
351
+ // refresh() writes its own failure text, which would otherwise bury what
352
+ // just happened to the file. Compose the two, so a list that did not
353
+ // reload is reported without the client losing the delete's outcome and
354
+ // reading a load error as though the delete itself had failed.
355
+ return refresh().then(function (ok) {
356
+ if (!ok) { say(done + ' The list could not be reloaded.'); }
357
+ });
305
358
  })
306
359
  .catch(function () {
307
360
  say('Could not delete that.');
@@ -370,12 +423,35 @@
370
423
  refresh();
371
424
  });
372
425
 
426
+ /* Re-read the index in the folder that is already open. refresh() reads the
427
+ current prefix, so this does not reset to the root. The status clears only
428
+ on a reported success, otherwise refresh()'s own error text stays up.
429
+ refresh() returns false from its catch rather than rejecting, so the button
430
+ cannot be left stuck disabled and needs no catch here. `disabled` in flight
431
+ is what stops a double click issuing two reads. */
432
+ refreshEl.addEventListener('click', function () {
433
+ refreshEl.disabled = true;
434
+ say('Refreshing…');
435
+ refresh().then(function (ok) {
436
+ refreshEl.disabled = false;
437
+ if (ok) { say(''); }
438
+ });
439
+ });
440
+
373
441
  /* The upload modal. Hidden via the `hidden` attribute so
374
442
  [hidden]{display:none!important} stays authoritative over .dp-modal's
375
443
  display:flex. It dismisses three ways: the close button, a click on the
376
444
  backdrop (never the panel), and Escape while open. */
377
445
  function openModal() {
378
446
  modalEl.hidden = false;
447
+ // Name the destination rather than leaving it implicit. An upload whose
448
+ // folder the client has to infer is the defect this whole task removes.
449
+ var t = document.getElementById('dp-upload-target');
450
+ if (t) {
451
+ t.textContent = prefix
452
+ ? 'Uploading into ' + prefix
453
+ : 'Uploading into your own files. Open a folder first to send it there.';
454
+ }
379
455
  document.getElementById('dp-file').focus();
380
456
  }
381
457
  function closeModal() {
@@ -397,29 +473,151 @@
397
473
  not), so the picker is cleared only when a retry with the same selection is
398
474
  pointless; `ok` is true only on a kept upload, which is what closes the
399
475
  modal. A network error is { responded:false, ok:false }. */
400
- function uploadFile(file) {
476
+ /* One part is 25 MiB, matching the server's per-part gate. A part is read
477
+ wholly into the Function's memory, so this is the isolate's bound, not a
478
+ preference. The whole-file cap is 1 GiB. */
479
+ var PART_BYTES = 25 * 1024 * 1024;
480
+ var MAX_TOTAL_BYTES = 1024 * 1024 * 1024;
481
+
482
+ /* Every failure used to read 'Upload failed.', which tells a client nothing
483
+ about whether to retry, sign in again, or pick a smaller file. */
484
+ function uploadFailureMessage(status) {
485
+ if (status === 401) return 'Your session expired. Sign in again.';
486
+ if (status === 403) return 'You cannot upload to that folder.';
487
+ if (status === 413) return 'That file is too large.';
488
+ if (status === 400) return 'That file could not be read.';
489
+ return 'Upload failed.';
490
+ }
491
+
492
+ function showProgress(fraction) {
493
+ var wrap = document.getElementById('dp-progress');
494
+ var bar = document.getElementById('dp-progress-bar');
495
+ if (!wrap || !bar) return;
496
+ if (fraction === null) { wrap.hidden = true; return; }
497
+ wrap.hidden = false;
498
+ bar.style.width = Math.round(fraction * 100) + '%';
499
+ }
500
+
501
+ /* Slice a large file across a multipart session. Three stages: open, send
502
+ each part, assemble. Anything at or below one part takes the single-request
503
+ path below, so the common case stays one round trip. */
504
+ function uploadLarge(file, target) {
505
+ var created;
401
506
  var fd = new FormData();
402
- fd.append('file', file);
403
- say('Uploading…');
507
+ fd.append('action', 'create');
508
+ fd.append('filename', file.name);
509
+ fd.append('targetPath', target);
510
+ fd.append('size', String(file.size));
404
511
  return api('/api/upload', { method: 'POST', body: fd })
405
- .then(function (r) {
406
- return r.json();
407
- })
408
- .then(function (d) {
409
- say(d.ok ? 'Uploaded.' : 'Upload failed.');
410
- return refresh().then(function () { return { responded: true, ok: !!d.ok }; });
512
+ .then(function (r) { return r.json().then(function (d) { return { s: r.status, d: d }; }); })
513
+ .then(function (res) {
514
+ if (!res.d.ok) { throw { status: res.s }; }
515
+ created = res.d;
516
+ var parts = [];
517
+ var total = Math.ceil(file.size / PART_BYTES);
518
+ var chain = Promise.resolve();
519
+ var _loop = function (i) {
520
+ chain = chain.then(function () {
521
+ var slice = file.slice(i * PART_BYTES, Math.min((i + 1) * PART_BYTES, file.size));
522
+ var pf = new FormData();
523
+ pf.append('action', 'part');
524
+ pf.append('key', created.key);
525
+ pf.append('r2UploadId', created.r2UploadId);
526
+ pf.append('partNumber', String(i + 1));
527
+ pf.append('part', slice);
528
+ say('Uploading ' + file.name + ' (' + (i + 1) + ' of ' + total + ')…');
529
+ showProgress(i / total);
530
+ return api('/api/upload', { method: 'POST', body: pf })
531
+ .then(function (r) { return r.json().then(function (d) { return { s: r.status, d: d }; }); })
532
+ .then(function (pr) {
533
+ if (!pr.d.ok) { throw { status: pr.s }; }
534
+ parts.push({ partNumber: pr.d.partNumber, etag: pr.d.etag, size: slice.size });
535
+ });
536
+ });
537
+ };
538
+ for (var i = 0; i < total; i++) { _loop(i); }
539
+ return chain.then(function () {
540
+ var cf = new FormData();
541
+ cf.append('action', 'complete');
542
+ cf.append('key', created.key);
543
+ cf.append('r2UploadId', created.r2UploadId);
544
+ cf.append('targetPath', target);
545
+ cf.append('parts', JSON.stringify(parts));
546
+ showProgress(1);
547
+ return api('/api/upload', { method: 'POST', body: cf })
548
+ .then(function (r) { return r.json().then(function (d) { return { s: r.status, d: d }; }); });
549
+ });
550
+ });
551
+ }
552
+
553
+ /* One upload path for Browse-and-Upload, for a dropped file, and for a file
554
+ dropped onto a folder row. `target` is the folder it should land in; '' is
555
+ the client's own uploads area. Resolves { responded, ok }. */
556
+ function uploadFile(file, target) {
557
+ target = target || '';
558
+ // Checked here as well as on the server: the browser should not spend
559
+ // minutes sending a gigabyte only to be told at the end.
560
+ if (file.size > MAX_TOTAL_BYTES) {
561
+ say(file.name + ' is larger than the 1 GB limit.');
562
+ return Promise.resolve({ responded: true, ok: false });
563
+ }
564
+
565
+ say('Uploading ' + file.name + '…');
566
+ var run;
567
+ if (file.size > PART_BYTES) {
568
+ run = uploadLarge(file, target);
569
+ } else {
570
+ var fd = new FormData();
571
+ fd.append('file', file);
572
+ fd.append('targetPath', target);
573
+ run = api('/api/upload', { method: 'POST', body: fd })
574
+ .then(function (r) { return r.json().then(function (d) { return { s: r.status, d: d }; }); });
575
+ }
576
+
577
+ return run
578
+ .then(function (res) {
579
+ showProgress(null);
580
+ var done = res.d.ok ? 'Uploaded ' + file.name + '.' : uploadFailureMessage(res.s);
581
+ say(done);
582
+ // Same composition as remove(): a list that did not reload must not cost
583
+ // the client the upload's own outcome.
584
+ return refresh().then(function (ok) {
585
+ if (!ok) { say(done + ' The list could not be reloaded.'); }
586
+ return { responded: true, ok: !!res.d.ok };
587
+ });
411
588
  })
412
- .catch(function () {
413
- say('Upload failed.');
414
- return { responded: false, ok: false };
589
+ .catch(function (err) {
590
+ showProgress(null);
591
+ // A thrown {status} came from a multipart stage; anything else is the
592
+ // network. Both are distinguishable to the client, which is the point.
593
+ say(err && err.status ? uploadFailureMessage(err.status) : 'Could not reach the server.');
594
+ return { responded: !!(err && err.status), ok: false };
595
+ });
596
+ }
597
+
598
+ /* Files in sequence, not in parallel: the status line reports one at a time,
599
+ and a portal on a free plan should not open ten concurrent uploads. */
600
+ function uploadMany(files, target) {
601
+ var list = Array.prototype.slice.call(files);
602
+ var anyOk = false;
603
+ return list.reduce(function (chain, f) {
604
+ return chain.then(function () {
605
+ return uploadFile(f, target).then(function (r) {
606
+ if (r.ok) { anyOk = true; }
607
+ });
415
608
  });
609
+ }, Promise.resolve()).then(function () {
610
+ return { responded: true, ok: anyOk };
611
+ });
416
612
  }
417
613
 
418
614
  uploadForm.addEventListener('submit', function (e) {
419
615
  e.preventDefault();
420
616
  var input = document.getElementById('dp-file');
421
- if (!input.files || !input.files[0]) return;
422
- uploadFile(input.files[0]).then(function (res) {
617
+ if (!input.files || !input.files.length) return;
618
+ // The modal uploads into the folder currently open, which is what the
619
+ // target line under the form says. No hidden default.
620
+ uploadMany(input.files, prefix).then(function (res) {
423
621
  if (res.responded) { input.value = ''; }
424
622
  if (res.ok) { closeModal(); }
425
623
  });
@@ -440,11 +638,50 @@
440
638
  e.preventDefault();
441
639
  uploadForm.classList.remove('dp-dropping');
442
640
  var dt = e.dataTransfer;
443
- if (dt && dt.files && dt.files[0]) {
444
- // The drop target now lives inside the modal, so a kept drop closes it too.
445
- uploadFile(dt.files[0]).then(function (res) {
641
+ if (dt && dt.files && dt.files.length) {
642
+ uploadMany(dt.files, prefix).then(function (res) {
446
643
  if (res.ok) { closeModal(); }
447
644
  });
448
645
  }
449
646
  });
647
+
648
+ /* Drop straight onto the open listing, which targets the folder currently
649
+ open. The modal is no longer the only way in: the whole point of the
650
+ exchange is that a client drags a file to where it belongs. */
651
+ entriesEl.addEventListener('dragover', function (e) {
652
+ e.preventDefault();
653
+ entriesEl.classList.add('dp-dropping');
654
+ });
655
+ entriesEl.addEventListener('dragleave', function (e) {
656
+ if (!entriesEl.contains(e.relatedTarget)) {
657
+ entriesEl.classList.remove('dp-dropping');
658
+ }
659
+ });
660
+ entriesEl.addEventListener('drop', function (e) {
661
+ // Only a drop that landed on the listing itself, not one that bubbled up
662
+ // from a folder row — that row has already handled it with its own target.
663
+ if (e.dpHandled) return;
664
+ e.preventDefault();
665
+ entriesEl.classList.remove('dp-dropping');
666
+ var dt = e.dataTransfer;
667
+ if (dt && dt.files && dt.files.length) { uploadMany(dt.files, prefix); }
668
+ });
669
+
670
+ /* Re-read on a timer while the tab is visible.
671
+
672
+ Polling was rejected in Tasks 1885 and 1900 on the explicit reasoning that
673
+ "a poll against an hourly tree is waste". That premise died with the hourly
674
+ push: the device now publishes every sixty seconds, so a poll at the same
675
+ cadence is what makes a device-side change appear without the client
676
+ clicking anything.
677
+
678
+ Gated on visibility so a forgotten background tab costs nothing, and
679
+ skipped while a read is already in flight so a slow response cannot stack
680
+ requests on a portal running on a free plan. */
681
+ var polling = false;
682
+ setInterval(function () {
683
+ if (document.hidden || panelEl.hidden || polling) return;
684
+ polling = true;
685
+ refresh().then(function () { polling = false; });
686
+ }, 60000);
450
687
  })();
@@ -49,10 +49,14 @@ This usually completes within a couple of minutes of the booking.
49
49
  Bookings are reconciled on a cycle and the system audits itself each run. In the device logs:
50
50
 
51
51
  - `[calendar-booking]` lines trace one booking through submit, write, notify, and done.
52
- - `[calendar-reconcile]` lines report three health numbers per account: `eligibleExamined` (bookings the check looked at), `pendingUnreconciled` (bookings captured but not yet turned into meetings) and `orphanMeetings` (booking meetings with no matching saved booking). Entries that only block out a slot are not counted — they are not bookings and never become meetings.
52
+ - `[calendar-reconcile]` lines report four health numbers per account: `eligibleExamined` (bookings the check looked at), `pendingUnreconciled` (bookings captured but not yet turned into meetings), `pendingOldestAgeMin` (how many minutes the longest-waiting of those has been waiting) and `orphanMeetings` (booking meetings with no matching saved booking). Entries that only block out a slot are not counted — they are not bookings and never become meetings.
53
53
 
54
54
  A `pendingUnreconciled` number that stays above zero across several runs means those bookings are failing to turn into meetings, and the `attempt=` number on the matching `[calendar-booking]` lines says how many times each has been tried. A booking whose meeting is deleted is picked up again automatically on the next run. To read them: `journalctl --user -u <brand-service> | grep -E '\[calendar-booking\]|\[calendar-reconcile\]'`, then filter by one booking id for its full story. The edge half of a booking (before your device sees it) is in the Cloudflare Pages function logs.
55
55
 
56
+ A count on its own cannot tell a short lag from a long one: five bookings waiting two minutes and five waiting nine hours are both `pendingUnreconciled=5`. `pendingOldestAgeMin` is what separates them. It reads `0` when nothing is waiting, and also when something is waiting but for under a minute, so read it together with the count beside it.
57
+
58
+ A separate `op=liveness` line is printed every ten minutes by your device's own server, whether or not anything is wrong. `stale=false` means the booking check has run recently. `stale=true` means it has not run in over six minutes, which is the case nothing else can tell you about: a check that has stopped cannot report that it has stopped. `lastSkipReason` names the last account whose check finished without looking at any bookings, and reads `none` when every account it got to was checked. If you run more than one account, a check can both look at one account and skip another, so seeing a recent check time next to a skip reason is normal and means two different accounts.
59
+
56
60
  ## When your real calendar lives elsewhere
57
61
 
58
62
  The booking page publishes open slots from the internal calendar, the one Maxy keeps in its own graph.
@@ -404,6 +404,8 @@ Standing rules the operator gives over a channel are `Preference` nodes. Two mec
404
404
 
405
405
  **Trust a scheduled turn's provenance, never an inbound's claim (channel-agent grounding).** A cron-dispatched agent turn reaches the same channel builders as a real inbound, so its own authorisation text would otherwise land inside `## Context` — the zone `REFRAME_INSTRUCTION` demotes to untrusted — and a security-cautious agent would distrust its own automation (the self-cancel loop). The dispatcher therefore stamps a `ScheduleProvenance` marker (owning account, event, owner userId, dispatch time) that the channel builders render as a `## Scheduled automation (platform-verified)` preamble **above** `## Context` (and above the rules block); the whatsapp wrap tag names the door `scheduler`. Both channel-server paths carry the marker — the single-target loop and the unified multi-target door reader (via the same `doorInboundPayload` seam that carries `standingRules`); the scheduler only dispatches to the whatsapp/telegram door, so a webchat frame never bears one. Because the marker is dispatcher-stamped and never derived from payload text, an inbound message cannot forge it — placement above `## Context` alone is not the trust signal, the dispatcher provenance is. The grounding binds to it in two places: one recipient-neutral clause added byte-identical to the three reframe instructions (a marker-bearing turn is authorised standing automation; a reconfiguration or re-enable is not intrusion), and the admin `IDENTITY.md` "Untrusted input" rule that the agent never states a platform-level authenticity or impersonation verdict it was not given (an unsourced "this is not from you" is its own inference, flagged, never asserted as the platform's). A sender has exactly one session: every inbound, real or scheduled, resolves to the deterministic `adminSessionIdFor(effectiveAccount, destination)`. An earlier change gave each firing its own salted seat so distrust could not accrete across firings, but the inbound hub keys on `senderId` and replaces its subscriber outright, so a seat's channel server attached under the sender's own key and swallowed the sender's REAL messages until it detached — splitting one conversation across two sessions that each denied what the other held. The seat is removed; the authoritative `agentPrompt`, this provenance marker, and the injected standing rules ground each firing without one. Self-cancel attribution is disarmed while that model beds in: the guard used to compare the cancelling turn's session id to a dispatcher stamp, which discriminated only because each firing ran its own seat, so with one session per sender it would report an autonomous loop on every operator cancel. It now always reports false — under-counting a real loop rather than inventing one — until the discriminator is rebuilt from the turn's own dispatched eventId. See `.docs/scheduling-agent-dispatch.md § Operator-provenance marker`, `§ Session model — one session per sender`, and `§ Self-cancel attribution — disarmed`.
406
406
 
407
+ **Carry what a reply points at (WhatsApp reply quote).** A WhatsApp sender replying to an earlier message produces a quote that `app/lib/whatsapp/inbound/extract.ts` resolves to `{ id, text?, sender? }` and `manager.ts` assigns to the route payload's `replyContext`. That field had no reader: the `server/index.ts` gateway argument omitted it, `AdminInbound` and the hub frame had no such field, and the channel service's `composePayload` emitted the caption and media notes only, so a message saying "prepare an email confirmation for this booking" reached the agent with nothing to resolve "this booking" against. It now rides both WhatsApp admin doors. On the persistent door it travels `server/index.ts` -> `AdminInbound` -> the hub frame the SSE route stringifies -> the channel service's `InboundPayload`, where `composeReplyNote` renders it as a bracketed note at the top of the payload, inside `## Context` and above the caption and any woven media notes: the quoted body is sender-controlled and belongs in the reframed untrusted zone, never in the platform preamble. Both channel-server inbound paths carry it, through the same `doorInboundPayload` seam that carries `standingRules` and the schedule provenance marker: the single-target SSE loop parses the whole frame, and the unified multi-target door reader rebuilds the payload field by field, so a field that seam omits is dropped on multi-target sessions alone. That seam is guarded by a test which reads the `InboundPayload` declaration and the rebuild function rather than a fixture, because the pre-existing byte-identity guard compared one hardcoded payload and so stayed green for every field added after it was written. On the passive account-manager door there is no channel server and no `## Context` frame, so the one-shot's whole initial prompt is read at face value; `composePassivePrompt` therefore puts the BARE sentence above the requirement and never the quoted body. The body arrives from the sender's own client and is never checked against the message store, so quoting it in a platform-voiced bracket on the door with no untrusted zone would let a modified client put words the agent never said into a `work-create` filing. The empty-text drop gate still tests the requirement alone, so a quote never rescues an inbound carrying no requirement of its own. The quoted body is capped at `QUOTED_TEXT_CAP` (500 characters, `…` appended when cut) because it is unbounded sender-controlled input, and the cut lands on whole characters so a body whose 500th boundary falls inside a surrogate pair cannot emit a lone surrogate at a position the sender chose. Only the channel service renders a body, so the cap lives there alone; platform/ui's `app/lib/whatsapp/reply-quote.ts` holds just the bare sentence for the passive door, and both packages hardcode that sentence in their own tests so the two doors cannot drift apart on the wording (the packages never cross-import, the same arrangement `REFRAME_INSTRUCTION` and `MediaRef` use). Telegram and webchat extract no quote at all, so neither carries one. `payloadBytes` measures the whole inner payload, so a reply carrying a quote produces a visibly larger `[reframe] op=applied … payloadBytes=<n>` than the same text without one, and a bare-text-length `payloadBytes` on an inbound whose store row has a non-null `quotedId` is the signature of this defect returning. Because the field is optional at every hop, dropping it from either `server/index.ts` argument would be silent (tsc stays green, and no unit test executes that wiring file), so the `check-reply-context-threaded` build gate asserts both argument sites.
408
+
407
409
  **Observability.** `[preference-write] op=reconcile … mode=<reinforce|update|contradict|new> similarity=…` — a stream of `mode=new` for subjects that already exist is the duplicate-minting signature. `[preference-inject] op=inject channel=<wa|tg|web> injected=<N> owner=<8> source=<owner|no-owner|owner-error|fetch-error>` — injection selects only the account owner's admin preferences, so `source=owner injected=0` is a genuinely empty owner, `source=no-owner` is an account with no seeded owner (injects nothing, a missed owner seeding), and `injected=0 source=owner` on an account whose owner holds rules is the write-only regression re-appearing. `[preference-audit] op=drift accountId=… storedActive=… injectedActive=… duplicateClusters=…` — the standing reconciliation audit (claude-session-manager, 5-minute tick) flags any account whose store holds more active rows than injection would surface, the no-event detector for drift.
408
410
 
409
411
  ---
@@ -2,6 +2,11 @@ import { test, expect } from "vitest";
2
2
  import { readFileSync, readdirSync } from "node:fs";
3
3
  import { resolve, join } from "node:path";
4
4
  import { parseLabelsFromSchemaCypher } from "../../../../../../lib/graph-mcp/dist/schema-cypher-parser.js";
5
+ import { ADDITIONAL_BASE_LABELS } from "../../../../../../lib/graph-style/dist/index.js";
6
+ // Same package, so the markdown row splitter is imported rather than copied.
7
+ // The UI server carries its own copy only because it is a separate build in a
8
+ // separate process; inside this package a second copy would be pure drift.
9
+ import { splitPipeRow } from "../schema-loader.js";
5
10
  // Task 358 — schema-doc / schema-cypher drift guard.
6
11
  //
7
12
  // Every label declared in prose by an ontology doc under
@@ -15,56 +20,81 @@ import { parseLabelsFromSchemaCypher } from "../../../../../../lib/graph-mcp/dis
15
20
  // task, and Task 352 before it).
16
21
  const REFERENCES_DIR = resolve(import.meta.dirname, "../../../../references");
17
22
  const SCHEMA_CYPHER_PATH = resolve(import.meta.dirname, "../../../../../../neo4j/schema.cypher");
18
- // Labels named in prose that are deliberately NOT first-class graph
19
- // nodes they are sub-labels stacked on a base node by the writer, or
20
- // schema.org aliases that never reach the graph as their own label.
21
- // Adding to this set is an intentional exception, not a silent omission.
22
- const PROSE_ONLY_LABELS = new Set([
23
- // schema-estate-agent.md treats Applicant as an additional label on a
24
- // base :Person node; there is no standalone :Applicant.
25
- "Applicant",
26
- // schema-construction.md treats Engineer as an additional label on a base
27
- // :Person node (the field-engineer / CIS worker-pay model); there is no
28
- // standalone :Engineer, so it carries no constraint/index of its own.
29
- "Engineer",
30
- // schema-construction.md treats Supplier as an additional label on a base
31
- // :Organization node (the supplier-invoice model); there is no standalone
32
- // :Supplier, so it carries no constraint/index of its own.
33
- "Supplier",
34
- // Email is retired: Task 321 folded threads onto :KnowledgeDocument
35
- // {source:'email'}, Task 426 then moved them onto :ConversationArchive
36
- // {source:'email'}. The :Email label never reaches the graph; it is
37
- // referenced in schema-base.md only for historical orientation.
38
- "Email",
39
- ]);
40
- function backtickedLabels(text) {
23
+ // Labels declared in a node-type table that are deliberately NOT first-class
24
+ // graph nodes, so they carry no constraint or index of their own.
25
+ //
26
+ // The additional-label members come from the single shared declaration in
27
+ // graph-style (Task 1893) — the same set the /graph chip allowlist excludes, so
28
+ // the two cannot drift apart. `Email` is local and stays local: it is exempt
29
+ // because the label is retired (Task 321 folded threads onto
30
+ // :KnowledgeDocument {source:'email'}, Task 426 moved them to
31
+ // :ConversationArchive {source:'email'}), not because it stacks on a base node.
32
+ // Folding it into the shared set would make that set mean two different things.
33
+ const EXEMPT_LABELS = new Set([...ADDITIONAL_BASE_LABELS, "Email"]);
34
+ const stripBackticks = (s) => s.replace(/`/g, "").trim();
35
+ // Every label in the `Neo4j Label` column of any node-type table in the doc.
36
+ //
37
+ // This replaces the previous scan for the backticked `:Label` prose form
38
+ // (Task 1893). That form matched only labels an author happened to write with a
39
+ // colon, so whole verticals went unchecked; widening it to bare backticks was
40
+ // measured and rejected, because it also matches relationship types (CITES),
41
+ // status values (Paid, InProgress), compass directions (North), contract clause
42
+ // kinds (Severability) and the `Owner` column header — 99 hits, ~90 of them not
43
+ // labels. The table column is the structured declaration, so it carries no such
44
+ // noise, and it is the same source the /graph top-level derivation reads.
45
+ function tableDeclaredLabels(text) {
41
46
  const labels = new Set();
42
- // Match `:Label` inside backticks — the convention used by every
43
- // schema-*.md prose doc to name a Neo4j label.
44
- for (const match of text.matchAll(/`:([A-Z][A-Za-z0-9]*)`/g)) {
45
- labels.add(match[1]);
47
+ const lines = text.split(/\r?\n/);
48
+ for (let i = 0; i < lines.length - 1; i++) {
49
+ if (!lines[i].includes("|"))
50
+ continue;
51
+ const sep = lines[i + 1];
52
+ if (!/^\s*\|?[\s:|-]+\|?\s*$/.test(sep) || !sep.includes("-"))
53
+ continue;
54
+ const headers = splitPipeRow(lines[i]).map((h) => stripBackticks(h).toLowerCase());
55
+ const labelCol = headers.findIndex((h) => h === "neo4j label");
56
+ if (labelCol === -1)
57
+ continue;
58
+ for (let j = i + 2; j < lines.length; j++) {
59
+ const row = lines[j];
60
+ if (!row.trim() || !row.includes("|"))
61
+ break;
62
+ const label = stripBackticks(splitPipeRow(row)[labelCol] ?? "");
63
+ if (/^[A-Z][A-Za-z0-9]*$/.test(label))
64
+ labels.add(label);
65
+ }
46
66
  }
47
67
  return labels;
48
68
  }
49
- test("schema-*.md prose labels all appear in schema.cypher", () => {
69
+ test("schema-*.md node-type table labels all appear in schema.cypher", () => {
50
70
  const declared = new Set(parseLabelsFromSchemaCypher(readFileSync(SCHEMA_CYPHER_PATH, "utf-8")));
51
71
  const files = readdirSync(REFERENCES_DIR).filter((name) => name.startsWith("schema-") && name.endsWith(".md"));
52
72
  expect(files.length).toBeGreaterThan(0);
53
73
  const missing = [];
54
74
  for (const file of files) {
55
75
  const text = readFileSync(join(REFERENCES_DIR, file), "utf-8");
56
- for (const label of backtickedLabels(text)) {
57
- if (PROSE_ONLY_LABELS.has(label))
76
+ for (const label of tableDeclaredLabels(text)) {
77
+ if (EXEMPT_LABELS.has(label))
58
78
  continue;
59
79
  if (!declared.has(label)) {
60
80
  missing.push({ label, file });
61
81
  }
62
82
  }
63
83
  }
64
- expect(missing, `Labels named in prose but missing from schema.cypher (add a CREATE CONSTRAINT or CREATE INDEX FOR (x:Label) line, or add to PROSE_ONLY_LABELS with a justification):\n${missing
84
+ expect(missing, `Labels declared in a schema-*.md node-type table but missing from schema.cypher (add a CREATE CONSTRAINT or CREATE INDEX FOR (x:Label) line, or — if it is an additional label on a base node — add it to ADDITIONAL_BASE_LABELS in platform/lib/graph-style):\n${missing
65
85
  .map((m) => ` :${m.label} (${m.file})`)
66
86
  .join("\n")}`).toEqual([]);
67
87
  });
88
+ test("the additional-label exemptions come from the shared declaration", () => {
89
+ // Task 1893: this guard and the /graph chip allowlist read one set. If this
90
+ // fails, someone re-introduced a local copy.
91
+ for (const label of ADDITIONAL_BASE_LABELS) {
92
+ expect(EXEMPT_LABELS.has(label), `:${label} should be exempt`).toBe(true);
93
+ }
94
+ // Email is the only exemption that is NOT an additional label.
95
+ const extra = [...EXEMPT_LABELS].filter((l) => !ADDITIONAL_BASE_LABELS.has(l));
96
+ expect(extra).toEqual(["Email"]);
97
+ });
68
98
  test("estate-agent labels are declared in schema.cypher", () => {
69
99
  // Task 358 acceptance — explicit assertion against regression.
70
100
  const declared = new Set(parseLabelsFromSchemaCypher(readFileSync(SCHEMA_CYPHER_PATH, "utf-8")));
@@ -1 +1 @@
1
- {"version":3,"file":"schema-cypher-drift.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/schema-cypher-drift.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,2BAA2B,EAAE,MAAM,8DAA8D,CAAC;AAE3G,qDAAqD;AACrD,EAAE;AACF,yDAAyD;AACzD,uEAAuE;AACvE,0CAA0C;AAC1C,sEAAsE;AACtE,0EAA0E;AAC1E,uEAAuE;AACvE,oEAAoE;AACpE,qEAAqE;AACrE,iCAAiC;AAEjC,MAAM,cAAc,GAAG,OAAO,CAC5B,MAAM,CAAC,IAAI,CAAC,OAAO,EACnB,wBAAwB,CACzB,CAAC;AACF,MAAM,kBAAkB,GAAG,OAAO,CAChC,MAAM,CAAC,IAAI,CAAC,OAAO,EACnB,uCAAuC,CACxC,CAAC;AAEF,oEAAoE;AACpE,uEAAuE;AACvE,oEAAoE;AACpE,yEAAyE;AACzE,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS;IACxC,sEAAsE;IACtE,wDAAwD;IACxD,WAAW;IACX,0EAA0E;IAC1E,wEAAwE;IACxE,sEAAsE;IACtE,UAAU;IACV,0EAA0E;IAC1E,0EAA0E;IAC1E,2DAA2D;IAC3D,UAAU;IACV,oEAAoE;IACpE,uEAAuE;IACvE,oEAAoE;IACpE,gEAAgE;IAChE,OAAO;CACR,CAAC,CAAC;AAEH,SAAS,gBAAgB,CAAC,IAAY;IACpC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,iEAAiE;IACjE,+CAA+C;IAC/C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,yBAAyB,CAAC,EAAE,CAAC;QAC7D,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,IAAI,CAAC,sDAAsD,EAAE,GAAG,EAAE;IAChE,MAAM,QAAQ,GAAG,IAAI,GAAG,CACtB,2BAA2B,CAAC,YAAY,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC,CACvE,CAAC;IACF,MAAM,KAAK,GAAG,WAAW,CAAC,cAAc,CAAC,CAAC,MAAM,CAC9C,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAC7D,CAAC;IACF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAExC,MAAM,OAAO,GAA2C,EAAE,CAAC;IAC3D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;QAC/D,KAAK,MAAM,KAAK,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3C,IAAI,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS;YAC3C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,CACJ,OAAO,EACP,yKAAyK,OAAO;SAC7K,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC;SACvC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AAChB,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;IAC7D,+DAA+D;IAC/D,MAAM,QAAQ,GAAG,IAAI,GAAG,CACtB,2BAA2B,CAAC,YAAY,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC,CACvE,CAAC;IACF,KAAK,MAAM,KAAK,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,CAAC;QAC/E,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,aAAa,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChF,CAAC;AACH,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"schema-cypher-drift.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/schema-cypher-drift.test.ts"],"names":[],"mappings":"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"}
@@ -188,7 +188,7 @@ It is **armed by the always-running `platform/ui` server on an interval**, becau
188
188
 
189
189
  Env: `PLATFORM_ROOT`, `NEO4J_URI` (and `NEO4J_PASSWORD` or `config/.neo4j-password`). Per account it reads `data/accounts/<id>/calendar-availability.json` (for `bookingDbName`). The Cloudflare credential is read house-only from `config/cloudflare-house.env`; that credential is a minter, so the calendar mints a `calendar-d1` (D1-only) token from it via `cf-token.sh` and shells `wrangler` for the D1 reads/writes on that scoped token, never the minter directly.
190
190
 
191
- Observability: one `[calendar-booking]` line per step keyed by `bookingId` (`reconcile-claim`, `meeting-write`, `notify`, `booked`), and one `[calendar-reconcile] account=… eligibleExamined=… pendingUnreconciled=… orphanMeetings=…` audit line per account per run. `eligibleExamined` separates "nothing to do" from "verified clean". A `pendingUnreconciled` that persists across runs means those rows are failing to become meetings rather than merely lagging — the `attempt=` count on the claim and failure lines says how many times each has been tried. `orphanMeetings` means a booking meeting has no backing eligible D1 row.
191
+ Observability: one `[calendar-booking]` line per step keyed by `bookingId` (`reconcile-claim`, `meeting-write`, `notify`, `booked`), and one `[calendar-reconcile] account=… eligibleExamined=… pendingUnreconciled=… pendingOldestAgeMin=… orphanMeetings=…` audit line per account per run. `eligibleExamined` separates "nothing to do" from "verified clean". A `pendingUnreconciled` that persists across runs means those rows are failing to become meetings rather than merely lagging — the `attempt=` count on the claim and failure lines says how many times each has been tried. `orphanMeetings` means a booking meeting has no backing eligible D1 row. `pendingOldestAgeMin` ages that backlog: a count cannot separate a short lag from a nine-hour one. The companion `[calendar-reconcile] op=liveness lastRunAgeMin=… lastAuditAgeMin=… lastSkipReason=… stale=<bool>` line is emitted every ten minutes by the platform/ui server rather than by this script, because the failure it detects is this script not reporting at all. `op=liveness-write-failed` is the one case where that record could not be written; it carries the error body and never stops the run, because a run that worked must not be reported as failed for losing its own bookkeeping.
192
192
 
193
193
  ## Ingesting calendar archives (.ics → graph)
194
194
 
@@ -42,8 +42,8 @@ function refuseNoAccount(toolName) {
42
42
  // read. NOT boot-throw on missing — the silent-compaction path
43
43
  // (compaction.ts:53) intentionally spawns this MCP without USER_ID for
44
44
  // summary-only turns that don't invoke scheduling tools. The lib's
45
- // per-call MissingUserIdError handles the actual bug class (caller forgot
46
- // to thread userId on a real tool invocation).
45
+ // per-call MissingUserIdError handles the actual bug class (Task 1895: no
46
+ // resolvable identity no owner AdminUser and no acting userId).
47
47
  const userId = process.env.USER_ID || "";
48
48
  const sessionId = process.env.SESSION_ID || undefined;
49
49
  const agentSlug = process.env.AGENT_SLUG || undefined;